diff --git a/src/assets/core.scss b/src/assets/core.scss index b1acba5..72e2ebf 100644 --- a/src/assets/core.scss +++ b/src/assets/core.scss @@ -29,12 +29,12 @@ } ::-webkit-scrollbar-thumb { - background: #ccc; + background: rgba(64, 150, 255, 0.5); height: 10px; border-radius: 5px; &:hover { - background: #999; + background: rgba(64, 150, 255, 1); cursor: pointer; } } @@ -176,9 +176,22 @@ } } +.page-action-to-top { + @apply text-right; + .btn-to-top { + @apply w-[44px] h-[44px] inline-block bg-blue-300 text-center p-0 transition hover:bg-blue-500; + min-width: 0; + border-radius: 50px; + font-size: 24px; + } +} + .data-list-container { height: calc(100vh - var(--app-header-header) - 200px); overflow: auto; + margin-right: -20px; + padding-right: 16px; + scrollbar-gutter: stable; .data-list-container-inner { @@ -186,6 +199,20 @@ } // override antd style +.ant-checkbox { + border-radius: 2px; + + .ant-checkbox-inner { + border-radius: 2px; + width: 18px; + height: 18px; + + &:after { + //inset-inline-start: 28%; + } + } +} + .ant-message { z-index: var(--message-z-index); } @@ -259,6 +286,7 @@ .select-value { @apply text-blue-500 px-4 cursor-pointer h-[31px]; } + .options-list-container { @apply overflow-auto py-1 drop-shadow absolute top-[30px]; border-radius: 0 0 0.75rem 0.75rem; @@ -281,7 +309,8 @@ .tag-select-child-container { .ant-popover-inner { - @apply p-0 overflow-hidden drop-shadow shadow-none; // + @apply p-0 overflow-hidden drop-shadow shadow-none; + // background: linear-gradient(180deg, rgb(235, 242, 253) 0%, rgb(244, 247, 252) 100%); border-radius: 0 0.75rem 0.75rem 0; transform: translate(16px, -7px); diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index 04c873f..575a6f8 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -69,13 +69,11 @@ export const IconPin = ({style, className}: IconProps) => ( ) export const IconDelete = ({style, className}: IconProps) => ( + width="0.86em" height="1em" viewBox="0 0 20 23" version="1.1"> - + d="M3.66667 22.3828C2.99444 22.3828 2.41918 22.1437 1.94089 21.6654C1.46259 21.1871 1.22304 20.6114 1.22222 19.9384V4.04948H0V1.60503H6.11111V0.382812H13.4444V1.60503H19.5556V4.04948H18.3333V19.9384C18.3333 20.6106 18.0942 21.1863 17.6159 21.6654C17.1376 22.1445 16.5619 22.3836 15.8889 22.3828H3.66667ZM15.8889 4.04948H3.66667V19.9384H15.8889V4.04948ZM6.11111 17.4939H8.55555V6.49392H6.11111V17.4939ZM11 17.4939H13.4444V6.49392H11V17.4939Z" + fill="currentColor" /> + ) export const IconAddText = ({style, className}: IconProps) => ( @@ -84,6 +82,8 @@ export const IconAddText = ({style, className}: IconProps) => ( + ) @@ -130,9 +130,8 @@ export const IconLive = ({style, className}: IconProps) => ( export const IconEdit = ({style, className}: IconProps) => ( - + width="1em" height="1em" viewBox="0 0 23 24" version="1.1"> + ) \ No newline at end of file diff --git a/src/components/scoller/button-to-top.tsx b/src/components/scoller/button-to-top.tsx new file mode 100644 index 0000000..66b7b52 --- /dev/null +++ b/src/components/scoller/button-to-top.tsx @@ -0,0 +1,26 @@ +import {Button} from "antd"; +import {ArrowUpOutlined} from "@ant-design/icons"; + + +type ButtonToTopProps = { + onClick?: () => void; + visible?: boolean; + container?: HTMLElement | string; +} +export default function ButtonToTop(props: ButtonToTopProps) { + return ( +
+ {props.visible && } +
+ ) +} \ No newline at end of file diff --git a/src/components/scoller/infinite-scroller.tsx b/src/components/scoller/infinite-scroller.tsx index e25bd78..288c46a 100644 --- a/src/components/scoller/infinite-scroller.tsx +++ b/src/components/scoller/infinite-scroller.tsx @@ -1,12 +1,17 @@ -import React, {useEffect} from "react"; -import {useInViewport} from "ahooks"; +import React, {CSSProperties, useCallback, useEffect, useImperativeHandle, useRef} from "react"; +import {useInViewport, useScroll} from "ahooks"; +export type InfiniteScrollerRef = { + scrollToPosition: (top: number) => void +}; export type InfiniteScrollerProps = { children?: React.ReactNode; className?: string; rootClassName?: string; + style?: CSSProperties; loadingPlaceholder?: React.ReactNode; onCallback: (page: number, prevPage) => void; + onScroll?: (top: number) => void; empty?: React.ReactNode; loading?: boolean; pagination?: { @@ -15,10 +20,32 @@ export type InfiniteScrollerProps = { total: number; }; } - -export default function InfiniteScroller(props: InfiniteScrollerProps) { +const InfiniteScroller = React.forwardRef((props, ref) => { const {pagination} = props; const [inView] = useInViewport(() => document.querySelector('.data-load-control-element')) + const scrollContainerRef = useRef(null) + const scrollPosition = useScroll(scrollContainerRef); + const scrollToPosition = useCallback((top: number) => { + if (scrollContainerRef.current) { + console.log('xaf'); + scrollContainerRef.current.scrollTo({ + top, + behavior: 'smooth' + }) + } + }, [scrollContainerRef]) + + useImperativeHandle(ref, () => { + return { + scrollToPosition + } + }) + + useEffect(() => { + if (scrollPosition && props.onScroll) { + props.onScroll(scrollPosition.top) + } + }, [scrollPosition]) useEffect(() => { if (!pagination) return; @@ -30,16 +57,18 @@ export default function InfiniteScroller(props: InfiniteScrollerProps) { } } }, [inView]) - return (
+ + return (
{props.children}
{props?.pagination && props.pagination.total > props.pagination.limit * props.pagination.page && (props.loadingPlaceholder ||
加载中...
)} {props?.empty && props.pagination?.total == 0 &&
-
- {props.empty} +
+ {props.empty}
}
); -} \ No newline at end of file +}) //(props: InfiniteScrollerProps) =>{} +export default InfiniteScroller \ No newline at end of file diff --git a/src/pages/news/components/edit-search-form.tsx b/src/pages/news/components/edit-search-form.tsx index 094b77d..ee66dd7 100644 --- a/src/pages/news/components/edit-search-form.tsx +++ b/src/pages/news/components/edit-search-form.tsx @@ -49,11 +49,11 @@ export default function EditSearchForm(props: { placeholder="请输入新闻标题关键词进行搜索" onPressEnter={handleSubmit} /> - 来源 - + {/*来源*/} + {/**/}
) diff --git a/src/pages/news/components/style.module.scss b/src/pages/news/components/style.module.scss index 708fc0b..7fd0a67 100644 --- a/src/pages/news/components/style.module.scss +++ b/src/pages/news/components/style.module.scss @@ -31,10 +31,51 @@ } .newListTable{ :global{ - .header{} - .body{} .row{ - @apply bg-white mt-2 p-4 rounded; + @apply bg-white mt-2 py-2 px-4 rounded-xl gap-2 border; + border-width: 2px; + &.checked{ + @apply border-primary-blue bg-primary-blue-bg; + } + } + .col{ + @apply flex items-center relative pl-6; + height: 44px; + &:after{ + @apply absolute; + border-right: solid 1px #e8e8e8; + content: ' '; + top:2px; + bottom: 2px; + left:0; + } + } + .title{ + @apply flex-1 pl-0; + &:after{ + display: none; + } + } + .source{ + width: 150px; + } + .time{ + width: 150px; + } + .operations{ + @apply gap-4; + width: 100px; + } + .header{ + @apply bg-primary-bg; + .operations{ + } + } + .body{} + .icon-btn{ + @apply text-gray-400 hover:text-blue-500 cursor-pointer; + font-size: 18px; + } } } \ No newline at end of file diff --git a/src/pages/news/edit.tsx b/src/pages/news/edit.tsx index 0bf5626..2eaeced 100644 --- a/src/pages/news/edit.tsx +++ b/src/pages/news/edit.tsx @@ -1,142 +1,136 @@ -import {Button, Checkbox, Pagination, Space, Table, TableColumnsType, TableProps, Typography} from "antd"; +import {Checkbox, Space} from "antd"; -import {Card} from "@/components/card"; -import React, {useState} from "react"; +import React, {useRef, useState} from "react"; import {useRequest} from "ahooks"; import {formatTime} from "@/util/strings.ts"; import ArticleEditModal from "@/components/article/edit-modal.tsx"; import {getList} from "@/service/api/article.ts"; import EditSearchForm from "@/pages/news/components/edit-search-form.tsx"; import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx"; -import {Key} from "antd/es/table/interface"; import styles from './components/style.module.scss' -import InfiniteScroller from "@/components/scoller/infinite-scroller.tsx"; +import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx"; +import {IconDelete, IconEdit} from "@/components/icons"; +import {clsx} from "clsx"; +import ButtonToTop from "@/components/scoller/button-to-top.tsx"; export default function NewEdit() { + const [state, setState] = useState<{ + checkAll?: boolean; + showToTop?: boolean; + }>({}) const [editId, setEditId] = useState(-1) const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [params, setParams] = useState({ pagination: {page: 1, limit: 10} }) - const {data, refresh, loading} = useRequest(() => getList(params), {refreshDeps: [params]}) + const [data, setData] = useState>() + const {refresh, loading} = useRequest(() => getList(params), { + refreshDeps: [params], + onSuccess: (data) => { + setData(prev => { + // 判断页码是否是第1页 + if (data.pagination.page == 1) return data; + return { + list: [...(prev?.list || []), ...(data?.list || [])], + pagination: data.pagination + } + }) + } + }) - // : TableColumnsType - const columns = [ - { - title: '标题', - dataIndex: 'title', - render: (_, record) => (
-
{_}
-
{record.summary}
-
) - }, - { - title: '来源', - minWidth: 150, - dataIndex: 'media_name', - }, - { - title: '时间', - width: 150, - dataIndex: 'time', - render: (_, record) => { - return formatTime(record.publish_time, 'YYYY-MM-DD HH:mm') - } - }, - { - title: '操作', - width: 80, - align: 'center', - render: (_, record) => ( - - - - ), - }, - ]; + const handleItemChecked = (checked: boolean, item: ListArticleItem) => { + if (checked) { + setSelectedRowKeys(prev => [...prev, item.id]) + } else { + setSelectedRowKeys(prev => prev.filter(it => it != item.id)) + } + } + const handleCheckAll = (checked: boolean) => { + setState({checkAll: checked}) + if (checked) { + setSelectedRowKeys(data?.list?.map(item => item.id) || []) + } else { + setSelectedRowKeys([]) + } + } + const scrollerRef = useRef(null) return (
- + {/**/}
-
-
-
-
标题
-
来源
-
时间
-
操作
+ +
+
+ + 总共 {data?.list.length} 条 + 已选 {selectedRowKeys.length} 条 + +
+ { + handleCheckAll(!state.checkAll) + }}>全选 + { + handleCheckAll(e.target.checked) + }} + indeterminate={selectedRowKeys.length > 0 && selectedRowKeys.length < data?.list.length}>
- { +
+
+
+
标题
+
来源
+
时间
+
操作
+
+ { setParams(prev => ({ ...prev, pagination: {page, limit: 10} })) - }} loading={loading} pagination={data?.pagination}> + }} onScroll={(top)=> setState({showToTop: top > 30})} loading={loading} pagination={data?.pagination}>
{data?.list?.map((item, i) => { - return
-
-
{item.title}
-
{item.summary}
+ const checked = selectedRowKeys.includes(item.id) + return
+
+
+
{item.title}
+
{item.summary}
+
-
-
{item.media_name}
+
+
{item.media_name}
-
+
{formatTime(item.publish_time, 'YYYY-MM-DD HH:mm')}
+ className="text-sm">{formatTime(item.publish_time, 'YYYY-MM-DD HH:mm')}
-
- - - - - +
+ setEditId(item.id)}> + + handleItemChecked(e.target.checked, item)}/>
})}
- {/**/} - {/* columns={columns}*/} - {/* dataSource={data?.list || []}*/} - {/* rowKey={'id'}*/} - {/* bordered={false}*/} - {/* pagination={false}*/} - {/* rowHoverable={false}*/} - {/* className={styles.newListTable}*/} - {/*/>*/} - {data?.pagination && data?.pagination.total > 0 && -
- setParams(prev => ({ - ...prev, - pagination: {page, limit: 10} - }))} - /> - -
} + +
+ scrollerRef.current?.scrollToPosition(0)} /> +
+ +
+
}
-
已选 {checkedId.length} 条
+ + 总共 {data?.list.length} 条 + 已选 {checkedId.length} 条 +
{ handleCheckAll(!state.checkAll) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index ae5bde1..c7b5678 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -31,6 +31,7 @@ const AppRouter = () => { theme={{ token: { borderRadius: 4, + }, }} diff --git a/tailwind.config.js b/tailwind.config.js index c678899..9b69019 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,6 +3,7 @@ const themeConfig = { colors:{ 'primary':'#7356f6', 'primary-blue':'rgb(64, 150, 255)', + 'primary-blue-bg':'#d9eaff', 'primary-bg': 'rgb(244, 247, 252)', 'info':'rgba(238, 245, 255, 1)', 'news-to-edit':'#ececec',