💄 update video item active

This commit is contained in:
LittleBoy 2024-12-15 13:33:01 +08:00
parent 950bc59847
commit 2525358eb9
4 changed files with 198 additions and 112 deletions

View File

@ -105,12 +105,30 @@
} }
} }
} }
.page-live{
.live-player{
max-height: calc(100vh - var(--app-header-header) - 130px);
overflow: hidden;
iframe{
width: 100%;
height: 100%;
overflow: hidden;
}
}
}
.video-item-shadow { .video-item-shadow {
box-shadow: 0 0 6px 0 var(--tw-shadow-color); box-shadow: 0 0 6px 0 var(--tw-shadow-color);
//filter: drop-shadow(0 0 6px var(--tw-shadow-color)); //filter: drop-shadow(0 0 6px var(--tw-shadow-color));
} }
.video-list-sort-container{ .video-list-sort-container{
height: calc(100vh - var(--app-header-header) - 300px); min-height: 300px;
max-height: calc(100vh - var(--app-header-header) - 300px);
overflow: auto;
}
.live-video-list-sort-container{
min-height: 300px;
padding-right: 10px;
max-height: calc(100vh - var(--app-header-header) - 200px);
overflow: auto; overflow: auto;
} }

View File

@ -20,6 +20,7 @@ type Props = {
onEdit?: () => void; onEdit?: () => void;
onRemove?: () => void; onRemove?: () => void;
id: number; id: number;
className?: string;
} }
export const VideoListItem = ( export const VideoListItem = (
@ -27,6 +28,7 @@ export const VideoListItem = (
// index, // index,
id, video, onPlay, onRemove, checked, id, video, onPlay, onRemove, checked,
onCheckedChange, onEdit, active, editable, onCheckedChange, onEdit, active, editable,
className,
}: Props) => { }: Props) => {
const { const {
attributes, listeners, attributes, listeners,
@ -40,7 +42,7 @@ export const VideoListItem = (
}, [checked]) }, [checked])
return <div return <div
className={'video-item flex items-center gap-3 mb-5'} className={`video-item flex items-center gap-3 ${className}`}
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}> ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}>
{/*{index && index > 0 && <div className="flex items-center px-2">*/} {/*{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 className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}

View File

@ -4,9 +4,11 @@ import {SortableContext, arrayMove} from '@dnd-kit/sortable';
import {DndContext} from "@dnd-kit/core"; import {DndContext} from "@dnd-kit/core";
import {VideoListItem} from "@/components/video/video-list-item.tsx"; import {VideoListItem} from "@/components/video/video-list-item.tsx";
import {getList} from "@/service/api/live.ts"; import {getList, playState} from "@/service/api/live.ts";
import styles from './style.module.scss' import styles from './style.module.scss'
import {set} from "lodash";
import {showToast} from "@/components/message.ts";
export default function LiveIndex() { export default function LiveIndex() {
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([]) const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
@ -15,14 +17,55 @@ export default function LiveIndex() {
const [editable, setEditable] = useState<boolean>(false) const [editable, setEditable] = useState<boolean>(false)
const [state, setState] = useState({ const [state, setState] = useState({
activeId: -1, activeIndex: -1,
}) })
const showVideoItem = (index: number) => {
// 找到对应video item 并显示在视图可见区域
const container = document.querySelector('.live-video-list-sort-container')
const item = document.querySelector(`.list-item-${index}`)
if (item && container) {
// 获取容器数据
const containerRect = container.getBoundingClientRect()
// 获取对应item的数据
const rect = item.getBoundingClientRect()
// 计算对应item需要在容器中滚动的距离
const scrollDistance = rect.top - containerRect.top
// 设置滚动高度
container.scrollTo({
top: index == 0 ? 0 : container.scrollTop + scrollDistance - 10,
behavior: 'smooth'
})
}
}
const activeToNext = () => {
const endToFirst = state.activeIndex >= videoData.length - 1
const activeIndex = endToFirst ? 0 : state.activeIndex + 1
setState(() => {
return {
activeIndex
}
})
if (endToFirst) {
showToast('即将播放第一条视频');
}
// 找到对应video item 并显示在视图可见区域
showVideoItem(activeIndex)
}
const initPlayingState = (videoList?: LiveVideoInfo[] | null) => {
const list = videoList || videoData || []
playState().then(ret => {
setState({
activeIndex: 0 //list.findIndex(v => v.id === ret.id)
})
});
}
useEffect(() => { useEffect(() => {
getList().then(res => { getList().then(res => {
setVideoData([ res.list = [
{ {
id: 1, id: 11,
video_id: 1, video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要', video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
@ -32,7 +75,7 @@ export default function LiveIndex() {
order_no: '1' order_no: '1'
}, },
{ {
id: 2, id: 0,
video_id: 1, video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要', video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
@ -42,7 +85,7 @@ export default function LiveIndex() {
order_no: '1' order_no: '1'
}, },
{ {
id: 3, id: 10,
video_id: 1, video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要', video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg', cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
@ -91,7 +134,10 @@ export default function LiveIndex() {
status: 1, status: 1,
order_no: '1' order_no: '1'
} }
])
]
setVideoData(res.list || [])
initPlayingState(res.list || [])
}) })
}, []) }, [])
const processDeleteVideo = async (_idArray: number[]) => { const processDeleteVideo = async (_idArray: number[]) => {
@ -109,26 +155,25 @@ export default function LiveIndex() {
title: '提示', title: '提示',
content: '是否采纳全部编辑操作?', content: '是否采纳全部编辑操作?',
onOk: () => { onOk: () => {
message.info('编辑成功!!!'); showToast('编辑成功!!!', 'info');
} }
}) })
} }
return (<div className="container py-10 page-live"> return (<div className="container py-10 page-live">
{contextHolder} {contextHolder}
<div className="flex"> <div className="flex">
<div className="video-player-container mr-8 flex flex-col"> <div className="video-player-container mr-8 flex flex-col">
<div className="text-center text-base"></div> <div className="text-center text-base"></div>
<div className="video-player flex justify-center flex-1 mt-5"> <div className="video-player flex justify-center flex-1 mt-5">
<div className=" rounded overflow-hidden w-[360px] h-[700px]"> <div className="live-player rounded overflow-hidden w-[360px] h-[700px]">
<iframe src="https://fm.gachafun.com/" className="border-0 w-full h-full max-h-full"></iframe> {/*<iframe src="https://fm.gachafun.com/" className="border-0 w-full h-full max-h-full"></iframe>*/}
</div> </div>
</div> </div>
</div> </div>
<div className="video-list-container flex-1 "> <div className="video-list-container flex-1">
<div className=" bg-white py-8 px-6 rounded py-1"> <div className=" bg-white py-8 px-6 rounded">
<div className="live-control flex justify-between mb-8"> <div className="live-control flex justify-between mb-4">
{editable ? <> {editable ? <>
<div className="flex gap-2"> <div className="flex gap-2">
<Button type="primary" onClick={handleConfirm}></Button> <Button type="primary" onClick={handleConfirm}></Button>
@ -140,48 +185,66 @@ export default function LiveIndex() {
</> : <div> </> : <div>
<Button type="primary" onClick={() => setEditable(true)}></Button> <Button type="primary" onClick={() => setEditable(true)}></Button>
</div>} </div>}
<div className="flex gap-2">
<Button type="primary" onClick={showFirst}>showFirst</Button>
<Button type="primary" onClick={activeToNext}>Next</Button>
</div>
</div> </div>
<DndContext onDragEnd={(e) => { <div className="live-video-list-sort-container">
const {active, over} = e; <div className="flex">
if (over && active.id !== over.id) { <div className="sort-number-container mr-2">
let oldIndex = -1, newIndex = -1; {videoData.map((v, index) => (
const originArr = [...videoData] <div key={index} className="flex items-center px-2 h-[80px] mt-3 mb-2">
setVideoData((items) => { <div
oldIndex = items.findIndex(s => s.id == active.id); className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index + 1}</div>
newIndex = items.findIndex(s => s.id == over.id); </div>
return arrayMove(items, oldIndex, newIndex); ))}
}); </div>
modal.confirm({ <div className="sort-list-container flex-1">
title: '提示', <DndContext onDragEnd={(e) => {
content: '是否要移动到指定位置', const {active, over} = e;
onCancel: () => { if (over && active.id !== over.id) {
setVideoData(originArr); let oldIndex = -1, newIndex = -1;
}, const originArr = [...videoData]
onOk: () => { setVideoData((items) => {
setVideoData([...videoData]) oldIndex = items.findIndex(s => s.id == active.id);
} newIndex = items.findIndex(s => s.id == over.id);
}) return arrayMove(items, oldIndex, newIndex);
} });
}}> modal.confirm({
<SortableContext items={videoData}> title: '提示',
{videoData.map((v, index) => ( content: '是否要移动到指定位置',
<VideoListItem onCancel: () => {
video={v} setVideoData(originArr);
index={index + 1} },
id={v.id} onOk: () => {
active={state.activeId == v.id} setVideoData([...videoData])
key={index} }
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
}) })
}} }
onRemove={() => processDeleteVideo([v.id])} }}>
editable={editable} <SortableContext items={videoData}>
/>))} {videoData.map((v, index) => (
</SortableContext> <VideoListItem
</DndContext> video={v}
index={index + 1}
id={v.id}
active={state.activeIndex == index}
key={index}
className={`list-item-${index} mt-3 mb-2`}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
})
}}
onRemove={() => processDeleteVideo([v.id])}
editable={editable}
/>))}
</SortableContext>
</DndContext>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import {message, Modal} from "antd"; import {Empty, message, Modal} from "antd";
import React, {useEffect, useMemo, useRef, useState} from "react"; import React, {useEffect, useMemo, useRef, useState} from "react";
import {DndContext} from "@dnd-kit/core"; import {DndContext} from "@dnd-kit/core";
import {arrayMove, SortableContext} from "@dnd-kit/sortable"; import {arrayMove, SortableContext} from "@dnd-kit/sortable";
@ -43,7 +43,7 @@ export default function VideoIndex() {
}) })
} }
const playVideo = (video: VideoInfo,playingIndex:number) => { const playVideo = (video: VideoInfo, playingIndex: number) => {
setState({ setState({
playingIndex playingIndex
}) })
@ -88,58 +88,61 @@ export default function VideoIndex() {
</div> </div>
</div> </div>
<div className={'flex video-list-sort-container'}> <div className={'flex video-list-sort-container'}>
<div className="sort-number-container mr-2"> {videoData.length == 0 ? <div className="m-auto"><Empty/></div> : <>
{videoData.map((v, index) => ( <div className="sort-number-container mr-2">
<div className="flex items-center px-2 h-[80px] mb-5"> {videoData.map((v, index) => (
<div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div> <div key={index} className="flex items-center px-2 h-[80px] mb-5">
</div> <div
))} className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index + 1}</div>
</div> </div>
<div className="sort-list-container"> ))}
<DndContext onDragEnd={(e) => { </div>
const {active, over} = e; <div className="sort-list-container">
if (over && active.id !== over.id) { <DndContext onDragEnd={(e) => {
let oldIndex = -1, newIndex = -1; const {active, over} = e;
const originArr = [...videoData] if (over && active.id !== over.id) {
setVideoData((items) => { let oldIndex = -1, newIndex = -1;
oldIndex = items.findIndex(s => s.id == active.id); const originArr = [...videoData]
newIndex = items.findIndex(s => s.id == over.id); setVideoData((items) => {
return arrayMove(items, oldIndex, newIndex); oldIndex = items.findIndex(s => s.id == active.id);
}); newIndex = items.findIndex(s => s.id == over.id);
modal.confirm({ return arrayMove(items, oldIndex, newIndex);
title: '提示', });
content: '是否要移动到指定位置', modal.confirm({
onCancel: () => { title: '提示',
setVideoData(originArr); content: '是否要移动到指定位置',
} onCancel: () => {
}) setVideoData(originArr);
} }
}}> })
<SortableContext items={videoData}> }
{videoData.map((v, index) => ( }}>
<VideoListItem <SortableContext items={videoData}>
video={v} {videoData.map((v, index) => (
index={index + 1} <VideoListItem
id={v.id} video={v}
key={index} index={index + 1}
active={state.playingIndex == index} id={v.id}
checked={checkedIdArray.includes(v.id)} key={index}
onCheckedChange={(checked) => { active={state.playingIndex == index}
setCheckedIdArray(idArray => { checked={checkedIdArray.includes(v.id)}
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id); onCheckedChange={(checked) => {
setState({checkedAll: newArr.length == videoData.length}) setCheckedIdArray(idArray => {
return newArr; const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
}) setState({checkedAll: newArr.length == videoData.length})
}} return newArr;
onPlay={() => playVideo(v,index)} })
onEdit={() => { }}
setEditId(v.article_id) onPlay={() => playVideo(v, index)}
}} onEdit={() => {
editable setEditId(v.article_id)
/>))} }}
</SortableContext> editable
</DndContext> />))}
</div> </SortableContext>
</DndContext>
</div>
</>}
</div> </div>
<div className="text-right mt-10"> <div className="text-right mt-10">
<ButtonPush2Room ids={checkedIdArray}/> <ButtonPush2Room ids={checkedIdArray}/>