From be22fc387afb1f2a8e932ab88cf9ecfa594cbefa Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sun, 15 Dec 2024 18:00:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=8C=89=E9=92=AE;=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=B1=95=E7=A4=BA;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/core.scss | 17 +++ src/components/article/edit-modal.tsx | 49 +++---- src/components/button-batch.tsx | 56 ++++++++ src/components/message.ts | 12 +- src/pages/live/index.tsx | 4 - src/pages/video/index.tsx | 178 +++++++++++++------------- src/service/api/video.ts | 5 +- 7 files changed, 189 insertions(+), 132 deletions(-) create mode 100644 src/components/button-batch.tsx diff --git a/src/assets/core.scss b/src/assets/core.scss index de064aa..47b4bef 100644 --- a/src/assets/core.scss +++ b/src/assets/core.scss @@ -19,6 +19,22 @@ @tailwind utilities; +::-webkit-scrollbar { + width: 4px; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: #999; + height: 10px; + border-radius: 5px; + + &:hover { + background: #666; + cursor: pointer; + } +} + .btn { @apply px-5 py-2 rounded-md bg-white border text-sm; &:hover { @@ -124,6 +140,7 @@ min-height: 300px; max-height: calc(100vh - var(--app-header-header) - 300px); overflow: auto; + padding-right: 10px; } .live-video-list-sort-container{ diff --git a/src/components/article/edit-modal.tsx b/src/components/article/edit-modal.tsx index 4896d71..f607075 100644 --- a/src/components/article/edit-modal.tsx +++ b/src/components/article/edit-modal.tsx @@ -11,20 +11,24 @@ type Props = { onClose?: (saved?: boolean) => void; } +const DEFAULT_STATE = { + loading: false, + open: false, + msgTitle: '', + msgGroup: '', + error:'' +} export default function ArticleEditModal(props: Props) { const [groups, setGroups] = useState([]); const [title, setTitle] = useState('') const [state, setState] = useSetState({ - loading: false, - open: false, - msgTitle: '', - msgGroup: '', + ...DEFAULT_STATE }) // 保存数据 const handleSave = () => { - console.log(groups, title) + setState({error: ''}) if (!title) { // setState({msgTitle: '请输入标题内容'}); return; @@ -37,39 +41,24 @@ export default function ArticleEditModal(props: Props) { setState({loading: true}) save(title, groups, props.id > 0 ? props.id : undefined).then(() => { props.onClose?.(true) + }).catch(e=>{ + setState({error: e.data || '保存失败,请重试!'}) }).finally(() => { setState({loading: false}) }); } useEffect(() => { + setState({...DEFAULT_STATE}) if (props.id) { if (props.id > 0) { - if (props.type == 'news') { - article.getById(props.id).then(res => { - setGroups(res.content_group) - setTitle(res.title) - }) - } + article.getById(props.id).then(res => { + setGroups(res.content_group) + setTitle(res.title) + }) } else { // 新增 - setGroups([ - [{ - type: 'text', - content: '韩国国会当地时间14日16时举行全体会议,就在野党阵营第二次提出的尹锡悦总统弹劾案进行表决。根据投票结果,有204票赞成,85票反对,3票弃权,8票无效,弹劾案最终获得通过,尹锡悦的总统职务立即停止。' - }], - [ - { - type: 'text', - content: '韩国宪法法院将在180天内完成弹劾案审判程序。如果宪法法院作出弹劾案不成立的裁决,尹锡悦将立即恢复总统职务;如果宪法法院认可弹劾案成立,尹锡悦将立即被罢免,预计韩国将在明年4月至6月间举行大选。' - }, - { - type: 'image', - content: 'https://zverse-on.oss-cn-shanghai.aliyuncs.com/metahuman/workbench/20241214/193c442df75.jpeg' - }, - - ], - ]) - setTitle('韩国国会通过总统弹劾案 尹锡悦职务立即停止') + setGroups([]) + setTitle('') } } }, [props.id]) @@ -83,6 +72,7 @@ export default function ArticleEditModal(props: Props) { onCancel={()=>props.onClose?.()} okButtonProps={{loading: state.loading}} onOk={handleSave} + okText={props.type == 'news' ? '确定' : '重新生成'} >
@@ -111,6 +101,7 @@ export default function ArticleEditModal(props: Props) { }} />
+ {state.error &&
{state.error}
}
); } \ No newline at end of file diff --git a/src/components/button-batch.tsx b/src/components/button-batch.tsx new file mode 100644 index 0000000..2f55bfb --- /dev/null +++ b/src/components/button-batch.tsx @@ -0,0 +1,56 @@ +import React, {useState} from "react"; +import {Button, Modal} from "antd"; +import {ButtonType} from "antd/es/button"; +import {showErrorToast, showToast} from "@/components/message.ts"; + +type Props = { + selected: any[], + type?: ButtonType; + emptyMessage: string, + confirmMessage: React.ReactNode, + onProcess: (ids: Id[]) => Promise + successMessage?: string; + onSuccess?: () => void; + children?: React.ReactNode + +} +/** + * 统一批量操作按钮 + */ +export default function ButtonBatch( + { + selected, emptyMessage, successMessage, children, + type, confirmMessage, onProcess,onSuccess + }: Props) { + const [loading, setLoading] = useState(false) + const onBatchProcess = async () => { + setLoading(true) + try { + await onProcess(selected) + if (successMessage) showToast(successMessage, 'success') + if (onSuccess) { + onSuccess() + } + } catch (e) { + showErrorToast(e) + } finally { + setLoading(false) + } + } + const handleBtnClick = () => { + if (selected.length == 0) { + showToast(emptyMessage, 'warning') + return; + } + Modal.confirm({ + title: '操作提示', + centered: true, + content: confirmMessage, + onOk: onBatchProcess + }) + } + + return ( + + ) +} \ No newline at end of file diff --git a/src/components/message.ts b/src/components/message.ts index 80b258c..e37404b 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -1,16 +1,18 @@ import {message} from "antd"; import {BizError} from "@/service/types.ts"; -export function showToast(content: string, type?: 'success' | 'info' | 'warning' | 'error') { +export function showToast(content: string, type?: 'success' | 'info' | 'warning' | 'error', duration?: number) { message.open({ type, content, + duration, className: 'aui-toast' }).then(); } -export function showErrorToast(e:Error|BizError) { - showToast(String(((e instanceof BizError)?e.data:'') || e.message),'error') + +export function showErrorToast(e: Error | BizError) { + showToast(String(((e instanceof BizError) ? e.data : '') || e.message), 'error') } @@ -22,14 +24,14 @@ export function showLoading(content = 'Loading...') { content, }).then(); return { - update(content: string,type?: 'success' | 'info' | 'warning' | 'error'){ + update(content: string, type?: 'success' | 'info' | 'warning' | 'error') { message.open({ key, content, type }).then(); }, - close(){ + close() { message.destroy(key); } } diff --git a/src/pages/live/index.tsx b/src/pages/live/index.tsx index ee20e2a..ce03999 100644 --- a/src/pages/live/index.tsx +++ b/src/pages/live/index.tsx @@ -185,10 +185,6 @@ export default function LiveIndex() { :
} -
- - -
diff --git a/src/pages/video/index.tsx b/src/pages/video/index.tsx index 0ba080a..59a9c0e 100644 --- a/src/pages/video/index.tsx +++ b/src/pages/video/index.tsx @@ -1,4 +1,4 @@ -import {Empty, message, Modal} from "antd"; +import {Empty, Modal} from "antd"; import React, {useEffect, useMemo, useRef, useState} from "react"; import {DndContext} from "@dnd-kit/core"; import {arrayMove, SortableContext} from "@dnd-kit/sortable"; @@ -8,17 +8,15 @@ import {clsx} from "clsx"; import {VideoListItem} from "@/components/video/video-list-item.tsx"; import ArticleEditModal from "@/components/article/edit-modal.tsx"; -import {getList} from "@/service/api/video.ts"; +import {deleteByIds, getList} from "@/service/api/video.ts"; import {formatDuration} from "@/util/strings.ts"; import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx"; +import ButtonBatch from "@/components/button-batch.tsx"; export default function VideoIndex() { const [editId, setEditId] = useState(-1) - const [videoData, setVideoData] = useState([]) - - const [modal, contextHolder] = Modal.useModal() const videoRef = useRef(null) const [state, setState] = useSetState({ @@ -26,45 +24,31 @@ export default function VideoIndex() { playingIndex: -1, }) const [checkedIdArray, setCheckedIdArray] = useState([]) - const processDeleteVideo = async (_idArray: number[]) => { - message.info('删除成功!!!' + _idArray.join('')); - } - useEffect(() => { + + // 加载列表 + const loadList = () => { getList().then((ret) => { setVideoData(ret.list || []) - }) - }, []) - - const handleDeleteBatch = () => { - modal.confirm({ - title: '提示', - content: '是否要删除选择的视频?', - onOk: () => processDeleteVideo(checkedIdArray) + setState({checkedAll: false, playingIndex: -1}) }) } + // 播放视频 const playVideo = (video: VideoInfo, playingIndex: number) => { - setState({ - playingIndex - }) - console.log('play', video) - // if (videoRef.current) { - // videoRef.current!.src = video.play_url - // } + setState({playingIndex}) + if (videoRef.current && video.oss_video_url) { + videoRef.current!.src = video.oss_video_url + } } + // 处理全选 const handleAllCheckedChange = () => { - // setVideoData(list=>{ - // list.map(s=>{ - // s.checked = !state.checkedAll - // }) - // return list - // }) setCheckedIdArray(state.checkedAll ? [] : videoData.map(v => v.id)) setState({ checkedAll: !state.checkedAll }) } - + // + useEffect(loadList, []) const totalDuration = useMemo(() => { if (!videoData || videoData.length == 0) return 0; // 计算总时长 @@ -75,76 +59,86 @@ export default function VideoIndex() { {contextHolder}
-
+
视频时长: {formatDuration(totalDuration)}
-
- 批量删除 +
+ 批量删除 +
-
- {videoData.length == 0 ?
: <> -
- {videoData.map((v, index) => ( -
-
{index + 1}
-
- ))} -
-
- { - const {active, over} = e; - if (over && active.id !== over.id) { - let oldIndex = -1, newIndex = -1; - const originArr = [...videoData] - setVideoData((items) => { - oldIndex = items.findIndex(s => s.id == active.id); - newIndex = items.findIndex(s => s.id == over.id); - return arrayMove(items, oldIndex, newIndex); - }); - modal.confirm({ - title: '提示', - content: '是否要移动到指定位置', - onCancel: () => { - setVideoData(originArr); - } - }) - } - }}> - - {videoData.map((v, index) => ( - { - setCheckedIdArray(idArray => { - const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id); - setState({checkedAll: newArr.length == videoData.length}) - return newArr; - }) - }} - onPlay={() => playVideo(v, index)} - onEdit={() => { - setEditId(v.article_id) - }} - editable - />))} - - -
- } +
+
+ {videoData.length == 0 ?
: <> +
+ {videoData.map((v, index) => ( +
+
{index + 1}
+
+ ))} +
+
+ { + const {active, over} = e; + if (over && active.id !== over.id) { + let oldIndex = -1, newIndex = -1; + const originArr = [...videoData] + setVideoData((items) => { + oldIndex = items.findIndex(s => s.id == active.id); + newIndex = items.findIndex(s => s.id == over.id); + return arrayMove(items, oldIndex, newIndex); + }); + modal.confirm({ + title: '提示', + content: '是否要移动到指定位置', + onCancel: () => { + setVideoData(originArr); + } + }) + } + }}> + + {videoData.map((v, index) => ( + { + setCheckedIdArray(idArray => { + const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id); + setState({checkedAll: newArr.length == videoData.length}) + return newArr; + }) + }} + onPlay={() => playVideo(v, index)} + onEdit={() => { + setEditId(v.article_id) + }} + editable + />))} + + +
+ } +
-
+
@@ -157,6 +151,6 @@ export default function VideoIndex() {
- setEditId(-1)}/> + setEditId(-1)}/>
) } \ No newline at end of file diff --git a/src/service/api/video.ts b/src/service/api/video.ts index 9d514ba..d85dad3 100644 --- a/src/service/api/video.ts +++ b/src/service/api/video.ts @@ -25,10 +25,11 @@ export function getById(id: Id) { return post({url: '/video/detail/' + id}) } -export function deleteById(id: Id) { - return post({url: '/video/detail/' + id}) +export function deleteByIds(ids: Id[]) { + return post('/video/remove', {ids}) } + export function modifyOrder(ids: Id[]) { return post('/video/modifyorder', {ids}) }