update 全选逻辑,批量操作按钮样式

This commit is contained in:
LittleBoy 2024-12-22 22:55:38 +08:00
parent cb316aa596
commit 7ccd5b4086
11 changed files with 67 additions and 53 deletions

View File

@ -343,9 +343,11 @@
.page-action { .page-action {
@apply fixed right-10 bottom-10 flex flex-col gap-4; @apply fixed right-10 bottom-10 flex flex-col gap-4;
button { button {
@apply border-0 min-w-[120px] h-[40px] rounded-3xl text-white pr-4 flex items-center justify-between; @apply border-0 min-w-[120px] h-[40px] rounded-3xl pr-4 flex items-center justify-between drop-shadow;
.text { .text {
flex: 1; flex: 1;
text-align: left;
padding-left: 15px;
} }
&:disabled { &:disabled {

View File

@ -10,7 +10,7 @@ type ButtonToTopProps = {
export default function ButtonToTop(props: ButtonToTopProps) { export default function ButtonToTop(props: ButtonToTopProps) {
return ( return (
<div className={'page-action-to-top'}> <div className={'page-action-to-top'}>
{props.visible && <button className="btn-to-top" onClick={()=>{ {props.visible && <button className="btn-to-top text-white" onClick={()=>{
console.log(props) console.log(props)
if(props.onClick){ if(props.onClick){
props.onClick() props.onClick()

View File

@ -24,7 +24,7 @@ export default function LiveIndex() {
const [modal, contextHolder] = Modal.useModal() const [modal, contextHolder] = Modal.useModal()
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([]) const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
const [editable, setEditable] = useState<boolean>(false) const [editable, setEditable] = useState<boolean>(false)
const scrollerRef = useRef<InfiniteScrollerRef|null>(null) const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const [state, setState] = useSetState({ const [state, setState] = useSetState({
activeIndex: -1, activeIndex: -1,
@ -128,7 +128,7 @@ export default function LiveIndex() {
}).catch(showErrorToast) }).catch(showErrorToast)
} }
const handleConfirm = () => { const handleConfirm = () => {
if(!editable){ if (!editable) {
setEditable(true) setEditable(true)
return; return;
} }
@ -164,11 +164,11 @@ export default function LiveIndex() {
return videoData.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0); return videoData.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0);
}, [videoData]) }, [videoData])
const currentSelectedId = useMemo(()=>{ const currentSelectedId = useMemo(() => {
if(state.activeIndex < 0 || state.activeIndex >= videoData.length) return []; if (state.activeIndex < 0 || state.activeIndex >= videoData.length) return [];
const currentId = videoData[state.activeIndex]; const currentId = videoData[state.activeIndex];
return checkedIdArray.filter(id => currentId.id != id) return checkedIdArray.filter(id => currentId.id != id)
},[checkedIdArray,state.activeIndex]) }, [checkedIdArray, state.activeIndex])
return (<div className="container py-10 page-live"> return (<div className="container py-10 page-live">
{contextHolder} {contextHolder}
@ -189,14 +189,14 @@ export default function LiveIndex() {
<div className="video-list-container video-list-sort-container flex-1"> <div className="video-list-container video-list-sort-container flex-1">
<div className="live-control flex justify-between mb-4"> <div className="live-control flex justify-between mb-4">
<div className="text-sm"> <div className="text-sm">
<span>{state.activeIndex == -1?'暂未播放':`播放到${state.activeIndex}`},</span> <span>{state.activeIndex == -1 ? '暂未播放' : `播放到${state.activeIndex}`},</span>
<span>{videoData.length}</span> <span>{videoData.length}</span>
</div> </div>
<div className="flex gap-2 items-center text-sm"> <div className="flex gap-2 items-center text-sm">
<div className={'flex items-center text-gray-400 cursor-pointer select-none'} <div className={'flex items-center text-gray-400 cursor-pointer select-none'}
onClick={handleConfirm}> onClick={handleConfirm}>
<span>{editable?'已解锁':'锁定状态不可排序'}</span> <span>{editable ? '已解锁' : '锁定状态不可排序'}</span>
<span className="ml-2"> <span className="ml-2">
{editable ? <IconUnlock/> : <IconLocked/>} {editable ? <IconUnlock/> : <IconLocked/>}
</span> </span>
@ -222,7 +222,7 @@ export default function LiveIndex() {
</div> </div>
<div className=""> <div className="">
<div className="live-video-list-sort-container "> <div className="live-video-list-sort-container ">
<InfiniteScroller ref={scrollerRef} onScroll={top=> setState({showToTop: top > 30})}> <InfiniteScroller ref={scrollerRef} onScroll={top => setState({showToTop: top > 30})}>
<div className="sort-list-container flex-1"> <div className="sort-list-container flex-1">
<DndContext onDragEnd={(e) => { <DndContext onDragEnd={(e) => {
const {active, over} = e; const {active, over} = e;
@ -266,9 +266,9 @@ export default function LiveIndex() {
</div> </div>
</div> </div>
<div className="page-action"> <div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/> <ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
<ButtonBatch <ButtonBatch
className='bg-gray-300 hover:bg-gray-400' className='bg-gray-300 hover:bg-gray-400 text-white'
selected={currentSelectedId} selected={currentSelectedId}
emptyMessage={`请选择要删除的视频`} emptyMessage={`请选择要删除的视频`}
confirmMessage={`是否删除当前的${currentSelectedId.length}条视频?`} confirmMessage={`是否删除当前的${currentSelectedId.length}条视频?`}
@ -276,7 +276,8 @@ export default function LiveIndex() {
onProcess={processDeleteVideo} onProcess={processDeleteVideo}
> >
<span className={'text'}></span> <span className={'text'}></span>
<IconDelete className={'text-white'} /></ButtonBatch> <IconDelete/>
</ButtonBatch>
</div> </div>
</div>) </div>)
} }

View File

@ -1,13 +1,13 @@
import {App, Button} from "antd"; import {App} from "antd";
import {showToast} from "@/components/message.ts"; import {showToast} from "@/components/message.ts";
import {useState} from "react"; import {useState} from "react";
import {push2article} from "@/service/api/news.ts"; import {push2article} from "@/service/api/news.ts";
import {IconArrowRight, IconDelete} from "@/components/icons"; import {IconDelete} from "@/components/icons";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
export default function ButtonDeleteBatch(props: { ids: Id[]; }) { export default function ButtonDeleteBatch(props: { ids: Id[]; }) {
const {modal} = App.useApp(); const {modal} = App.useApp();
const [loading,setLoading] = useState(false) const [loading, setLoading] = useState(false)
const navigate = useNavigate(); const navigate = useNavigate();
const handlePush = () => { const handlePush = () => {
setLoading(true) setLoading(true)
@ -37,10 +37,10 @@ export default function ButtonDeleteBatch(props: { ids: Id[]; }) {
<button <button
loading={loading} loading={loading}
onClick={onPushClick} onClick={onPushClick}
className='bg-gray-400 hover:bg-gray-500' className='bg-gray-300 hover:bg-gray-400 text-white'
> >
<span className={'text'}></span> <span className={'text'}></span>
<IconDelete className={'text-white'} /> <IconDelete className=""/>
</button> </button>
</div> </div>
) )

View File

@ -65,6 +65,7 @@ async function downloadAsZip(list: NewsInfo[]) {
export default function ButtonNewsDownload(props: { ids: Id[] }) { export default function ButtonNewsDownload(props: { ids: Id[] }) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const onDownloadClick = async (ids: Id[]) => { const onDownloadClick = async (ids: Id[]) => {
if(loading) return;
if (props.ids.length === 0) { if (props.ids.length === 0) {
showToast('请选择要下载的新闻', 'warning') showToast('请选择要下载的新闻', 'warning')
return return
@ -80,13 +81,13 @@ export default function ButtonNewsDownload(props: { ids: Id[] }) {
} }
} }
return ( return (
<Button <button
className={'btn-info'} disabled={loading}
loading={loading} onClick={() => onDownloadClick(props.ids)} className={'btn-action bg-[#eef5ff] text-gray-800 hover:bg-[#d2e3ff]'}
icon={<IconDownload className={'text-white'}/>} onClick={() => onDownloadClick(props.ids)}
iconPosition={'end'}
> >
<span className="text"></span> <span className="text"></span>
</Button> <IconDownload />
</button>
) )
} }

View File

@ -1,4 +1,4 @@
import {App, Button} from "antd"; import {App} from "antd";
import {showToast} from "@/components/message.ts"; import {showToast} from "@/components/message.ts";
import {useState} from "react"; import {useState} from "react";
import {push2article} from "@/service/api/news.ts"; import {push2article} from "@/service/api/news.ts";
@ -22,7 +22,7 @@ export default function ButtonPushNews2Article(props: { ids: Id[]; }) {
} }
const onPushClick = () => { const onPushClick = () => {
if (props.ids.length === 0) { if (props.ids.length === 0) {
showToast('请选择要推的新闻', 'warning') showToast('请选择要推入编辑的新闻', 'warning')
return return
} }
modal.confirm({ modal.confirm({
@ -33,14 +33,13 @@ export default function ButtonPushNews2Article(props: { ids: Id[]; }) {
}) })
} }
return ( return (
<Button <button
loading={loading} loading={loading}
onClick={onPushClick} onClick={onPushClick}
className='btn-action' className='bg-[#4096ff] hover:bg-blue-600 text-white'
icon={<IconArrowRight className={'text-white'} />}
iconPosition={'end'}
> >
<span className={'text'}></span> <span className={'text'}></span>
</Button> <IconArrowRight className={'text-white'} />
</button>
) )
} }

View File

@ -1,8 +1,8 @@
import {Button, Modal} from "antd"; import { Modal} from "antd";
import React, {useState} from "react"; import React, {useState} from "react";
import {showErrorToast, showToast} from "@/components/message.ts"; import {showErrorToast, showToast} from "@/components/message.ts";
import {push2video} from "@/service/api/article.ts"; import {push2video} from "@/service/api/article.ts";
import {IconArrowRight, IconDelete} from "@/components/icons"; import {IconArrowRight} from "@/components/icons";
export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) { export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) {
@ -29,16 +29,15 @@ export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => v
} }
return ( return (
<div> <div>
<Button <button
type="primary" type="primary"
loading={loading} loading={loading}
className='btn-action btn-gray-300' className='bg-[#4096ff] hover:bg-blue-600 text-white'
icon={<IconArrowRight className={'text-white'}/>}
onClick={onPushClick} onClick={onPushClick}
iconPosition={'end'}
> >
<span className={'text'}></span> <span className={'text'}></span>
</Button> <IconArrowRight className={'text-white'}/>
</button>
</div> </div>
) )
} }

View File

@ -77,10 +77,9 @@ export default function NewEdit() {
<span className={'inline-block cursor-pointer mr-2'} onClick={() => { <span className={'inline-block cursor-pointer mr-2'} onClick={() => {
handleCheckAll(!state.checkAll) handleCheckAll(!state.checkAll)
}}></span> }}></span>
<Checkbox checked={state.checkAll} onChange={e => { <Checkbox checked={state.checkAll && selectedRowKeys.length == data?.list.length} onChange={e => {
handleCheckAll(e.target.checked) handleCheckAll(e.target.checked)
}} }} />
indeterminate={selectedRowKeys.length > 0 && selectedRowKeys.length < data?.list.length}></Checkbox>
</div> </div>
</div> </div>
<div className={styles.newListTable}> <div className={styles.newListTable}>

View File

@ -1,4 +1,4 @@
import React, {useState} from "react"; import React, {useRef, useState} from "react";
import {Checkbox, Divider, Empty, Modal, Pagination, Space} from "antd"; import {Checkbox, Divider, Empty, Modal, Pagination, Space} from "antd";
import {useRequest} from "ahooks"; import {useRequest} from "ahooks";
@ -9,7 +9,8 @@ import {formatTime} from "@/util/strings.ts";
import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx"; import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx";
import ButtonNewsDownload from "@/pages/news/components/button-news-download.tsx"; import ButtonNewsDownload from "@/pages/news/components/button-news-download.tsx";
import {clsx} from "clsx"; import {clsx} from "clsx";
import InfiniteScroller from "@/components/scoller/infinite-scroller.tsx"; import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
export default function NewsIndex() { export default function NewsIndex() {
@ -21,6 +22,7 @@ export default function NewsIndex() {
const [state, setState] = useState<{ const [state, setState] = useState<{
checkAll?: boolean; checkAll?: boolean;
showToTop?: boolean;
}>({}) }>({})
const [data, setData] = useState<DataList<ListCrawlerNewsItem>>(); const [data, setData] = useState<DataList<ListCrawlerNewsItem>>();
@ -58,6 +60,7 @@ export default function NewsIndex() {
setCheckedId([]) setCheckedId([])
} }
} }
const scrollerRef = useRef<InfiniteScrollerRef|null>(null)
return (<div className={'container pb-5'}> return (<div className={'container pb-5'}>
@ -79,22 +82,24 @@ export default function NewsIndex() {
<div className="news-list-container"> <div className="news-list-container">
<div className="controls flex justify-end mb-3 gap-2"> <div className="controls flex justify-end mb-3 gap-2">
<Space> <Space>
<span> {data?.list.length} </span> <span> {data?.list?.length || 0} </span>
<span className={'text-blue-500'}> {checkedId.length} </span> <span className={'text-blue-500'}> {checkedId.length} </span>
</Space> </Space>
<div> <div>
<span className={'inline-block cursor-pointer mr-2'} onClick={() => { <span className={'inline-block cursor-pointer mr-2'} onClick={() => {
handleCheckAll(!state.checkAll) handleCheckAll(!state.checkAll)
}}></span> }}></span>
<Checkbox checked={state.checkAll} onChange={e => { <Checkbox checked={state.checkAll && checkedId.length == data?.list.length} onChange={e => {
handleCheckAll(e.target.checked) handleCheckAll(e.target.checked)
}}></Checkbox> }}></Checkbox>
</div> </div>
</div> </div>
<InfiniteScroller <InfiniteScroller
className="grid grid-cols-3 gap-4 lg:grid-cols-4 pb-10" className="grid grid-cols-3 gap-4 lg:grid-cols-4 pb-2"
pagination={data?.pagination} pagination={data?.pagination}
loading={loading} loading={loading}
ref={scrollerRef}
onScroll={(top)=> setState({showToTop: top > 30})}
onCallback={(page) => { onCallback={(page) => {
setParams({...params, pagination: {...params.pagination, page}}) setParams({...params, pagination: {...params.pagination, page}})
}} }}
@ -150,6 +155,7 @@ export default function NewsIndex() {
</div> </div>
<div className="page-action"> <div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)} />
<div> <div>
<ButtonNewsDownload ids={checkedId}/> <ButtonNewsDownload ids={checkedId}/>
</div> </div>

View File

@ -34,9 +34,17 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
}) })
} }
return ( return (
<Button type="primary" loading={loading} onClick={onPushClick}>
<span className="text"></span> <div>
<IconArrowRight /> <button
</Button> type="primary"
disabled={loading}
className='bg-[#4096ff] hover:bg-blue-600 text-white'
onClick={onPushClick}
>
<span className={'text'}></span>
<IconArrowRight />
</button>
</div>
) )
} }

View File

@ -176,13 +176,12 @@ export default function VideoIndex() {
</div> </div>
<div className="page-action"> <div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/> <ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/>
{/*<ButtonDeleteBatch ids={checkedIdArray} onSuccess={loadList}/>*/}
<ButtonBatch <ButtonBatch
onProcess={deleteByIds} onProcess={deleteByIds}
selected={checkedIdArray} selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`} emptyMessage={`请选择要删除的新闻视频`}
title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`} title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`}
className='bg-gray-300 hover:bg-gray-400' className='bg-gray-300 hover:bg-gray-400 text-white'
confirmMessage={`删除后需从新闻素材中`} confirmMessage={`删除后需从新闻素材中`}
onSuccess={() => { onSuccess={() => {
showToast('删除成功!', 'success') showToast('删除成功!', 'success')
@ -190,7 +189,7 @@ export default function VideoIndex() {
}} }}
> >
<span className="text"></span> <span className="text"></span>
<IconDelete className={'text-white'} /> <IconDelete />
</ButtonBatch> </ButtonBatch>
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/> <ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
</div> </div>