diff --git a/package.json b/package.json index 040a9a4..c2a8f7f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/assets/core.scss b/src/assets/core.scss index 47b4bef..08985b6 100644 --- a/src/assets/core.scss +++ b/src/assets/core.scss @@ -1,3 +1,5 @@ +@use "./libs" as *; + :root { font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif; line-height: 1.5; @@ -35,22 +37,25 @@ } } -.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; + } +} \ No newline at end of file diff --git a/src/assets/libs.scss b/src/assets/libs.scss new file mode 100644 index 0000000..891a092 --- /dev/null +++ b/src/assets/libs.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/src/components/article/block.tsx b/src/components/article/block.tsx index 1553d3e..32db078 100644 --- a/src/components/article/block.tsx +++ b/src/components/article/block.tsx @@ -80,7 +80,6 @@ export default function ArticleBlock(
handleBlockChange(0, block)} data={blocks[0]} isFirstBlock={index == 0} @@ -98,7 +97,7 @@ export default function ArticleBlock( {editable &&
{ index > 0 ? 请确认删除此删除此分组?
} + title={
请确认删除此分组?
} onConfirm={onRemove} okText="删除" cancelText="取消" diff --git a/src/components/video/video-list-item.tsx b/src/components/video/video-list-item.tsx index 2a45281..b85f009 100644 --- a/src/components/video/video-list-item.tsx +++ b/src/components/video/video-list-item.tsx @@ -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,8 +45,8 @@ export const VideoListItem = ( className={`video-item flex items-center gap-3 ${className}`} ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}> {/*{index && index > 0 &&
*/} - {/*
{index}
*/} - {/*
}*/} + {/*
{index}
*/} + {/*
}*/}
{video.title || video.video_title}
@@ -54,14 +54,14 @@ export const VideoListItem = ( {video.video_title}/
- {editable && -
- {!active ? : } - {onPlay && - } +
+ {sortable && (!active ? : )} + {onPlay && + } + {editable && <> {onEdit && } @@ -73,15 +73,14 @@ export const VideoListItem = ( } }}> {onRemove && 请确认删除此视频?
} + title={
请确认删除此视频?
} onConfirm={onRemove} okText="删除" cancelText="取消" > } -
- } + } + } \ No newline at end of file diff --git a/src/pages/live/index.tsx b/src/pages/live/index.tsx index ce03999..af456fa 100644 --- a/src/pages/live/index.tsx +++ b/src/pages/live/index.tsx @@ -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([]) @@ -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 (
{contextHolder} @@ -177,14 +125,20 @@ export default function LiveIndex() { {editable ? <>
- -
-
- 批量删除 +
:
- +
} + {!editable &&
+ 批量删除 +
}
@@ -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]) + // } + // }) } }}> @@ -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} />))} diff --git a/src/pages/test.tsx b/src/pages/test.tsx new file mode 100644 index 0000000..80d3c0a --- /dev/null +++ b/src/pages/test.tsx @@ -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(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 (
+
+ +
+ +
) +} \ No newline at end of file diff --git a/src/pages/video/index.tsx b/src/pages/video/index.tsx index c8adcad..cdbbda4 100644 --- a/src/pages/video/index.tsx +++ b/src/pages/video/index.tsx @@ -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([]) @@ -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() + }} >批量删除