update ui
This commit is contained in:
parent
72d967fbc6
commit
7ee5cac052
19
.ide/Dockerfile
Normal file
19
.ide/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM node:20
|
||||
|
||||
# 以及按需安装其他软件
|
||||
# RUN apt-get update && apt-get install -y git
|
||||
|
||||
# 安装 code-server 和 vscode 常用插件
|
||||
RUN curl -fsSL https://code-server.dev/install.sh | sh \
|
||||
&& code-server --install-extension redhat.vscode-yaml \
|
||||
&& code-server --install-extension dbaeumer.vscode-eslint \
|
||||
&& code-server --install-extension eamodio.gitlens \
|
||||
&& code-server --install-extension tencent-cloud.coding-copilot \
|
||||
&& echo done
|
||||
|
||||
# 安装 ssh 服务,用于支持 VSCode 客户端通过 Remote-SSH 访问开发环境
|
||||
RUN apt-get update && apt-get install -y wget unzip openssh-server
|
||||
|
||||
# 指定字符集支持命令行输入中文(根据需要选择字符集)
|
||||
ENV LANG C.UTF-8
|
||||
ENV LANGUAGE C.UTF-8
|
@ -152,7 +152,7 @@
|
||||
}
|
||||
|
||||
.list-row {
|
||||
@apply flex bg-white mt-2 py-2 px-4 rounded-xl gap-2 border;
|
||||
@apply flex bg-white mt-2 py-1 rounded-xl gap-2 border;
|
||||
border-width: 2px;
|
||||
&.playing{
|
||||
@apply border-primary-blue bg-[#d9eaff];
|
||||
@ -161,10 +161,10 @@
|
||||
@apply border-primary-blue bg-[#f4f7fc];
|
||||
}
|
||||
&.header-row{
|
||||
@apply text-sm;
|
||||
background: none;
|
||||
.col{
|
||||
height: 42px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@
|
||||
|
||||
.col {
|
||||
@apply flex items-center relative pl-4 text-center justify-center;
|
||||
height: 80px;
|
||||
height: 60px;
|
||||
|
||||
&:after {
|
||||
@apply absolute;
|
||||
@ -187,7 +187,7 @@
|
||||
}
|
||||
|
||||
.number {
|
||||
width: 50px;
|
||||
width: 70px;
|
||||
padding-left: 10px;
|
||||
&:after {
|
||||
display: none;
|
||||
@ -210,9 +210,9 @@
|
||||
}
|
||||
|
||||
.operation {
|
||||
@apply flex items-center ml-2 gap-4 text-lg text-gray-400 justify-between;
|
||||
width: 180px;
|
||||
padding: 0 20px 0 30px;
|
||||
@apply flex items-center ml-2 gap-4 text-lg text-gray-400 justify-center;
|
||||
width: 120px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,6 +266,45 @@
|
||||
}
|
||||
|
||||
// override antd style
|
||||
.data-list-load-spin{
|
||||
.ant-spin-container::after{
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.popconfirm-main{
|
||||
.ant-popover-inner{
|
||||
@apply bg-white px-6 py-6 rounded-xl;
|
||||
min-width: 360px;
|
||||
box-shadow: 0 0 10px rgba(25, 25, 25, 0.1);
|
||||
}
|
||||
.icon-warning{
|
||||
@apply text-red-500;
|
||||
font-size: 20px;
|
||||
transform: translateY(5px);
|
||||
margin-right: 10px;
|
||||
}
|
||||
.ant-popconfirm-message{
|
||||
.ant-popconfirm-title{
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
.ant-popconfirm-description{
|
||||
@apply mt-4 text-gray-400 text-sm;
|
||||
margin-left: -30px;
|
||||
}
|
||||
}
|
||||
.ant-popconfirm-buttons{
|
||||
@apply mt-8;
|
||||
button{
|
||||
@apply rounded-2xl py-4 px-8;
|
||||
}
|
||||
.ant-btn-default{
|
||||
@apply bg-white shadow-none text-popconfirm-btn-cancel border border-popconfirm-btn-cancel hover:border-popconfirm-btn-cancel hover:text-popconfirm-btn-cancel hover:bg-white hover:bg-popconfirm-btn-cancel/10;
|
||||
}
|
||||
.ant-btn-primary{
|
||||
@apply bg-white shadow-none text-popconfirm-bg border border-popconfirm-bg hover:text-popconfirm-bg hover:bg-white hover:bg-popconfirm-btn-primary-hover/10;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-checkbox {
|
||||
border-radius: 2px;
|
||||
|
||||
|
@ -112,6 +112,21 @@ export const IconAddCircle = ({style, className}: IconProps) => (
|
||||
fill="currentColor"/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconWarningCircle = ({style, className}: IconProps) => (
|
||||
<svg className={`svg-icon ${className || ''} icon-warning`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em" height="1em" viewBox="0 0 22 22" version="1.1">
|
||||
<g>
|
||||
<path
|
||||
d="M9.625 12.375V5.5H12.375V12.375H9.625ZM11 16.5C11.3647 16.5 11.7144 16.3551 11.9723 16.0973C12.2301 15.8394 12.375 15.4897 12.375 15.125C12.375 14.7603 12.2301 14.4106 11.9723 14.1527C11.7144 13.8949 11.3647 13.75 11 13.75C10.6353 13.75 10.2856 13.8949 10.0277 14.1527C9.76987 14.4106 9.625 14.7603 9.625 15.125C9.625 15.4897 9.76987 15.8394 10.0277 16.0973C10.2856 16.3551 10.6353 16.5 11 16.5Z"
|
||||
fill="currentColor"/>
|
||||
<path
|
||||
d="M0 11C0 8.08262 1.15893 5.28473 3.22183 3.22183C5.28473 1.15893 8.08262 0 11 0C13.9174 0 16.7153 1.15893 18.7782 3.22183C20.8411 5.28473 22 8.08262 22 11C22 13.9174 20.8411 16.7153 18.7782 18.7782C16.7153 20.8411 13.9174 22 11 22C8.08262 22 5.28473 20.8411 3.22183 18.7782C1.15893 16.7153 0 13.9174 0 11ZM11 2.75C9.91659 2.75 8.8438 2.96339 7.84286 3.37799C6.84193 3.7926 5.93245 4.40029 5.16637 5.16637C4.40029 5.93245 3.7926 6.84193 3.37799 7.84286C2.96339 8.8438 2.75 9.91659 2.75 11C2.75 12.0834 2.96339 13.1562 3.37799 14.1571C3.7926 15.1581 4.40029 16.0675 5.16637 16.8336C5.93245 17.5997 6.84193 18.2074 7.84286 18.622C8.8438 19.0366 9.91659 19.25 11 19.25C13.188 19.25 15.2865 18.3808 16.8336 16.8336C18.3808 15.2865 19.25 13.188 19.25 11C19.25 8.81196 18.3808 6.71354 16.8336 5.16637C15.2865 3.61919 13.188 2.75 11 2.75Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
)
|
||||
export const IconAdd = ({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 1024 1024" version="1.1">
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React, {CSSProperties, useCallback, useEffect, useImperativeHandle, useRef} from "react";
|
||||
import {useInViewport, useScroll} from "ahooks";
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {Spin} from "antd";
|
||||
|
||||
export type InfiniteScrollerRef = {
|
||||
scrollToPosition: (top: number) => void
|
||||
@ -27,8 +29,7 @@ const InfiniteScroller = React.forwardRef<InfiniteScrollerRef, InfiniteScrollerP
|
||||
const scrollPosition = useScroll(scrollContainerRef);
|
||||
const scrollToPosition = useCallback((top: number) => {
|
||||
if (scrollContainerRef.current) {
|
||||
console.log('xaf');
|
||||
scrollContainerRef.current.scrollTo({
|
||||
scrollContainerRef.current!.scrollTo({
|
||||
top,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
@ -59,16 +60,19 @@ const InfiniteScroller = React.forwardRef<InfiniteScrollerRef, InfiniteScrollerP
|
||||
}, [inView])
|
||||
|
||||
return (<div ref={scrollContainerRef} className={`data-list-container ${props.rootClassName}`} style={props.style}>
|
||||
<Spin wrapperClassName="data-list-load-spin" spinning={props.loading} indicator={<LoadingOutlined style={{fontSize:30}} spin />}>
|
||||
{props.loading && <div style={{minHeight:'30vh'}}></div>}
|
||||
<div className={`data-list-container-inner ${props.className}`}>{props.children}</div>
|
||||
{props?.pagination && props.pagination.total > props.pagination.limit * props.pagination.page && (props.loadingPlaceholder ||
|
||||
<div className="data-load-control-element py-10 text-center">
|
||||
<div className="loading-text">加载中...</div>
|
||||
</div>)}
|
||||
{props?.empty && props.pagination?.total == 0 && <div className="flex justify-center text-center pt-20">
|
||||
<div className=" rounded-lg px-4 py-10">
|
||||
{props.empty}
|
||||
{props?.empty && !props.loading && props.pagination?.total == 0 && <div className="flex justify-center text-center pt-20">
|
||||
<div className="rounded-lg px-4 py-10">
|
||||
{props.empty}
|
||||
</div>
|
||||
</div>}
|
||||
</Spin>
|
||||
</div>);
|
||||
}) //(props: InfiniteScrollerProps) =>{}
|
||||
export default InfiniteScroller
|
@ -93,10 +93,19 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
|
||||
})
|
||||
setTcPlayer(() => player)
|
||||
return () => {
|
||||
if (tcPlayer) {
|
||||
tcPlayer.pause()
|
||||
tcPlayer.unload()
|
||||
// if (tcPlayer) {
|
||||
// tcPlayer.pause()
|
||||
// tcPlayer.unload()
|
||||
// }else{
|
||||
// playerVideo.pause()
|
||||
// }
|
||||
console.log('destroy video')
|
||||
try{
|
||||
Array.from(document.querySelectorAll('video')).forEach(v => v.pause())
|
||||
}catch (e){
|
||||
console.log(e)
|
||||
}
|
||||
playerVideo.parentElement.removeChild(playerVideo)
|
||||
}
|
||||
}, [])
|
||||
React.useImperativeHandle(ref, () => {
|
||||
|
@ -1,12 +1,10 @@
|
||||
import {useSortable} from "@dnd-kit/sortable";
|
||||
import {useSetState} from "ahooks";
|
||||
import React, {useEffect} from "react";
|
||||
import {clsx} from "clsx";
|
||||
import {App, Checkbox, Popconfirm} from "antd";
|
||||
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled, LoadingOutlined} from "@ant-design/icons";
|
||||
import {Checkbox, Popconfirm} from "antd";
|
||||
|
||||
import ImageCover from '@/assets/images/cover.png'
|
||||
import {IconDelete, IconEdit, IconPlay, IconPlaying} from "@/components/icons";
|
||||
import {IconDelete, IconEdit, IconPlaying, IconWarningCircle} from "@/components/icons";
|
||||
import {VideoStatus} from "@/service/api/video.ts";
|
||||
import {formatTime} from "@/util/strings.ts";
|
||||
|
||||
@ -46,16 +44,7 @@ export const VideoListItem = (
|
||||
}, [checked])
|
||||
|
||||
const generating = (type == 'create' && video.status == VideoStatus.Generating )
|
||||
const {modal} = App.useApp()
|
||||
const handleDelete = () => {
|
||||
if(!onRemove) return;
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
centered: true,
|
||||
content: '是否要删除该视频',
|
||||
onOk: onRemove,
|
||||
})
|
||||
}
|
||||
|
||||
return <div
|
||||
className={`video-item ${className}`}
|
||||
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}
|
||||
@ -96,32 +85,41 @@ export const VideoListItem = (
|
||||
className="col generated-time"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
>{video.publish_time ? formatTime(video.publish_time) : ''}</div>
|
||||
>{video.ctime ? formatTime(video.ctime,'min') : '-'}</div>
|
||||
<div className="col operation">
|
||||
{/*{sortable && !generating && (!active ?*/}
|
||||
{/* <button className="hover:text-blue-500 cursor-move">*/}
|
||||
{/* <MenuOutlined/>*/}
|
||||
{/* </button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}*/}
|
||||
<div className={"flex items-center gap-4"}>
|
||||
{editable && !generating && <>
|
||||
{onEdit &&
|
||||
<button className="hover:text-blue-500" onClick={e=>{
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onEdit?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
|
||||
{editable && !generating && <>
|
||||
{onEdit &&
|
||||
<button className="hover:text-blue-500" onClick={e=>{
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onEdit?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
|
||||
{onRemove && <button className="hover:text-blue-500" onClick={handleDelete}><IconDelete/></button>}
|
||||
<Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}} />
|
||||
</>}
|
||||
{onRemove && <Popconfirm
|
||||
rootClassName={'popconfirm-main'}
|
||||
placement={'left'}
|
||||
arrow={false}
|
||||
icon={<IconWarningCircle/>}
|
||||
title={'你确定要删除吗?'}
|
||||
description={`删除后需从重新${type == 'news' ? '生成' : '推流'}`}
|
||||
onConfirm={onRemove}
|
||||
><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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,7 +33,8 @@ export default function LiveIndex() {
|
||||
showToTop: false,
|
||||
checkedAll: false,
|
||||
originSort: '',
|
||||
playProgress: 0
|
||||
playProgress: 0,
|
||||
loading:false
|
||||
})
|
||||
const activeIndex = useRef(state.activeIndex)
|
||||
useEffect(() => {
|
||||
@ -111,6 +112,7 @@ export default function LiveIndex() {
|
||||
|
||||
const loadList = () => {
|
||||
clearAllTimer();
|
||||
setState({loading: true})
|
||||
getList().then(res => {
|
||||
// console.log('origin list', res.list.map(s => s.id))
|
||||
setVideoData(() => (res.list || []))
|
||||
@ -118,6 +120,8 @@ export default function LiveIndex() {
|
||||
originSort: res.list ? res.list.map(s => s.id).join(',') : ''
|
||||
})
|
||||
setCheckedIdArray([])
|
||||
}).catch(showErrorToast).finally(()=>{
|
||||
setState({loading: false})
|
||||
});
|
||||
}
|
||||
|
||||
@ -191,14 +195,13 @@ export default function LiveIndex() {
|
||||
return checkedIdArray.filter(id => currentId.id != id)
|
||||
}, [checkedIdArray, state.activeIndex])
|
||||
|
||||
return (<div className="container py-10 page-live">
|
||||
return (<div className="container py-5 page-live">
|
||||
{contextHolder}
|
||||
<div className="h-[36px]"></div>
|
||||
<div className="flex">
|
||||
<div className="video-player-container mr-8 flex flex-col">
|
||||
<div className="text-center text-base">
|
||||
<span>视频时长: {formatDuration(currentTotalDuration)} / {formatDuration(totalDuration)}</span>
|
||||
</div>
|
||||
<div className="video-player flex justify-center flex-1 mt-5">
|
||||
<div className="video-player-container mr-16 flex flex-col">
|
||||
<div className="text-center text-base text-gray-400">直播界面</div>
|
||||
<div className="video-player flex justify-center flex-1 mt-1">
|
||||
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
|
||||
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
|
||||
<Player
|
||||
@ -210,10 +213,13 @@ export default function LiveIndex() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center text-base">
|
||||
<span>视频时长: {formatDuration(currentTotalDuration)} / {formatDuration(totalDuration)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="video-list-container video-list-sort-container flex-1">
|
||||
<div className="live-control flex justify-between mb-4">
|
||||
<div className="video-list-container video-list-sort-container flex-1 mt-1">
|
||||
<div className="live-control flex justify-between mb-1">
|
||||
<div className="text-sm">
|
||||
<span>当前{state.activeIndex == -1 ? '暂未播放' : `播放到${state.activeIndex}条`},</span>
|
||||
<span>共{videoData.length}条</span>
|
||||
@ -250,6 +256,7 @@ export default function LiveIndex() {
|
||||
<div className="live-video-list-sort-container ">
|
||||
<InfiniteScroller
|
||||
ref={scrollerRef}
|
||||
loading={state.loading}
|
||||
onScroll={top => setState({showToTop: top > 30})}
|
||||
onCallback={() => {
|
||||
}}
|
||||
@ -274,6 +281,7 @@ export default function LiveIndex() {
|
||||
id={v.id}
|
||||
key={index}
|
||||
active={state.activeIndex == index}
|
||||
playing={state.activeIndex == index}
|
||||
className={`list-item-${index} mt-3 mb-2`}
|
||||
checked={checkedIdArray.includes(v.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
|
@ -202,9 +202,10 @@ export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps)
|
||||
</div>
|
||||
<div className="tags-list-container">
|
||||
{
|
||||
tags.filter(s => s.value !== 999999).map(it => (
|
||||
<div
|
||||
className={`filter-item border flex items-center px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded`}
|
||||
tags.filter(s => s.value !== 999999).map(it => {
|
||||
const currentPinned = pinnedTag?.includes(Number(it.value));
|
||||
return (<div
|
||||
className={`filter-item border flex items-center px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${currentPinned?'bg-gray-100':''} hover:border-gray-400`}
|
||||
key={it.value}
|
||||
onClick={() => {
|
||||
const value = Number(it.value)
|
||||
@ -215,10 +216,11 @@ export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps)
|
||||
}
|
||||
}}>
|
||||
<span>{it.label}</span>
|
||||
{pinnedTag?.includes(Number(it.value)) &&
|
||||
{currentPinned &&
|
||||
<span className={'ml-2'}><IconPin/></span>}
|
||||
</div>)
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,8 +78,9 @@
|
||||
.header{
|
||||
@apply bg-primary-bg;
|
||||
.col{
|
||||
|
||||
@apply text-sm;
|
||||
height: 42px;
|
||||
|
||||
}
|
||||
.operations{
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx";
|
||||
|
||||
import styles from './components/style.module.scss'
|
||||
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
||||
import {IconDelete, IconEdit} from "@/components/icons";
|
||||
import {IconDelete, IconWarningCircle} 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";
|
||||
@ -58,11 +58,11 @@ export default function NewEdit() {
|
||||
setSelectedRowKeys([])
|
||||
}
|
||||
}
|
||||
const scrollerRef = useRef<InfiniteScrollerRef|null>(null)
|
||||
const handleDelete = (id)=>{
|
||||
deleteByIds([id]).then(()=>{
|
||||
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
||||
const handleDelete = (id) => {
|
||||
deleteByIds([id]).then(() => {
|
||||
refresh()
|
||||
showToast('删除成功','success')
|
||||
showToast('删除成功', 'success')
|
||||
}).catch(showErrorToast)
|
||||
}
|
||||
|
||||
@ -83,9 +83,10 @@ export default function NewEdit() {
|
||||
<span className={'inline-block cursor-pointer mr-2'} onClick={() => {
|
||||
handleCheckAll(!state.checkAll)
|
||||
}}>全选</span>
|
||||
<Checkbox checked={state.checkAll && selectedRowKeys.length == data?.list.length} onChange={e => {
|
||||
handleCheckAll(e.target.checked)
|
||||
}} />
|
||||
<Checkbox checked={state.checkAll && selectedRowKeys.length == data?.list.length}
|
||||
onChange={e => {
|
||||
handleCheckAll(e.target.checked)
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.newListTable}>
|
||||
@ -102,7 +103,8 @@ export default function NewEdit() {
|
||||
...prev,
|
||||
pagination: {page, limit: 10}
|
||||
}))
|
||||
}} onScroll={(top)=> setState({showToTop: top > 30})} loading={loading} pagination={data?.pagination}>
|
||||
}} onScroll={(top) => setState({showToTop: top > 30})} loading={loading}
|
||||
pagination={data?.pagination}>
|
||||
<div className="body">
|
||||
{data?.list?.map((item, i) => {
|
||||
const checked = selectedRowKeys.includes(item.id)
|
||||
@ -129,9 +131,17 @@ export default function NewEdit() {
|
||||
</div>
|
||||
<div className="col operations">
|
||||
{/*<span className="icon-btn"><IconEdit/></span>*/}
|
||||
<Popconfirm title={'确认删除此新闻吗?'} description={'删除后需从新闻素材中重新选择'} onConfirm={()=>{
|
||||
handleDelete(item.id)
|
||||
}}>
|
||||
<Popconfirm
|
||||
rootClassName={'popconfirm-main'}
|
||||
placement={'left'}
|
||||
arrow={false}
|
||||
icon={<IconWarningCircle/>}
|
||||
title={'你确定要删除吗?'}
|
||||
description={'删除后需从新闻素材中重新选择'}
|
||||
onConfirm={() => {
|
||||
handleDelete(item.id)
|
||||
}}
|
||||
>
|
||||
<span className="icon-btn"><IconDelete/></span>
|
||||
</Popconfirm>
|
||||
<Checkbox checked={checked}
|
||||
@ -144,8 +154,8 @@ export default function NewEdit() {
|
||||
</div>
|
||||
|
||||
<div className="page-action">
|
||||
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)} />
|
||||
{selectedRowKeys?.length >0 && <ButtonDeleteBatch ids={selectedRowKeys} onSuccess={refresh}/>}
|
||||
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
||||
{selectedRowKeys?.length > 0 && <ButtonDeleteBatch ids={selectedRowKeys} onSuccess={refresh}/>}
|
||||
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Checkbox, Empty} from "antd";
|
||||
import {Checkbox, Empty, Space} from "antd";
|
||||
import React, {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {DndContext} from "@dnd-kit/core";
|
||||
import {arrayMove, SortableContext} from "@dnd-kit/sortable";
|
||||
@ -16,7 +16,6 @@ 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";
|
||||
import {playState} from "@/service/api/live.ts";
|
||||
|
||||
export default function VideoIndex() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
@ -32,12 +31,14 @@ export default function VideoIndex() {
|
||||
playState: {
|
||||
current: -1,
|
||||
total: -1
|
||||
}
|
||||
},
|
||||
loading:false
|
||||
})
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
|
||||
|
||||
// 加载列表
|
||||
const loadList = (needReset = true) => {
|
||||
setState({loading: true})
|
||||
getList().then((ret) => {
|
||||
const list = ret.list || []
|
||||
setVideoData(list)
|
||||
@ -50,6 +51,9 @@ export default function VideoIndex() {
|
||||
// 每5s重新获取一次最新数据
|
||||
setTimeout(() => loadList(false), 5000)
|
||||
}
|
||||
}).catch(showErrorToast)
|
||||
.finally(()=>{
|
||||
setState({loading: false})
|
||||
})
|
||||
}
|
||||
|
||||
@ -112,12 +116,13 @@ export default function VideoIndex() {
|
||||
}).catch(showErrorToast)
|
||||
}
|
||||
|
||||
return (<div className="container py-10 page-live">
|
||||
return (<div className="container py-5 page-live">
|
||||
<div className="h-[36px]"></div>
|
||||
<div className="flex">
|
||||
<div className="video-player-container mr-16 w-[360px] flex flex-col">
|
||||
<div className="text-center text-base text-gray-400">预览视频 - 点击视频列表播放</div>
|
||||
<div className="video-player flex items-center mt-2">
|
||||
<div className=" w-[360px] h-[630px] rounded overflow-hidden">
|
||||
<div className=" w-[360px] h-[636px] rounded overflow-hidden">
|
||||
<Player
|
||||
ref={player} url={videoData[state.playingIndex]?.oss_video_url}
|
||||
onChange={(state) => {
|
||||
@ -137,11 +142,15 @@ export default function VideoIndex() {
|
||||
</div>
|
||||
<div className="text-center text-sm mt-4 text-gray-400">{formatDuration(state.playState.current)} / {formatDuration(state.playState.total)}</div>
|
||||
</div>
|
||||
<div className="video-list-container rounded flex flex-col flex-1">
|
||||
<div className="video-list-container rounded mt-2 flex flex-col flex-1">
|
||||
<div className="live-control flex justify-between">
|
||||
<div className="pl-[70px]"></div>
|
||||
<div className="flex items-center pr-[10px]">
|
||||
<button className="hover:text-blue-300 text-gray-400 text-lg"
|
||||
<div className="flex items-center">
|
||||
<Space>
|
||||
<span>总共 {videoData.length || 0} 条</span>
|
||||
<span className={'text-blue-500'}>已选 {checkedIdArray.length} 条</span>
|
||||
</Space>
|
||||
<button className="hover:text-blue-300 text-gray-400 ml-2"
|
||||
onClick={handleAllCheckedChange}>
|
||||
<span className="text-sm mr-2">全选</span>
|
||||
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
|
||||
@ -149,7 +158,7 @@ export default function VideoIndex() {
|
||||
<Checkbox checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'video-list-sort-container flex-1'}>
|
||||
<div className={'video-list-sort-container flex-1 mt-1'}>
|
||||
<div className="list-header">
|
||||
<div className="list-row header-row">
|
||||
<div className="col number">No.</div>
|
||||
@ -159,7 +168,7 @@ export default function VideoIndex() {
|
||||
<div className="col operation"></div>
|
||||
</div>
|
||||
</div>
|
||||
<InfiniteScroller ref={scrollerRef} onScroll={top => setState({showToTop: top > 30})}>
|
||||
<InfiniteScroller loading={state.loading} ref={scrollerRef} onScroll={top => setState({showToTop: top > 30})}>
|
||||
{
|
||||
videoData.length == 0 ? <div className="m-auto"><Empty/></div> :
|
||||
<div className="sort-list-container flex-1">
|
||||
|
2
src/types/api.d.ts
vendored
2
src/types/api.d.ts
vendored
@ -98,6 +98,7 @@ declare interface VideoInfo {
|
||||
article_id: number;
|
||||
status: number;
|
||||
publish_time?: number|string;
|
||||
ctime?: number|string;
|
||||
}
|
||||
// room live
|
||||
declare interface LiveVideoInfo {
|
||||
@ -111,6 +112,7 @@ declare interface LiveVideoInfo {
|
||||
status: number;
|
||||
order_no: string;
|
||||
publish_time?: number|string;
|
||||
ctime?: number|string;
|
||||
}
|
||||
|
||||
declare interface LiveState{
|
||||
|
@ -11,6 +11,11 @@ const themeConfig = {
|
||||
'active': '#FFE0E0',
|
||||
'primary-red':'#F5222D',
|
||||
'primary-red-70':'rgba(245,34,45,0.7)',
|
||||
|
||||
'popconfirm-bg':'#ff5C5C',
|
||||
'popconfirm-btn-primary-hover':'#f15656',
|
||||
'popconfirm-btn-cancel':'#818181',
|
||||
'popconfirm-btn-cancel-hover':'rgba(71, 71, 71, 1)',
|
||||
},
|
||||
widths:{
|
||||
'chat-avatar-size': '32px',
|
||||
@ -48,6 +53,9 @@ export default {
|
||||
},
|
||||
backgroundColor: {
|
||||
...themeConfig.colors,
|
||||
},
|
||||
textColor: {
|
||||
...themeConfig.colors,
|
||||
}
|
||||
},
|
||||
screens: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user