Compare commits
2 Commits
39f254c99b
...
58ace4514b
Author | SHA1 | Date | |
---|---|---|---|
58ace4514b | |||
0592d97e39 |
@ -24,6 +24,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"file-saver": "^2.0.5",
|
||||
"flv.js": "^1.6.2",
|
||||
"jszip": "^3.10.1",
|
||||
"qs": "^6.12.1",
|
||||
"react": "^18.3.1",
|
||||
|
@ -1,3 +1,5 @@
|
||||
@use "./libs" as *;
|
||||
|
||||
:root {
|
||||
font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.5;
|
||||
@ -25,32 +27,35 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #999;
|
||||
background: #ccc;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
background: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-5 py-2 rounded-md bg-white border text-sm;
|
||||
&:hover {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
@apply bg-blue-500 text-white border-blue-500;
|
||||
@layer base {
|
||||
.btn {
|
||||
@apply px-5 py-2 rounded-md bg-white border text-sm;
|
||||
&:hover {
|
||||
@apply bg-blue-600;
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
@apply bg-blue-500 text-white border-blue-500;
|
||||
&:hover {
|
||||
@apply bg-blue-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white rounded-lg p-5 my-10;
|
||||
.card {
|
||||
@apply bg-white rounded-lg p-5 my-10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.radio-icon, .checkbox-icon {
|
||||
@ -121,31 +126,41 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-live{
|
||||
.live-player{
|
||||
|
||||
.page-live {
|
||||
.live-player {
|
||||
max-height: calc(100vh - var(--app-header-header) - 130px);
|
||||
overflow: hidden;
|
||||
iframe{
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-item-shadow {
|
||||
box-shadow: 0 0 6px 0 var(--tw-shadow-color);
|
||||
//filter: drop-shadow(0 0 6px var(--tw-shadow-color));
|
||||
}
|
||||
.video-list-sort-container{
|
||||
|
||||
.video-list-sort-container {
|
||||
min-height: 300px;
|
||||
max-height: calc(100vh - var(--app-header-header) - 300px);
|
||||
overflow: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.live-video-list-sort-container{
|
||||
.live-video-list-sort-container {
|
||||
min-height: 300px;
|
||||
padding-right: 10px;
|
||||
max-height: calc(100vh - var(--app-header-header) - 200px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app-main-navigation {
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none;
|
||||
}
|
||||
}
|
22
src/assets/libs.scss
Normal file
22
src/assets/libs.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@mixin media-breakpoint-down($name) {
|
||||
@if $name == sm {
|
||||
@media (max-width: 767px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $name == md {
|
||||
@media (max-width: 991px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $name == lg {
|
||||
@media (max-width: 1199px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $name == xl {
|
||||
@media (max-width: 1399px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
@ -80,7 +80,6 @@ export default function ArticleBlock(
|
||||
<div>
|
||||
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
|
||||
<BlockText
|
||||
isFirstBlock={true}
|
||||
onChange={(block) => handleBlockChange(0, block)}
|
||||
data={blocks[0]}
|
||||
isFirstBlock={index == 0}
|
||||
@ -98,7 +97,7 @@ export default function ArticleBlock(
|
||||
{editable && <div className="ml-2 flex flex-col justify-between ">
|
||||
{
|
||||
index > 0 ? <Popconfirm
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此删除此分组?</span></div>}
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此分组?</span></div>}
|
||||
onConfirm={onRemove}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
|
@ -11,19 +11,46 @@ type Props = {
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
|
||||
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
|
||||
const lastGroup = groups[groups.length - 1]
|
||||
if (lastGroup && lastGroup.filter(s=>s.type == 'text') == 0) {
|
||||
// 如果上一个group中没有文本则直接合并
|
||||
lastGroup.push(...blocks)
|
||||
} else {
|
||||
groups.push(blocks)
|
||||
}
|
||||
}
|
||||
|
||||
function rebuildGroups(groups: BlockContent[][]) {
|
||||
if (groups.length < 2) {
|
||||
Array(2 - groups.length).fill([{type: 'text', content: ''}]).forEach((it) => {
|
||||
groups.push(it)
|
||||
const _groups: BlockContent[][] = [];
|
||||
if (!groups || groups.length == 0) return _groups;
|
||||
groups.forEach((blocks,index) => {
|
||||
if(!blocks) return;
|
||||
if (blocks.length == 1) {
|
||||
if(index == 0) _groups.push(blocks)
|
||||
else pushBlocksToGroup(blocks,_groups)
|
||||
} else {
|
||||
if(index == 0){
|
||||
_groups.push([blocks[0]])
|
||||
_groups.push(blocks.slice(1))
|
||||
}else{
|
||||
pushBlocksToGroup(blocks,_groups)
|
||||
}
|
||||
}
|
||||
});
|
||||
if (_groups.length < 2) {
|
||||
Array(2 - _groups.length).fill([{type: 'text', content: ''}]).forEach((it) => {
|
||||
_groups.push(it)
|
||||
})
|
||||
}
|
||||
|
||||
return groups;
|
||||
console.log('rebuildGroups', _groups)
|
||||
return _groups;
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default function ArticleGroup({groups: _groups, editable, onChange,errorMessage}: Props) {
|
||||
export default function ArticleGroup({groups: _groups, editable, onChange, errorMessage}: Props) {
|
||||
const groups = rebuildGroups(_groups)
|
||||
/**
|
||||
* 添加一个组
|
||||
|
@ -2,7 +2,7 @@ import {useSortable} from "@dnd-kit/sortable";
|
||||
import {useSetState} from "ahooks";
|
||||
import React, {useEffect} from "react";
|
||||
import {clsx} from "clsx";
|
||||
import {Popconfirm} from "antd";
|
||||
import {Image, Popconfirm} from "antd";
|
||||
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled} from "@ant-design/icons";
|
||||
|
||||
import ImageCover from '@/assets/images/cover.png'
|
||||
@ -28,7 +28,7 @@ export const VideoListItem = (
|
||||
// index,
|
||||
id, video, onPlay, onRemove, checked,
|
||||
onCheckedChange, onEdit, active, editable,
|
||||
className,
|
||||
className, sortable
|
||||
}: Props) => {
|
||||
const {
|
||||
attributes, listeners,
|
||||
@ -45,23 +45,23 @@ export const VideoListItem = (
|
||||
className={`video-item flex items-center gap-3 ${className}`}
|
||||
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}>
|
||||
{/*{index && index > 0 && <div className="flex items-center px-2">*/}
|
||||
{/* <div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}
|
||||
{/*</div>}*/}
|
||||
{/* <div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}
|
||||
{/*</div>}*/}
|
||||
<div
|
||||
className={`video-item-info flex gap-2 flex-1 bg-gray-100 h-[80px] overflow-hidden rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
|
||||
<div className={'video-title leading-7 flex-1'}>{video.title || video.video_title}</div>
|
||||
<div className={'video-item-cover'}>
|
||||
<img className="w-[100px] rounded-md" src={video.cover_url || ImageCover} alt={video.video_title}/>
|
||||
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
|
||||
<img className="w-[100px] h-[56px] object-cover" src={video.cover_url || video.cover || ImageCover} alt={video.video_title}/>
|
||||
</div>
|
||||
</div>
|
||||
{editable &&
|
||||
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
|
||||
{!active ? <button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
|
||||
<MenuOutlined/>
|
||||
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>}
|
||||
{onPlay &&
|
||||
<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
|
||||
</button>}
|
||||
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
|
||||
{sortable && (!active ? <button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
|
||||
<MenuOutlined/>
|
||||
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
|
||||
{onPlay &&
|
||||
<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
|
||||
</button>}
|
||||
{editable && <>
|
||||
{onEdit &&
|
||||
<button className="hover:text-blue-500" onClick={onEdit} style={{fontSize: '1.1em'}}><IconEdit/>
|
||||
</button>}
|
||||
@ -73,15 +73,14 @@ export const VideoListItem = (
|
||||
}
|
||||
}}><CheckCircleFilled className={clsx({'text-blue-500': state.checked})}/></button>
|
||||
{onRemove && <Popconfirm
|
||||
title="提示"
|
||||
description={<div style={{minWidth: 150}}><span>请确认删除此视频?</span></div>}
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此视频?</span></div>}
|
||||
onConfirm={onRemove}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<button className="hover:text-blue-500"><MinusCircleFilled/></button>
|
||||
</Popconfirm>}
|
||||
</div>
|
||||
}
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
@ -4,11 +4,12 @@ import {SortableContext, arrayMove} from '@dnd-kit/sortable';
|
||||
import {DndContext} from "@dnd-kit/core";
|
||||
|
||||
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||
import {getList, playState} from "@/service/api/live.ts";
|
||||
import {deleteByIds, getList, modifyOrder, playState} from "@/service/api/live.ts";
|
||||
|
||||
import styles from './style.module.scss'
|
||||
import {set} from "lodash";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
import ButtonBatch from "@/components/button-batch.tsx";
|
||||
|
||||
export default function LiveIndex() {
|
||||
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
||||
@ -38,6 +39,7 @@ export default function LiveIndex() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const activeToNext = (index?: number) => {
|
||||
const endToFirst = index != undefined && index > -1 ? false : state.activeIndex >= videoData.length - 1
|
||||
const activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : state.activeIndex + 1)
|
||||
@ -60,105 +62,51 @@ export default function LiveIndex() {
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const loadList = () => {
|
||||
getList().then(res => {
|
||||
res.list = [
|
||||
{
|
||||
id: 11,
|
||||
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: 0,
|
||||
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: 10,
|
||||
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'
|
||||
}
|
||||
|
||||
]
|
||||
console.log('origin list', res.list.map(s => s.id))
|
||||
setVideoData(res.list || [])
|
||||
initPlayingState(res.list || [])
|
||||
})
|
||||
}, [])
|
||||
const processDeleteVideo = async (_idArray: number[]) => {
|
||||
message.info('删除成功!!!' + _idArray.join(''));
|
||||
setCheckedIdArray([])
|
||||
initPlayingState(res.list)
|
||||
});
|
||||
}
|
||||
const handleDeleteBatch = () => {
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否要删除选择的视频?',
|
||||
onOk: () => processDeleteVideo(checkedIdArray)
|
||||
})
|
||||
|
||||
useEffect(loadList, [])
|
||||
|
||||
const processDeleteVideo = async (ids: number[]) => {
|
||||
deleteByIds(ids).then(()=>{
|
||||
showToast('删除成功!','success')
|
||||
loadList()
|
||||
}).catch(showErrorToast)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否采纳全部编辑操作?',
|
||||
content: '是否采纳移动视频位置操作?',
|
||||
onOk: () => {
|
||||
showToast('编辑成功!!!', 'info');
|
||||
//showToast('编辑成功!!!', 'info');
|
||||
modifyOrder(videoData.map(s => s.id)).then(() => {
|
||||
setEditable(false)
|
||||
loadList()
|
||||
}).catch(() => {
|
||||
showToast('调整视频顺序失败,请重试!')
|
||||
})
|
||||
// showToast('编辑成功!!!', 'info');
|
||||
// console.log('origin list', videoData.map(s => s.id))
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleCancelConfirm = () => {
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否取消移动视频位置操作?',
|
||||
onOk: () => {
|
||||
showToast('退出并清除移动视频位置操作!', 'info');
|
||||
loadList()
|
||||
setEditable(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (<div className="container py-10 page-live">
|
||||
{contextHolder}
|
||||
@ -177,14 +125,20 @@ export default function LiveIndex() {
|
||||
{editable ? <>
|
||||
<div className="flex gap-2">
|
||||
<Button type="primary" onClick={handleConfirm}>确定</Button>
|
||||
<Button onClick={() => setEditable(false)}>退出</Button>
|
||||
</div>
|
||||
<div>
|
||||
<span className="cursor-pointer" onClick={handleDeleteBatch}>批量删除</span>
|
||||
<Button onClick={handleCancelConfirm}>退出</Button>
|
||||
</div>
|
||||
</> : <div>
|
||||
<Button type="primary" onClick={() => setEditable(true)}>编辑</Button>
|
||||
<Button type="primary" onClick={() => setEditable(true)}>重新排序</Button>
|
||||
</div>}
|
||||
{!editable && <div>
|
||||
<ButtonBatch
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={`请选择要删除的新闻视频`}
|
||||
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
|
||||
onSuccess={loadList}
|
||||
onProcess={processDeleteVideo}
|
||||
>批量删除</ButtonBatch>
|
||||
</div>}
|
||||
</div>
|
||||
<div className="live-video-list-sort-container">
|
||||
<div className="flex">
|
||||
@ -201,22 +155,22 @@ export default function LiveIndex() {
|
||||
const {active, over} = e;
|
||||
if (over && active.id !== over.id) {
|
||||
let oldIndex = -1, newIndex = -1;
|
||||
const originArr = [...videoData]
|
||||
// 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);
|
||||
},
|
||||
onOk: () => {
|
||||
setVideoData([...videoData])
|
||||
}
|
||||
})
|
||||
// modal.confirm({
|
||||
// title: '提示',
|
||||
// content: '是否要移动到指定位置',
|
||||
// onCancel: () => {
|
||||
// setVideoData(originArr);
|
||||
// },
|
||||
// onOk: () => {
|
||||
// setVideoData([...videoData])
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}}>
|
||||
<SortableContext items={videoData}>
|
||||
@ -225,16 +179,18 @@ export default function LiveIndex() {
|
||||
video={v}
|
||||
index={index + 1}
|
||||
id={v.id}
|
||||
active={state.activeIndex == index}
|
||||
key={index}
|
||||
active={state.activeIndex == index}
|
||||
className={`list-item-${index} mt-3 mb-2`}
|
||||
checked={checkedIdArray.includes(v.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
setCheckedIdArray(idArray => {
|
||||
return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
||||
})
|
||||
}}
|
||||
onRemove={() => processDeleteVideo([v.id])}
|
||||
editable={editable}
|
||||
editable={!editable}
|
||||
sortable={editable}
|
||||
/>))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
@ -1,13 +1,12 @@
|
||||
import {useState} from "react";
|
||||
import {Checkbox, Empty, Modal, Pagination, Space} from "antd";
|
||||
import {useRequest, useSetState} from "ahooks";
|
||||
import {useRequest} from "ahooks";
|
||||
|
||||
import {Card} from "@/components/card";
|
||||
import {getList} from "@/service/api/article.ts";
|
||||
|
||||
import SearchPanel from "@/pages/news/components/search-panel.tsx";
|
||||
import styles from './style.module.scss'
|
||||
import {getById} from "@/service/api/news.ts";
|
||||
import {getById,getList} from "@/service/api/news.ts";
|
||||
import {showLoading} from "@/components/message.ts";
|
||||
import {formatTime} from "@/util/strings.ts";
|
||||
import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx";
|
||||
|
90
src/pages/test.tsx
Normal file
90
src/pages/test.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import {useRef, useState} from "react";
|
||||
import {Button} from "antd";
|
||||
import FlvJs from "flv.js";
|
||||
|
||||
const list = [
|
||||
{
|
||||
"id": 10,
|
||||
"cover_url": "",
|
||||
"video_id": 51,
|
||||
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
|
||||
"video_duration": 31910,
|
||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251497659736064.flv",
|
||||
"status": 4,
|
||||
"order_no": ""
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"cover_url": "",
|
||||
"video_id": 43,
|
||||
"video_title": "历时12天史上第三人 尹锡悦总统弹劾案获通过 一文梳理韩国政坛众生相",
|
||||
"video_duration": 728840,
|
||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv",
|
||||
"status": 4,
|
||||
"order_no": ""
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"cover_url": "",
|
||||
"video_id": 44,
|
||||
"video_title": "推动房地产市场止跌回稳,发力重点在哪里?",
|
||||
"video_duration": 57500,
|
||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229857764810752.flv",
|
||||
"status": 4,
|
||||
"order_no": ""
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"cover_url": "",
|
||||
"video_id": 52,
|
||||
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
|
||||
"video_duration": 37980,
|
||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251495390617600.flv",
|
||||
"status": 4,
|
||||
"order_no": ""
|
||||
}
|
||||
]
|
||||
|
||||
const cache:{
|
||||
flvPlayer?: FlvJs.Player
|
||||
} = {
|
||||
|
||||
}
|
||||
export default function Test() {
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||
const [index, setIndex] = useState(-1)
|
||||
const load = (url: string) => {
|
||||
if (FlvJs.isSupported()) {
|
||||
if(cache.flvPlayer){
|
||||
cache.flvPlayer.pause()
|
||||
cache.flvPlayer.unload()
|
||||
}
|
||||
cache.flvPlayer = FlvJs.createPlayer({
|
||||
type: 'flv',
|
||||
url: url
|
||||
})
|
||||
|
||||
cache.flvPlayer.attachMediaElement(videoRef.current!)
|
||||
cache.flvPlayer.load()
|
||||
cache.flvPlayer.play()
|
||||
}
|
||||
// const url = 'https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv'
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current!.src = url
|
||||
// videoRef.current?.play()
|
||||
// }
|
||||
}
|
||||
const play = () => {
|
||||
const next = index >= list.length - 1 ? 0 : index + 1
|
||||
load(list[next].video_oss_url)
|
||||
setIndex(next)
|
||||
}
|
||||
return (<div className="test container m-auto">
|
||||
<div className="my-10">
|
||||
<video controls className="border w-[400px] max-h-[600px]" ref={videoRef}></video>
|
||||
</div>
|
||||
<Button onClick={play}>load {index > -1 ? <span>
|
||||
{index} {list[index].video_title}
|
||||
</span>:''}</Button>
|
||||
</div>)
|
||||
}
|
@ -10,11 +10,11 @@ import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
||||
import {deleteByIds, getList, modifyOrder, push2room} 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";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import FlvJs from "flv.js";
|
||||
|
||||
const cache:{flvPlayer?: FlvJs.Player} = {}
|
||||
export default function VideoIndex() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
||||
@ -29,7 +29,6 @@ export default function VideoIndex() {
|
||||
// 加载列表
|
||||
const loadList = () => {
|
||||
getList().then((ret) => {
|
||||
console.log('origin list', ret.list.map(s => s.id))
|
||||
setCheckedIdArray([])
|
||||
setVideoData(ret.list || [])
|
||||
setState({checkedAll: false, playingIndex: -1})
|
||||
@ -40,6 +39,21 @@ export default function VideoIndex() {
|
||||
const playVideo = (video: VideoInfo, playingIndex: number) => {
|
||||
if (videoRef.current && video.oss_video_url) {
|
||||
setState({playingIndex})
|
||||
if (FlvJs.isSupported()) {
|
||||
// 已经有播放实例 则销毁
|
||||
if(cache.flvPlayer){
|
||||
cache.flvPlayer.pause()
|
||||
cache.flvPlayer.unload()
|
||||
}
|
||||
cache.flvPlayer = FlvJs.createPlayer({
|
||||
type: 'flv',
|
||||
url: video.oss_video_url
|
||||
})
|
||||
|
||||
cache.flvPlayer.attachMediaElement(videoRef.current!)
|
||||
cache.flvPlayer.load()
|
||||
cache.flvPlayer.play()
|
||||
}
|
||||
videoRef.current!.src = video.oss_video_url
|
||||
}
|
||||
}
|
||||
@ -51,7 +65,6 @@ export default function VideoIndex() {
|
||||
})
|
||||
}
|
||||
const handleModifySort = () => {
|
||||
|
||||
setVideoData((items) => {
|
||||
modifyOrder(items.map(s => s.id)).catch(() => {
|
||||
showToast('调整视频顺序失败,请重试!')
|
||||
@ -81,7 +94,10 @@ export default function VideoIndex() {
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={`请选择要删除的新闻视频`}
|
||||
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
|
||||
onSuccess={loadList}
|
||||
onSuccess={()=>{
|
||||
showToast('删除成功!','success')
|
||||
loadList()
|
||||
}}
|
||||
>批量删除</ButtonBatch>
|
||||
|
||||
<button className="ml-5 hover:text-blue-300 text-gray-400 text-lg"
|
||||
@ -144,7 +160,8 @@ export default function VideoIndex() {
|
||||
onEdit={() => {
|
||||
setEditId(v.article_id)
|
||||
}}
|
||||
editable
|
||||
editable={true}
|
||||
sortable={true}
|
||||
/>))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
@ -36,7 +36,7 @@ const NavItems = [
|
||||
]
|
||||
|
||||
export function DashboardNavigation() {
|
||||
return (<div className={'flex'}>
|
||||
return (<div className={'flex app-main-navigation'}>
|
||||
{NavItems.map((it, idx) => (
|
||||
<NavLink to={it.path} key={idx} className={clsx('nav-item cursor-pointer items-center')}>
|
||||
<span className="menu-text ml-1">{it.name}</span>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {RouteObject} from "react-router-dom";
|
||||
import ErrorBoundary from "@/routes/error.tsx";
|
||||
import UserAuth from "@/pages/user";
|
||||
import Test from "@/pages/test";
|
||||
import CreateIndex from "../pages/video";
|
||||
import LibraryIndex from "@/pages/library";
|
||||
import LiveIndex from "@/pages/live";
|
||||
@ -13,6 +14,10 @@ const routes: RouteObject[] = [
|
||||
path: '/user',
|
||||
element: <UserAuth/>,
|
||||
},
|
||||
{
|
||||
path: '/test',
|
||||
element: <Test/>,
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
element: <DashboardLayout/>,
|
||||
|
@ -12,9 +12,9 @@ export function getList() {
|
||||
}
|
||||
|
||||
export function modifyOrder(ids: Id[]) {
|
||||
return post('/video/order', {ids})
|
||||
return post('/room/order', {ids})
|
||||
}
|
||||
|
||||
export function deleteById(ids: Id[]) {
|
||||
return post('/video/remove', {ids})
|
||||
export function deleteByIds(ids: Id[]) {
|
||||
return post('/room/remove', {ids})
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
export function getList(data: ApiArticleSearchParams & ApiRequestPageParams) {
|
||||
return post<DataList<ListCrawlerNewsItem>>({url: '/article/search', data})
|
||||
return post<DataList<ListCrawlerNewsItem>>({url: '/spider/search', data})
|
||||
}
|
||||
|
||||
export function getById(id: Id) {
|
||||
|
18
yarn.lock
18
yarn.lock
@ -1412,6 +1412,11 @@ es-errors@^1.3.0:
|
||||
resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es6-promise@^4.2.8:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
||||
|
||||
esbuild@^0.21.3:
|
||||
version "0.21.5"
|
||||
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
|
||||
@ -1625,6 +1630,14 @@ flatted@^3.2.9:
|
||||
resolved "https://registry.npmmirror.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
|
||||
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
|
||||
|
||||
flv.js@^1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.npmmirror.com/flv.js/-/flv.js-1.6.2.tgz#fa3340fe3f7ee01d3977f7876aee66b8436e5922"
|
||||
integrity sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==
|
||||
dependencies:
|
||||
es6-promise "^4.2.8"
|
||||
webworkify-webpack "^2.1.5"
|
||||
|
||||
follow-redirects@^1.15.6:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
@ -3163,6 +3176,11 @@ vite@^5.2.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
webworkify-webpack@^2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.npmmirror.com/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz#bf4336624c0626cbe85cf1ffde157f7aa90b1d1c"
|
||||
integrity sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user