Compare commits
5 Commits
a3a2e09000
...
caa99cea3e
Author | SHA1 | Date | |
---|---|---|---|
caa99cea3e | |||
a84fc4cf75 | |||
f4117e0f67 | |||
3a1d19fbfd | |||
7bcdc03b53 |
@ -4,6 +4,8 @@ import {useEffect, useState} from "react";
|
||||
import {useSetState} from "ahooks";
|
||||
import * as article from "@/service/api/article.ts";
|
||||
import {regenerate} from "@/service/api/video.ts";
|
||||
import {push2video} from "@/service/api/article.ts";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
|
||||
type Props = {
|
||||
id?: number;
|
||||
@ -67,7 +69,8 @@ export default function ArticleEditModal(props: Props) {
|
||||
const [title, setTitle] = useState('')
|
||||
|
||||
const [state, setState] = useSetState({
|
||||
...DEFAULT_STATE
|
||||
...DEFAULT_STATE,
|
||||
generating:false
|
||||
})
|
||||
// 保存数据
|
||||
const handleSave = () => {
|
||||
@ -90,6 +93,19 @@ export default function ArticleEditModal(props: Props) {
|
||||
setState({loading: false})
|
||||
});
|
||||
}
|
||||
const handlePush2Video = () =>{
|
||||
if(!props.id || state.generating) return;
|
||||
setState({generating:true})
|
||||
push2video([props.id]).then(() => {
|
||||
showToast('推流成功', 'success')
|
||||
// navigate('/create?state=push-success',{
|
||||
// state: 'push-success'
|
||||
// })
|
||||
// props.onSuccess?.()
|
||||
}).catch(showErrorToast).finally(()=>{
|
||||
setState({generating:false})
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
setState({...DEFAULT_STATE})
|
||||
if (typeof (props.id) != 'undefined') {
|
||||
@ -142,7 +158,7 @@ export default function ArticleEditModal(props: Props) {
|
||||
</div>
|
||||
<div className="modal-control-footer flex justify-end">
|
||||
<div className="text-lg flex gap-10 ">
|
||||
{props.type == 'news' ? <button className="text-gray-400 hover:text-gray-800">生成视频</button> : null}
|
||||
{props.type == 'news' && props.id > 0 ? <button className="text-gray-400 hover:text-gray-800" onClick={handlePush2Video}>{state.generating?'推送中...':'生成视频'}</button> : null}
|
||||
<button className="text-gray-400 hover:text-gray-800" onClick={() => props.onClose?.()}>取消</button>
|
||||
<button onClick={handleSave} className="text-gray-800 hover:text-blue-500">{props.type == 'news' ? '确定' : '重新生成'}</button>
|
||||
</div>
|
||||
|
@ -124,7 +124,7 @@ export const IconAdd = ({style, className}: IconProps) => (
|
||||
export const IconLocked = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="0.81em" height="1em" viewBox="0 0 18 22" version="1.1">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M5.625 5.5C5.625 4.62479 5.98058 3.78542 6.61351 3.16655C7.24645 2.54768 8.10489 2.2 9 2.2C9.89511 2.2 10.7536 2.54768 11.3865 3.16655C12.0194 3.78542 12.375 4.62479 12.375 5.5V7.7H5.625V5.5ZM3.375 7.7V5.5C3.375 4.04131 3.96763 2.64236 5.02252 1.61091C6.07742 0.579463 7.50816 0 9 0C10.4918 0 11.9226 0.579463 12.9775 1.61091C14.0324 2.64236 14.625 4.04131 14.625 5.5V7.7H16.875C17.1734 7.7 17.4595 7.81589 17.6705 8.02218C17.8815 8.22847 18 8.50826 18 8.8V18.7C18 19.5752 17.6444 20.4146 17.0115 21.0335C16.3786 21.6523 15.5201 22 14.625 22H3.375C2.47989 22 1.62145 21.6523 0.988515 21.0335C0.355579 20.4146 0 19.5752 0 18.7V8.8C0 8.50826 0.118527 8.22847 0.329505 8.02218C0.540484 7.81589 0.826631 7.7 1.125 7.7H3.375ZM10.125 14.85C10.125 14.4124 10.3028 13.9927 10.6193 13.6833C10.9357 13.3738 11.3649 13.2 11.8125 13.2H11.8238C12.2713 13.2 12.7005 13.3738 13.017 13.6833C13.3335 13.9927 13.5113 14.4124 13.5113 14.85V14.861C13.5113 15.2986 13.3335 15.7183 13.017 16.0277C12.7005 16.3372 12.2713 16.511 11.8238 16.511H11.8125C11.3649 16.511 10.9357 16.3372 10.6193 16.0277C10.3028 15.7183 10.125 15.2986 10.125 14.861V14.85Z"
|
||||
fill="currentColor"/>
|
||||
</svg>
|
||||
@ -133,7 +133,7 @@ export const IconLocked = ({style, className}: IconProps) => (
|
||||
export const IconUnlock = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 24 24" version="1.1">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M11.418 7.68673V5.50001C11.418 4.04132 10.8166 2.64237 9.7459 1.61092C8.67525 0.579463 7.22314 0 5.70902 0C4.19489 0 2.74278 0.579463 1.67213 1.61092C0.601484 2.64237 0 4.04132 0 5.50001V7.68676H2.28361V5.50001C2.28361 4.62479 2.6445 3.78542 3.28689 3.16655C3.92927 2.54768 4.80054 2.2 5.70902 2.2C6.61749 2.2 7.48876 2.54768 8.13115 3.16655C8.77354 3.78542 9.13443 4.62479 9.13443 5.50001V7.68676H9.15656V7.69998H6.87295C6.57012 7.69998 6.2797 7.81587 6.06557 8.02216C5.85144 8.22845 5.73115 8.50824 5.73115 8.79998V18.7C5.73115 19.5752 6.09204 20.4146 6.73443 21.0335C7.37681 21.6523 8.24808 22 9.15656 22H20.5746C21.4831 22 22.3543 21.6523 22.9967 21.0335C23.6391 20.4146 24 19.5752 24 18.7V8.79998C24 8.50824 23.8797 8.22845 23.6656 8.02216C23.4514 7.81587 23.161 7.69998 22.8582 7.69998H20.5746V7.68673H18.291V7.69998H11.4402V7.68673H11.418ZM16.0074 14.85C16.0074 14.4124 16.1878 13.9927 16.509 13.6833C16.8302 13.3738 17.2658 13.2 17.7201 13.2H17.7315C18.1857 13.2 18.6214 13.3738 18.9426 13.6833C19.2638 13.9927 19.4442 14.4124 19.4442 14.85V14.861C19.4442 15.2986 19.2638 15.7183 18.9426 16.0277C18.6214 16.3372 18.1857 16.511 17.7315 16.511H17.7201C17.2658 16.511 16.8302 16.3372 16.509 16.0277C16.1878 15.7183 16.0074 15.2986 16.0074 14.861V14.85Z"
|
||||
fill="currentColor"/>
|
||||
|
||||
|
@ -25,6 +25,7 @@ type Props = {
|
||||
}
|
||||
export type PlayerInstance = {
|
||||
play: (url: string, currentTime: number) => void;
|
||||
pause: () => void;
|
||||
getState: () => State;
|
||||
}
|
||||
export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
|
||||
@ -96,6 +97,10 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
|
||||
}, [])
|
||||
React.useImperativeHandle(ref, () => {
|
||||
return {
|
||||
pause(){
|
||||
if (!tcPlayer) return;
|
||||
tcPlayer.pause()
|
||||
},
|
||||
play: (url, currentTime = 0) => {
|
||||
console.log('play', url, currentTime)
|
||||
if (!tcPlayer) return;
|
||||
|
@ -6,7 +6,7 @@ import {Checkbox, Popconfirm} from "antd";
|
||||
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled, LoadingOutlined} from "@ant-design/icons";
|
||||
|
||||
import ImageCover from '@/assets/images/cover.png'
|
||||
import {IconEdit, IconPlay, IconPlaying} from "@/components/icons";
|
||||
import {IconDelete, IconEdit, IconPlay, IconPlaying} from "@/components/icons";
|
||||
import {VideoStatus} from "@/service/api/video.ts";
|
||||
import {formatTime} from "@/util/strings.ts";
|
||||
|
||||
@ -52,8 +52,12 @@ export const VideoListItem = (
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className={`list-row ${generating ? 'disabled' : ''} ${active?'playing':''}`}>
|
||||
<div className="col number">{index}</div>
|
||||
<div className="col cover">
|
||||
<div
|
||||
className="col number"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
>{index}</div>
|
||||
<div className="col cover cursor-pointer">
|
||||
<div className="relative">
|
||||
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover}/>
|
||||
{generating &&
|
||||
@ -70,17 +74,25 @@ export const VideoListItem = (
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col title">
|
||||
<div
|
||||
className="col title"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
>
|
||||
<div className="line-clamp-2">
|
||||
{video.title || video.video_title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col generated-time">{video.publish_time ? formatTime(video.publish_time) : ''}</div>
|
||||
<div
|
||||
className="col generated-time"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
>{video.publish_time ? formatTime(video.publish_time) : ''}</div>
|
||||
<div className="col operation">
|
||||
{sortable && !generating && (!active ?
|
||||
<button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
|
||||
<MenuOutlined/>
|
||||
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
|
||||
{/*{sortable && !generating && (!active ?*/}
|
||||
{/* <button className="hover:text-blue-500 cursor-move">*/}
|
||||
{/* <MenuOutlined/>*/}
|
||||
{/* </button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}*/}
|
||||
|
||||
{editable && !generating && <>
|
||||
{onEdit &&
|
||||
@ -91,13 +103,6 @@ export const VideoListItem = (
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
<Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}} />
|
||||
|
||||
{onRemove && <Popconfirm
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此视频?</span></div>}
|
||||
@ -105,8 +110,15 @@ export const VideoListItem = (
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<button className="hover:text-blue-500"><MinusCircleFilled/></button>
|
||||
<button className="hover:text-blue-500"><IconDelete/></button>
|
||||
</Popconfirm>}
|
||||
<Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}} />
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,6 +20,7 @@ const cache: { flvPlayer?: FlvJs.Player, timerPlayNext?: any, timerLoadState?: a
|
||||
export default function LiveIndex() {
|
||||
|
||||
const player = useRef<PlayerInstance | null>(null)
|
||||
|
||||
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
||||
const [modal, contextHolder] = Modal.useModal()
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||
@ -31,6 +32,7 @@ export default function LiveIndex() {
|
||||
muted: true,
|
||||
showToTop: false,
|
||||
checkedAll: false,
|
||||
originSort:''
|
||||
})
|
||||
const activeIndex = useRef(state.activeIndex)
|
||||
useEffect(() => {
|
||||
@ -111,6 +113,9 @@ export default function LiveIndex() {
|
||||
getList().then(res => {
|
||||
// console.log('origin list', res.list.map(s => s.id))
|
||||
setVideoData(() => (res.list || []))
|
||||
setState({
|
||||
originSort: res.list?res.list.map(s => s.id).join(','):''
|
||||
})
|
||||
setCheckedIdArray([])
|
||||
});
|
||||
}
|
||||
@ -132,20 +137,26 @@ export default function LiveIndex() {
|
||||
setEditable(true)
|
||||
return;
|
||||
}
|
||||
const newSort = videoData.map(s => s.id).join(',')
|
||||
if(newSort == state.originSort){
|
||||
setEditable(false)
|
||||
return;
|
||||
}
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否采纳移动视频位置操作?',
|
||||
centered: true,
|
||||
onOk: () => {
|
||||
//showToast('编辑成功!!!', 'info');
|
||||
modifyOrder(videoData.map(s => s.id)).then(() => {
|
||||
showToast('已完成直播队列的修改!','success')
|
||||
setEditable(false)
|
||||
loadList()
|
||||
}).catch(() => {
|
||||
showToast('调整视频顺序失败,请重试!')
|
||||
showToast('调整视频顺序失败,请重试!','warning')
|
||||
})
|
||||
},
|
||||
onCancel: () => {
|
||||
showToast('退出并清除移动视频位置操作!', 'info');
|
||||
showToast('退出并恢复之前的直播队列!', 'info');
|
||||
loadList()
|
||||
setEditable(false)
|
||||
}
|
||||
|
@ -1,19 +1,17 @@
|
||||
import {App} from "antd";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {useState} from "react";
|
||||
import {push2article} from "@/service/api/news.ts";
|
||||
import {IconDelete} from "@/components/icons";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {deleteByIds} from "@/service/api/article.ts";
|
||||
|
||||
export default function ButtonDeleteBatch(props: { ids: Id[];onSuccess?: () => void; }) {
|
||||
const {modal} = App.useApp();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate();
|
||||
const handlePush = () => {
|
||||
setLoading(true)
|
||||
push2article(props.ids).then(() => {
|
||||
deleteByIds(props.ids).then(() => {
|
||||
props.onSuccess?.();
|
||||
showToast('删除成功', 'success')
|
||||
navigate('/edit')
|
||||
}).catch(() => {
|
||||
showToast('删除失败', 'error')
|
||||
}).finally(() => {
|
||||
@ -27,8 +25,8 @@ export default function ButtonDeleteBatch(props: { ids: Id[];onSuccess?: () => v
|
||||
return
|
||||
}
|
||||
modal.confirm({
|
||||
title: '操作提示',
|
||||
content: '是否确定删除选中的新闻?',
|
||||
title: `你确定要删除选择的 ${props.ids.length} 条新闻吗?`,
|
||||
content: '删除后需从新闻素材中重新选择',
|
||||
onOk: handlePush,
|
||||
centered: true
|
||||
})
|
||||
|
@ -1,32 +1,37 @@
|
||||
import { Modal} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
import {push2video} from "@/service/api/article.ts";
|
||||
import {IconArrowRight} from "@/components/icons";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
|
||||
export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const handlePush = () => {
|
||||
setLoading(true)
|
||||
push2video(props.ids).then(() => {
|
||||
showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success')
|
||||
props.onSuccess?.()
|
||||
showToast('推流成功', 'success')
|
||||
navigate('/create?state=push-success',{
|
||||
state: 'push-success'
|
||||
})
|
||||
// props.onSuccess?.()
|
||||
}).catch(showErrorToast).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const onPushClick = () => {
|
||||
if(loading) return;
|
||||
if (loading) return;
|
||||
if (props.ids.length === 0) {
|
||||
showToast('请选择要开播的新闻', 'warning')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '操作提示',
|
||||
content: '是否确定一键开播选中新闻?',
|
||||
onOk: handlePush
|
||||
})
|
||||
// Modal.confirm({
|
||||
// title: '操作提示',
|
||||
// content: '是否确定一键开播选中新闻?',
|
||||
// onOk: handlePush
|
||||
// })
|
||||
handlePush();
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
@ -35,7 +40,7 @@ export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => v
|
||||
className='bg-[#4096ff] hover:bg-blue-600 text-white'
|
||||
onClick={onPushClick}
|
||||
>
|
||||
<span className={'text'}>生成视频</span>
|
||||
<span className={'text'}>{loading?'推送中...':'生成视频'}</span>
|
||||
<IconArrowRight className={'text-white'}/>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -62,7 +62,7 @@
|
||||
}
|
||||
}
|
||||
.source{
|
||||
width: 150px;
|
||||
width: 180px;
|
||||
}
|
||||
.count-picture,.count-words{
|
||||
width: 120px;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {Checkbox, Space} from "antd";
|
||||
import {Checkbox, Popconfirm, Space} from "antd";
|
||||
|
||||
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 {deleteByIds, 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";
|
||||
|
||||
@ -14,6 +14,7 @@ import {IconDelete, IconEdit} from "@/components/icons";
|
||||
import {clsx} from "clsx";
|
||||
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
||||
import ButtonDeleteBatch from "@/pages/news/components/button-delete-batch.tsx";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
|
||||
|
||||
export default function NewEdit() {
|
||||
@ -58,7 +59,12 @@ export default function NewEdit() {
|
||||
}
|
||||
}
|
||||
const scrollerRef = useRef<InfiniteScrollerRef|null>(null)
|
||||
|
||||
const handleDelete = (id)=>{
|
||||
deleteByIds([id]).then(()=>{
|
||||
refresh()
|
||||
showToast('删除成功','success')
|
||||
}).catch(showErrorToast)
|
||||
}
|
||||
|
||||
return (<div className="container pb-5 news-edit">
|
||||
<div className="search-panel-container my-5">
|
||||
@ -101,7 +107,7 @@ export default function NewEdit() {
|
||||
{data?.list?.map((item, i) => {
|
||||
const checked = selectedRowKeys.includes(item.id)
|
||||
return <div key={i} className={clsx("row flex", {checked})}>
|
||||
<div className="col title">
|
||||
<div className="col title cursor-pointer" onClick={() => setEditId(item.id)}>
|
||||
<div>
|
||||
<div className="text-base">{item.title}</div>
|
||||
<div
|
||||
@ -109,21 +115,25 @@ export default function NewEdit() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col source">
|
||||
<div className="text-sm">{item.media_name}</div>
|
||||
<div className="text-sm line-clamp-1">{item.media_name}-{item.column_name}</div>
|
||||
</div>
|
||||
<div className="col count-picture">
|
||||
<div className="text-sm">{item.picture_count||'-'}</div>
|
||||
<div className="text-sm">{item.img_num}</div>
|
||||
</div>
|
||||
<div className="col count-words">
|
||||
<div className="text-sm">{item.words_count||'-'}</div>
|
||||
<div className="text-sm">{item.content_word_count}</div>
|
||||
</div>
|
||||
<div className="col time">
|
||||
<div
|
||||
className="text-sm">{formatTime(item.publish_time, 'YYYY-MM-DD HH:mm')}</div>
|
||||
</div>
|
||||
<div className="col operations">
|
||||
<span className="icon-btn" onClick={() => setEditId(item.id)}><IconEdit/></span>
|
||||
<span className="icon-btn"><IconDelete/></span>
|
||||
{/*<span className="icon-btn"><IconEdit/></span>*/}
|
||||
<Popconfirm title={'确认删除此新闻吗?'} description={'删除后需从新闻素材中重新选择'} onConfirm={()=>{
|
||||
handleDelete(item.id)
|
||||
}}>
|
||||
<span className="icon-btn"><IconDelete/></span>
|
||||
</Popconfirm>
|
||||
<Checkbox checked={checked}
|
||||
onChange={e => handleItemChecked(e.target.checked, item)}/>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import React, {useMemo, useRef, useState} from "react";
|
||||
import {Checkbox, Divider, Empty, Modal, Space} from "antd";
|
||||
import {useRequest} from "ahooks";
|
||||
import {CloseOutlined} from "@ant-design/icons"
|
||||
@ -55,10 +55,16 @@ export default function NewsIndex() {
|
||||
})
|
||||
}
|
||||
|
||||
const currentEnabledList = useMemo(()=>{
|
||||
if(data?.list && data?.list?.length > 0){
|
||||
return data.list.filter(s=>s.internal_article_id == 0)
|
||||
}
|
||||
return [];
|
||||
},[data?.list])
|
||||
const handleCheckAll = (checked: boolean) => {
|
||||
setState({checkAll: checked})
|
||||
if (checked) {
|
||||
setCheckedId(data?.list?.map(item => item.id) || [])
|
||||
setCheckedId(currentEnabledList.map(item => item.id) || [])
|
||||
} else {
|
||||
setCheckedId([])
|
||||
}
|
||||
@ -76,7 +82,6 @@ export default function NewsIndex() {
|
||||
{activeNews && <Modal
|
||||
rootClassName={'news-detail-modal'}
|
||||
closeIcon={null} open={true} width={1000}
|
||||
maskClosable={false}
|
||||
footer={null} onCancel={() => setActiveNews(undefined)}
|
||||
>
|
||||
<div className="news-detail pl-16 pr-1 flex pb-5">
|
||||
@ -114,7 +119,7 @@ export default function NewsIndex() {
|
||||
<span className={'inline-block cursor-pointer mr-2'} onClick={() => {
|
||||
handleCheckAll(!state.checkAll)
|
||||
}}>全选</span>
|
||||
<Checkbox checked={state.checkAll && checkedId.length == data?.list.length} onChange={e => {
|
||||
<Checkbox checked={state.checkAll && checkedId.length == currentEnabledList.length} onChange={e => {
|
||||
handleCheckAll(e.target.checked)
|
||||
}}></Checkbox>
|
||||
</div>
|
||||
@ -158,8 +163,8 @@ export default function NewsIndex() {
|
||||
<div className="line-clamp-1">来源: <span>{item.data_source_name}</span></div>
|
||||
<div className="extras flex items-center justify-between gap-3">
|
||||
<div><span>{formatTime(item.publish_time,'min')}</span></div>
|
||||
<div><span>图片数: {item.picture_count || '-'}</span></div>
|
||||
<div><span>字数: {item.words_count || '-'}</span></div>
|
||||
<div><span>图片数: {item.img_num}</span></div>
|
||||
<div><span>字数: {item.content_word_count}</span></div>
|
||||
<div
|
||||
className={`checkbox mt-1`}>
|
||||
{item.internal_article_id > 0 ?
|
||||
|
@ -15,9 +15,11 @@ import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
|
||||
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
||||
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
||||
import {IconDelete} from "@/components/icons";
|
||||
import {useLocation} from "react-router-dom";
|
||||
|
||||
export default function VideoIndex() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
const loc = useLocation()
|
||||
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
||||
const [modal, contextHolder] = Modal.useModal()
|
||||
const player = useRef<PlayerInstance | null>(null)
|
||||
@ -25,7 +27,8 @@ export default function VideoIndex() {
|
||||
const [state, setState] = useSetState({
|
||||
checkedAll: false,
|
||||
playingIndex: -1,
|
||||
showToTop: false
|
||||
showToTop: false,
|
||||
showStatePos: false
|
||||
})
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
|
||||
|
||||
@ -48,12 +51,19 @@ export default function VideoIndex() {
|
||||
|
||||
// 播放视频
|
||||
const playVideo = (video: VideoInfo, playingIndex: number) => {
|
||||
console.log(video)
|
||||
if(video.status == VideoStatus.Generating ) return;
|
||||
if (video.oss_video_url && video.status !== 1) {
|
||||
setState({playingIndex})
|
||||
player.current?.play(video.oss_video_url, 0)
|
||||
if(state.playingIndex == playingIndex){
|
||||
player.current?.pause();
|
||||
setState({playingIndex: -1})
|
||||
return;
|
||||
}
|
||||
if(video.status == VideoStatus.Generating ) return;
|
||||
setState({playingIndex})
|
||||
player.current?.play('https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-17/1186196465916190720.flv',30)
|
||||
//
|
||||
// if (video.oss_video_url && video.status !== 1) {
|
||||
// setState({playingIndex})
|
||||
// player.current?.play(video.oss_video_url, 0)
|
||||
// }
|
||||
}
|
||||
// 处理全选
|
||||
const handleAllCheckedChange = () => {
|
||||
@ -62,12 +72,13 @@ export default function VideoIndex() {
|
||||
checkedAll: !state.checkedAll
|
||||
})
|
||||
}
|
||||
const handleModifySort = () => {
|
||||
setVideoData((items) => {
|
||||
modifyOrder(items.map(s => s.id)).catch(() => {
|
||||
showToast('调整视频顺序失败,请重试!')
|
||||
}).finally(loadList)
|
||||
return items;
|
||||
const handleModifySort = (items:VideoInfo[]) => {
|
||||
|
||||
modifyOrder(items.map(s => s.id)).then(() => {
|
||||
showToast('调整视频顺序成功!','success')
|
||||
}).catch(()=>{
|
||||
loadList();
|
||||
showToast('调整视频顺序失败,请重试!','warning')
|
||||
})
|
||||
}
|
||||
//
|
||||
@ -81,6 +92,16 @@ export default function VideoIndex() {
|
||||
//return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
|
||||
}, [videoData,state.playingIndex])
|
||||
|
||||
useEffect(()=>{
|
||||
if(loc.state == 'push-success' && !state.showStatePos && videoData.length && scrollerRef.current){
|
||||
const generatingItem = document.querySelector(`.list-item-state-${VideoStatus.Generating}`)
|
||||
if(generatingItem){
|
||||
generatingItem.scrollIntoView({behavior: 'smooth'})
|
||||
setState({showStatePos: true})
|
||||
}
|
||||
}
|
||||
},[videoData,scrollerRef])
|
||||
|
||||
return (<div className="container py-10 page-live">
|
||||
{contextHolder}
|
||||
<div className="flex">
|
||||
@ -91,7 +112,7 @@ export default function VideoIndex() {
|
||||
<Player
|
||||
ref={player} url={videoData[state.playingIndex]?.oss_video_url}
|
||||
onChange={(state) => {
|
||||
if (state.end || state.end) setState({playingIndex: -1})
|
||||
if (state.end || state.error) setState({playingIndex: -1})
|
||||
}}
|
||||
className="w-[360px] h-[640px] bg-white"/>
|
||||
</div>
|
||||
@ -133,16 +154,18 @@ export default function VideoIndex() {
|
||||
setVideoData((items) => {
|
||||
oldIndex = items.findIndex(s => s.id == active.id);
|
||||
newIndex = items.findIndex(s => s.id == over.id);
|
||||
return arrayMove(items, oldIndex, newIndex);
|
||||
const newSorts = arrayMove(items, oldIndex, newIndex);
|
||||
handleModifySort(newSorts)
|
||||
return newSorts;
|
||||
});
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否要移动到指定位置',
|
||||
onOk: handleModifySort,
|
||||
onCancel: () => {
|
||||
setVideoData(originArr);
|
||||
}
|
||||
})
|
||||
// modal.confirm({
|
||||
// title: '提示',
|
||||
// content: '是否要移动到指定位置',
|
||||
// onOk: handleModifySort,
|
||||
// onCancel: () => {
|
||||
// setVideoData(originArr);
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}}>
|
||||
<SortableContext items={videoData}>
|
||||
@ -155,7 +178,7 @@ export default function VideoIndex() {
|
||||
type={'create'}
|
||||
active={state.playingIndex == index}
|
||||
checked={checkedIdArray.includes(v.id)}
|
||||
className={`list-item-${index} mt-3 mb-2`}
|
||||
className={`list-item-${index} mt-3 mb-2 list-item-state-${v.status}`}
|
||||
onCheckedChange={(checked) => {
|
||||
setCheckedIdArray(idArray => {
|
||||
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
||||
@ -164,6 +187,7 @@ export default function VideoIndex() {
|
||||
})
|
||||
}}
|
||||
onItemClick={ () => playVideo(v, index)}
|
||||
onRemove={()=>{}}
|
||||
onEdit={v.status == VideoStatus.Generating ? undefined : () => {
|
||||
setEditId(v.article_id)
|
||||
}}
|
||||
@ -180,21 +204,21 @@ export default function VideoIndex() {
|
||||
</div>
|
||||
<div className="page-action">
|
||||
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/>
|
||||
<ButtonBatch
|
||||
onProcess={deleteByIds}
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={`请选择要删除的新闻视频`}
|
||||
title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`}
|
||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
||||
confirmMessage={`删除后需从新闻素材中`}
|
||||
onSuccess={() => {
|
||||
{checkedIdArray.length > 0 && <ButtonBatch
|
||||
onProcess={deleteByIds}
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={`请选择要删除的新闻视频`}
|
||||
title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`}
|
||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
||||
confirmMessage={`删除后需从新闻素材中`}
|
||||
onSuccess={() => {
|
||||
showToast('删除成功!', 'success')
|
||||
loadList()
|
||||
}}
|
||||
>
|
||||
<span className="text">批量删除</span>
|
||||
<IconDelete />
|
||||
</ButtonBatch>
|
||||
>
|
||||
<span className="text">批量删除</span>
|
||||
<IconDelete />
|
||||
</ButtonBatch>}
|
||||
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,12 +20,12 @@ const NavigationUserContainer = () => {
|
||||
const {logout, user} = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'profile',
|
||||
label: <div onClick={() => {
|
||||
navigate('/history')
|
||||
}}>视频库</div>,
|
||||
},
|
||||
// {
|
||||
// key: 'profile',
|
||||
// label: <div onClick={() => {
|
||||
// navigate('/history')
|
||||
// }}>视频库</div>,
|
||||
// },
|
||||
{
|
||||
key: 'logout',
|
||||
label: <div onClick={() => {
|
||||
|
@ -12,9 +12,8 @@ export function getList(data: ApiArticleSearchParams) {
|
||||
* 删除 【本期不做】
|
||||
* @param id
|
||||
*/
|
||||
export function deleteById(id: Id) {
|
||||
throw new Error('Not implement')
|
||||
return post<{ article: any }>({url: '/article/delete/' + id})
|
||||
export function deleteByIds(article_ids: Id[]) {
|
||||
return post('/article/remove',{article_ids})
|
||||
}
|
||||
|
||||
export function getById(id: Id) {
|
||||
|
5
src/types/api.d.ts
vendored
5
src/types/api.d.ts
vendored
@ -60,8 +60,9 @@ interface BasicArticleInfo {
|
||||
summary: string;
|
||||
publish_time: string;
|
||||
media_name: string;
|
||||
picture_count?: number;
|
||||
words_count?: number;
|
||||
column_name?: string;
|
||||
img_num?: number;
|
||||
content_word_count?: number;
|
||||
media_id: number;
|
||||
fanwen_column_id: number;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user