From c63b0c088eeba7dd21efa3039458f18ef7f88bcb Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sat, 14 Dec 2024 22:10:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=95=B0=E5=AD=97=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/message.ts | 5 + src/components/video/video-list-item.tsx | 10 +- src/pages/library/components/search-form.tsx | 15 ++- src/pages/live/index.tsx | 102 ++++++++++++++++-- src/pages/live/style.module.scss | 3 + .../news/components/button-push2video.tsx | 18 ++-- src/pages/news/index.tsx | 2 +- .../video/components/button-push2room.tsx | 31 ++++++ src/pages/{create => video}/index.tsx | 38 ++++--- src/routes/routes.tsx | 2 +- src/service/api/article.ts | 15 +-- src/service/api/live.ts | 20 ++++ src/service/api/video.ts | 15 ++- src/service/request.ts | 14 ++- src/types/api.d.ts | 12 +++ src/util/strings.ts | 11 ++ 16 files changed, 243 insertions(+), 70 deletions(-) create mode 100644 src/pages/live/style.module.scss create mode 100644 src/pages/video/components/button-push2room.tsx rename src/pages/{create => video}/index.tsx (81%) create mode 100644 src/service/api/live.ts diff --git a/src/components/message.ts b/src/components/message.ts index 00d304f..80b258c 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -1,4 +1,5 @@ import {message} from "antd"; +import {BizError} from "@/service/types.ts"; export function showToast(content: string, type?: 'success' | 'info' | 'warning' | 'error') { @@ -8,6 +9,10 @@ export function showToast(content: string, type?: 'success' | 'info' | 'warning' className: 'aui-toast' }).then(); } +export function showErrorToast(e:Error|BizError) { + showToast(String(((e instanceof BizError)?e.data:'') || e.message),'error') +} + export function showLoading(content = 'Loading...') { const key = 'globalLoading_' + (new Date().getTime()); diff --git a/src/components/video/video-list-item.tsx b/src/components/video/video-list-item.tsx index 3a4a9fa..cb13bbd 100644 --- a/src/components/video/video-list-item.tsx +++ b/src/components/video/video-list-item.tsx @@ -8,7 +8,7 @@ import {IconEdit, IconPlay} from "@/components/icons"; import {Popconfirm} from "antd"; type Props = { - video: VideoInfo, + video: VideoInfo | LiveVideoInfo, editable?: boolean; sortable?: boolean; index?: number; @@ -25,7 +25,6 @@ export const VideoListItem = ( { index, id, video, onPlay, onRemove, checked, onCheckedChange, onEdit, active, editable, - }: Props) => { const { attributes, listeners, @@ -42,14 +41,13 @@ export const VideoListItem = ( className={'video-item flex items-center gap-3 mb-5'} ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}> {index && index > 0 &&
-
{id}
+
{index}
}
-
{video.title}
+
{video.title || video.video_title}
- {video.title}/ + {video.video_title}/
{editable && diff --git a/src/pages/library/components/search-form.tsx b/src/pages/library/components/search-form.tsx index c3ddf40..5835daf 100644 --- a/src/pages/library/components/search-form.tsx +++ b/src/pages/library/components/search-form.tsx @@ -17,9 +17,9 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) { timeRange: string; keywords: string; searching: boolean; - time: string; + time: number; }>({ - keywords: "", searching: false, timeRange: "", time: '-1' + keywords: "", searching: false, timeRange: "", time: 0 }) const onFinish = (values: any) => { setState({searching: true}) @@ -52,12 +52,11 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) { {/**/} {/* */} {/**/} - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/**/} + + + + + diff --git a/src/pages/live/index.tsx b/src/pages/live/index.tsx index 9b0504e..a67c09d 100644 --- a/src/pages/live/index.tsx +++ b/src/pages/live/index.tsx @@ -1,15 +1,99 @@ -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import {Button, message, Modal} from "antd"; import {SortableContext, arrayMove} from '@dnd-kit/sortable'; import {DndContext} from "@dnd-kit/core"; import {VideoListItem} from "@/components/video/video-list-item.tsx"; +import {getList} from "@/service/api/live.ts"; + +import styles from './style.module.scss' export default function LiveIndex() { - const [videoData, setVideoData] = useState() + const [videoData, setVideoData] = useState([]) const [modal, contextHolder] = Modal.useModal() const [checkedIdArray, setCheckedIdArray] = useState([]) - const [editable,setEditable] = useState(false) + const [editable, setEditable] = useState(false) + + const [state, setState] = useState({ + activeId: -1, + }) + + useEffect(() => { + getList().then(res => { + setVideoData([ + { + id: 1, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 2, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 3, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 4, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 5, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 6, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + }, + { + id: 7, + video_id: 1, + video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?', + cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', + video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4', + video_duration: 100, + status: 1, + order_no: '1' + } + ]) + }) + }, []) const processDeleteVideo = async (_idArray: number[]) => { message.info('删除成功!!!' + _idArray.join('')); } @@ -42,19 +126,19 @@ export default function LiveIndex() { -
+
- {editable ?<> + {editable ? <>
- +
批量删除
- :
- + :
+
}
@@ -86,7 +170,7 @@ export default function LiveIndex() { video={v} index={index + 1} id={v.id} - active={index == 0} + active={state.activeId == v.id} key={index} onCheckedChange={(checked) => { setCheckedIdArray(idArray => { diff --git a/src/pages/live/style.module.scss b/src/pages/live/style.module.scss new file mode 100644 index 0000000..5ba3fb7 --- /dev/null +++ b/src/pages/live/style.module.scss @@ -0,0 +1,3 @@ +.videoListContainer{ + +} \ No newline at end of file diff --git a/src/pages/news/components/button-push2video.tsx b/src/pages/news/components/button-push2video.tsx index c391851..42904e8 100644 --- a/src/pages/news/components/button-push2video.tsx +++ b/src/pages/news/components/button-push2video.tsx @@ -1,26 +1,26 @@ import {Button, Modal} from "antd"; import React, {useState} from "react"; -import {showToast} from "@/components/message.ts"; -import {push2article} from "@/service/api/news.ts"; +import {showErrorToast, showToast} from "@/components/message.ts"; +import {push2video} from "@/service/api/article.ts"; -export default function ButtonPush2Video(props: { ids: Id[]}){ - const [loading,setLoading] = useState(false) - const handlePush = ()=>{ +export default function ButtonPush2Video(props: { ids: Id[] }) { + const [loading, setLoading] = useState(false) + const handlePush = () => { setLoading(true) - push2article(props.ids).then(()=>{ + push2video(props.ids).then(() => { showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success') - }).finally(()=>{ + }).catch(showErrorToast).finally(() => { setLoading(false) }) } - const onPushClick = ()=>{ + const onPushClick = () => { if (props.ids.length === 0) { showToast('请选择要开播的新闻', 'warning') return } Modal.confirm({ - title:'操作提示', + title: '操作提示', content: '是否确定一键开播选中新闻?', onOk: handlePush }) diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index 8a8720c..19a2d2c 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -95,7 +95,7 @@ export default function NewsIndex() {
{ handleViewNewsDetail(item.id) - }}>{item.id}{item.title}
+ }}>{item.title}
{item.internal_article_id > 0 &&
已加入编辑界面
}
diff --git a/src/pages/video/components/button-push2room.tsx b/src/pages/video/components/button-push2room.tsx new file mode 100644 index 0000000..ff069fe --- /dev/null +++ b/src/pages/video/components/button-push2room.tsx @@ -0,0 +1,31 @@ +import {Button, Modal} from "antd"; +import React, {useState} from "react"; +import {showErrorToast, showToast} from "@/components/message.ts"; +import {push2room} from "@/service/api/video.ts"; + + +export default function ButtonPush2Room(props: { ids: Id[]}){ + const [loading,setLoading] = useState(false) + const handlePush = ()=>{ + setLoading(true) + push2room(props.ids).then(()=>{ + showToast('一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!', 'success') + }).catch(showErrorToast).finally(()=>{ + setLoading(false) + }) + } + const onPushClick = ()=>{ + if (props.ids.length === 0) { + showToast('请选择要推流的新闻', 'warning') + return + } + Modal.confirm({ + title:'操作提示', + content: '是否确定一键推流选中新闻视频??', + onOk: handlePush + }) + } + return ( + + ) +} \ No newline at end of file diff --git a/src/pages/create/index.tsx b/src/pages/video/index.tsx similarity index 81% rename from src/pages/create/index.tsx rename to src/pages/video/index.tsx index bb2f43b..985e2f7 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/video/index.tsx @@ -1,7 +1,5 @@ -import {Button, message, Modal} from "antd"; -import React, {useEffect, useRef, useState} from "react"; - -import {ArticleGroupList, MockVideoDataList} from "@/_local/mock-data"; +import {message, Modal} from "antd"; +import React, {useEffect, useMemo, useRef, useState} from "react"; import {DndContext} from "@dnd-kit/core"; import {arrayMove, SortableContext} from "@dnd-kit/sortable"; import {VideoListItem} from "@/components/video/video-list-item.tsx"; @@ -10,19 +8,18 @@ import {useSetState} from "ahooks"; import {CheckCircleFilled} from "@ant-design/icons"; import {clsx} from "clsx"; import {getList} from "@/service/api/video.ts"; +import {formatDuration} from "@/util/strings.ts"; +import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx"; -export default function CreateIndex() { - const [editNews, setEditNews] = useSetState<{ - title?: string; - groups?: ArticleContentGroup[]; - }>({}) +export default function VideoIndex() { + const [editId, setEditId] = useState(-1) const [videoData, setVideoData] = useState([]) useEffect(() => { - getList({}).then((ret) => { - setVideoData(ret.list) + getList().then((ret) => { + setVideoData(ret.list || []) }) }, []) @@ -63,13 +60,19 @@ export default function CreateIndex() { }) } + const totalDuration = useMemo(() => { + if(!videoData || videoData.length == 0) return 0; + // 计算总时长 + return videoData.reduce((sum, v) => sum + v.duration, 0); + }, [videoData]) + return (
{contextHolder}
- 视频时长: 00:00:29 + 视频时长: {formatDuration(totalDuration)}
批量删除 @@ -99,6 +102,7 @@ export default function CreateIndex() { } }}> + {videoData.map((v, index) => ( playVideo(v)} onEdit={() => { - setEditNews({title: v.title, groups: [...ArticleGroupList]}) + setEditId(v.article_id) }} editable />))}
- +
-
+
预览视频
-
+
- + setEditId(-1)}/>
) } \ No newline at end of file diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index d2a2dc2..9ae6fdd 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -1,7 +1,7 @@ import {RouteObject} from "react-router-dom"; import ErrorBoundary from "@/routes/error.tsx"; import UserAuth from "@/pages/user"; -import CreateIndex from "@/pages/create"; +import CreateIndex from "../pages/video"; import LibraryIndex from "@/pages/library"; import LiveIndex from "@/pages/live"; import NewsIndex from "@/pages/news"; diff --git a/src/service/api/article.ts b/src/service/api/article.ts index 7fb948c..cf3b9ef 100644 --- a/src/service/api/article.ts +++ b/src/service/api/article.ts @@ -22,12 +22,13 @@ export function getById(id: Id) { } export function save(title: string, content_group: BlockContent[][], id: number) { - return post<{ content: string }>({ - url: '/spider/article', - data: { - title, - content_group, - id - } + return post<{ content: string }>(id && id > 0 ? '/article/modify' : '/article/create/new', { + title, + content_group, + id }) +} + +export function push2video(article_ids: Id[]) { + return post('/article/push2video', {article_ids}) } \ No newline at end of file diff --git a/src/service/api/live.ts b/src/service/api/live.ts new file mode 100644 index 0000000..36b58d2 --- /dev/null +++ b/src/service/api/live.ts @@ -0,0 +1,20 @@ +import {post} from "@/service/request.ts"; + +export function playState() { + return post<{ + id: number; + start_time?: number; + }>({url: '/room/playing'}) +} + +export function getList() { + return post>('/room/list') +} + +export function modifyOrder(ids: Id[]) { + return post('/video/order', {ids}) +} + +export function deleteById(ids: Id[]) { + return post('/video/remove', {ids}) +} \ No newline at end of file diff --git a/src/service/api/video.ts b/src/service/api/video.ts index f8273a7..c9100c1 100644 --- a/src/service/api/video.ts +++ b/src/service/api/video.ts @@ -1,10 +1,7 @@ import {post} from "@/service/request.ts"; -export function getList(data: { - title?: string, - time_flag?: number; -}) { - return post>({url: '/video/list', data}) +export function getList() { + return post>('/video/list') } /** @@ -31,9 +28,11 @@ export function getById(id: Id) { export function deleteById(id: Id) { return post({url: '/video/detail/' + id}) } + export function modifyOrder(ids: Id[]) { - return post({url: ' /video/modifyorder',data:{ids}}) + return post('/video/modifyorder', {ids}) } -export function push2room(ids: Id[]) { - return post({url: ' /video/push2room',data:{ids}}) + +export function push2room(video_ids: Id[]) { + return post('/video/push2room', {video_ids}) } \ No newline at end of file diff --git a/src/service/request.ts b/src/service/request.ts index 676248f..77bba4a 100644 --- a/src/service/request.ts +++ b/src/service/request.ts @@ -2,6 +2,7 @@ import axios from 'axios'; import {stringify} from 'qs' import {BizError} from './types'; import {getAuthToken} from "@/hooks/useAuth.ts"; +import {showToast} from "@/components/message.ts"; const JSON_FORMAT: string = 'application/json'; const REQUEST_TIMEOUT = 300000; // 超时时长5min @@ -23,6 +24,7 @@ Axios.interceptors.request.use(config => { } return config }, err => { + console.log('请求拦截器报错',err) return Promise.reject(err) }) @@ -46,11 +48,11 @@ export function request(options: RequestOption) { return; } // const - const {code, message, data, request_id} = res.data + const {code, msg, data, trace_id} = res.data if (code == 0) { resolve(data as unknown as T) } else { - reject(new BizError(message, code, request_id, data as unknown as AllType)) + reject(new BizError(msg, code, trace_id, data as unknown as AllType)) } }).catch(e => { reject(new BizError(e.message, 500)) @@ -59,9 +61,13 @@ export function request(options: RequestOption) { } -export function post(params: RequestOption) { +export function post(params: RequestOption | string, _data?: AllType) { + const options = typeof params === 'string' ? {url: params} : params; + if (_data) { + options.data = _data + } return request({ - ...params, + ...options, method: 'post' }) } diff --git a/src/types/api.d.ts b/src/types/api.d.ts index ce4ca4b..1f7968b 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -89,3 +89,15 @@ declare interface VideoInfo { article_id: number; status: number; } +// room live +declare interface LiveVideoInfo { + id: number; + video_id: number; + video_title: string; + cover_url: string; + video_duration: number; + video_oss_url: string; + status: number; + order_no: string; +} + diff --git a/src/util/strings.ts b/src/util/strings.ts index 508d563..69cc62a 100644 --- a/src/util/strings.ts +++ b/src/util/strings.ts @@ -1,5 +1,6 @@ import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime" +import {padStart} from "lodash"; dayjs.extend(relativeTime) @@ -79,4 +80,14 @@ export function calcContentLengthLikeWord(str:string) { } catch (e) { return str.length } +} + +// 将时长转换成 时:分:秒 +export function formatDuration(duration: number) { + const hour = Math.floor(duration / 3600); + const minute = Math.floor((duration - hour * 3600) / 60); + const second = duration - hour * 3600 - minute * 60; + // 需要补0 + return padStart(hour.toString(), 2, '0') + ':' + padStart(minute.toString(), 2, '0') + ':' + padStart(second.toString(), 2, '0') + // return `${hour}:${minute}:${second}` } \ No newline at end of file