update 全选逻辑,批量操作按钮样式
This commit is contained in:
parent
cb316aa596
commit
7ccd5b4086
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
@ -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>)
|
||||||
}
|
}
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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}>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user