Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
9e1a7f521d |
@ -13,7 +13,7 @@
|
|||||||
--navigation-width: 100vw;
|
--navigation-width: 100vw;
|
||||||
--navigation-active-color: #ffe0e0;
|
--navigation-active-color: #ffe0e0;
|
||||||
--app-header-header: 70px;
|
--app-header-header: 70px;
|
||||||
--container-width: 1800px;
|
--container-width: 1600px;
|
||||||
--header-z-index: 99999;
|
--header-z-index: 99999;
|
||||||
--message-z-index: 100001;
|
--message-z-index: 100001;
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@
|
|||||||
|
|
||||||
.data-list-container {
|
.data-list-container {
|
||||||
@apply list-scroller-container;
|
@apply list-scroller-container;
|
||||||
height: calc(100vh - var(--app-header-header) - 200px);
|
height: calc(100vh - var(--app-header-header) - 100px);
|
||||||
|
|
||||||
.data-list-container-inner {
|
.data-list-container-inner {
|
||||||
|
|
||||||
|
@ -19,4 +19,14 @@
|
|||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@if $name == xxl {
|
||||||
|
@media (max-width: 1799px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $name == xxxl {
|
||||||
|
@media (max-width: 1999px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -30,6 +30,7 @@ type Props = {
|
|||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
onRegenerate?: () => void;
|
onRegenerate?: () => void;
|
||||||
hideCheckBox?: boolean;
|
hideCheckBox?: boolean;
|
||||||
|
operationRender?:React.ReactNode;
|
||||||
onItemClick?: () => void;
|
onItemClick?: () => void;
|
||||||
onRemove?: (action?:'delete' | 'rollback') => void;
|
onRemove?: (action?:'delete' | 'rollback') => void;
|
||||||
removeIcon?: React.ReactNode;
|
removeIcon?: React.ReactNode;
|
||||||
@ -43,7 +44,8 @@ export const VideoListItem = (
|
|||||||
id, video, onRemove,removeIcon, checked,playing,
|
id, video, onRemove,removeIcon, checked,playing,
|
||||||
onCheckedChange, onEdit, active, editable,
|
onCheckedChange, onEdit, active, editable,
|
||||||
className, sortable, type, index,onItemClick,
|
className, sortable, type, index,onItemClick,
|
||||||
additionOperation,onRegenerate,hideCheckBox
|
additionOperation,onRegenerate,hideCheckBox,
|
||||||
|
operationRender
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
attributes, listeners,
|
attributes, listeners,
|
||||||
@ -111,7 +113,7 @@ export const VideoListItem = (
|
|||||||
{... (sortable && !generating?listeners:{})}
|
{... (sortable && !generating?listeners:{})}
|
||||||
{... (sortable && !generating?attributes:{})}
|
{... (sortable && !generating?attributes:{})}
|
||||||
>{video.ctime ? formatTime(video.ctime,'min') : '-'}</div>
|
>{video.ctime ? formatTime(video.ctime,'min') : '-'}</div>
|
||||||
<div className="col operation">
|
{operationRender ?? <div className="col operation">
|
||||||
{/*{sortable && !generating && (!active ?*/}
|
{/*{sortable && !generating && (!active ?*/}
|
||||||
{/* <button className="hover:text-blue-500 cursor-move">*/}
|
{/* <button className="hover:text-blue-500 cursor-move">*/}
|
||||||
{/* <MenuOutlined/>*/}
|
{/* <MenuOutlined/>*/}
|
||||||
@ -154,7 +156,7 @@ export const VideoListItem = (
|
|||||||
</>}
|
</>}
|
||||||
{additionOperation}
|
{additionOperation}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ const AuthContext = createContext<AuthContextType | null>(null)
|
|||||||
// 权限相关初始化数据
|
// 权限相关初始化数据
|
||||||
const initialState: AuthProps = {
|
const initialState: AuthProps = {
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
isInitialized: false,
|
isInitialized: true,
|
||||||
user: null
|
user: null
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init().then(console.log)
|
//init().then(console.log)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 判断是否已经初始化
|
// 判断是否已经初始化
|
||||||
|
14
src/hooks/useLastState.ts
Normal file
14
src/hooks/useLastState.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// 通过useRef及useEffect实现 获取最新的state值
|
||||||
|
export function useLastState<T>(value: T){
|
||||||
|
// 创建ref
|
||||||
|
const lastState = React.useRef<T>(value);
|
||||||
|
lastState.current = value;
|
||||||
|
// 通过useEffect监听value的变化
|
||||||
|
const getLastState = React.useCallback(() => lastState.current, []);
|
||||||
|
// 返回最新的state值
|
||||||
|
return {
|
||||||
|
lastState,
|
||||||
|
getLastState
|
||||||
|
};
|
||||||
|
}
|
@ -8,7 +8,7 @@ export default function LivePlayer() {
|
|||||||
const [liveUrl, setLiveUrl] = useState<string>('http://fm.live.starbitech.com/fm/prod_fm.flv')
|
const [liveUrl, setLiveUrl] = useState<string>('http://fm.live.starbitech.com/fm/prod_fm.flv')
|
||||||
useMount(async ()=>{
|
useMount(async ()=>{
|
||||||
getLiveUrl().then((ret)=>{
|
getLiveUrl().then((ret)=>{
|
||||||
setLiveUrl(ret.flv_url)
|
//setLiveUrl(ret.flv_url)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return <div className="live-player-wrapper ">
|
return <div className="live-player-wrapper ">
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
||||||
import {Checkbox, Empty, Modal, Space} from "antd";
|
import {Empty, Space} from "antd";
|
||||||
import {SortableContext, arrayMove} from '@dnd-kit/sortable';
|
|
||||||
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 {deleteByIds, getList, modifyOrder, playState} from "@/service/api/live.ts";
|
import {getList, playState} from "@/service/api/live.ts";
|
||||||
|
|
||||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||||
import ButtonBatch from "@/components/button-batch.tsx";
|
|
||||||
import FlvJs from "flv.js";
|
import FlvJs from "flv.js";
|
||||||
import {formatDuration} from "@/util/strings.ts";
|
import {formatDuration} from "@/util/strings.ts";
|
||||||
import {useSetState} from "ahooks";
|
import {useSetState} from "ahooks";
|
||||||
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
||||||
import {IconDelete, IconLocked, IconUnlock} from "@/components/icons";
|
|
||||||
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
||||||
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
const cache: { flvPlayer?: FlvJs.Player, timerPlayNext?: any, timerLoadState?: any, prevUrl?: string } = {}
|
const cache: { flvPlayer?: FlvJs.Player, timerPlayNext?: any, timerLoadState?: any, prevUrl?: string } = {}
|
||||||
@ -23,9 +18,7 @@ export default function LiveIndex() {
|
|||||||
const player = useRef<PlayerInstance | null>(null)
|
const player = useRef<PlayerInstance | null>(null)
|
||||||
|
|
||||||
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
||||||
const [modal, contextHolder] = Modal.useModal()
|
|
||||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||||
const [editable, setEditable] = useState<boolean>(false)
|
|
||||||
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
||||||
|
|
||||||
const [state, setState] = useSetState({
|
const [state, setState] = useSetState({
|
||||||
@ -40,6 +33,7 @@ export default function LiveIndex() {
|
|||||||
const activeIndex = useRef(-1)
|
const activeIndex = useRef(-1)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
activeIndex.current = videoData.findIndex(s=>s.id == state.playId)
|
activeIndex.current = videoData.findIndex(s=>s.id == state.playId)
|
||||||
|
document.querySelector(`.video-item-${state.playId}`)?.scrollIntoView({behavior: 'smooth'})
|
||||||
}, [state.playId,videoData])
|
}, [state.playId,videoData])
|
||||||
|
|
||||||
// 显示当前播放视频对应 view item
|
// 显示当前播放视频对应 view item
|
||||||
@ -150,52 +144,6 @@ export default function LiveIndex() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 删除视频
|
|
||||||
const processDeleteVideo = async (ids: Id[]) => {
|
|
||||||
deleteByIds(ids).then(() => {
|
|
||||||
showToast(t('delete_success'), 'success')
|
|
||||||
loadList()
|
|
||||||
}).catch(showErrorToast)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
const handleConfirm = () => {
|
|
||||||
if (!editable) {
|
|
||||||
setEditable(true)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newSort = videoData.map(s => s.id).join(',')
|
|
||||||
if (newSort == state.originSort) {
|
|
||||||
setEditable(false)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modal.confirm({
|
|
||||||
title: t('confirm.title'),
|
|
||||||
content: t('video.sort_modify_confirm'),
|
|
||||||
centered: true,
|
|
||||||
onOk: () => {
|
|
||||||
//showToast('编辑成功!!!', 'info');
|
|
||||||
modifyOrder(videoData.map(s => s.id)).then(() => {
|
|
||||||
showToast(t('video.sort_modify_live_success'), 'success')
|
|
||||||
setEditable(false)
|
|
||||||
}).catch(() => {
|
|
||||||
showToast(t('video.sort_modify_failed'), 'warning')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
showToast(t('video.sort_modify_rollback'), 'info');
|
|
||||||
loadList()
|
|
||||||
setEditable(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const handleAllCheckedChange = () => {
|
|
||||||
if(editable) return;
|
|
||||||
setCheckedIdArray(state.checkedAll ? [] : videoData.map(v => v.id))
|
|
||||||
setState({
|
|
||||||
checkedAll: !state.checkedAll
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视频相关时长
|
// 视频相关时长
|
||||||
const totalDuration = useMemo(() => {
|
const totalDuration = useMemo(() => {
|
||||||
if (!videoData || videoData.length == 0) return 0;
|
if (!videoData || videoData.length == 0) return 0;
|
||||||
@ -222,21 +170,18 @@ export default function LiveIndex() {
|
|||||||
// return checkedIdArray.filter(id => currentId.id != id)
|
// return checkedIdArray.filter(id => currentId.id != id)
|
||||||
// }, [checkedIdArray, state.activeIndex])
|
// }, [checkedIdArray, state.activeIndex])
|
||||||
|
|
||||||
const currentSelectedVideoIds = useMemo(()=>{
|
|
||||||
return checkedIdArray.length == 0 ? [] : checkedIdArray.filter(id => id != state.playId)
|
|
||||||
},[checkedIdArray, state.playId])
|
|
||||||
|
|
||||||
return (<div className="container py-5 page-live">
|
return (<div className="container py-5 page-live">
|
||||||
<div className="h-[36px]"></div>
|
<div className="h-[36px]"></div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="video-player-container mr-16 flex items-center">
|
<div className="video-player-container mr-16 flex ">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-center text-base text-gray-400">{formatDuration(totalDuration)}</div>
|
<div className="text-center text-base text-gray-400">{formatDuration(totalDuration)}</div>
|
||||||
<div className="video-player flex justify-center flex-1 mt-1">
|
<div className="video-player flex justify-center flex-1 mt-1">
|
||||||
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
|
<div className="live-player relative rounded overflow-hidden w-[420px] h-[740px]"
|
||||||
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
|
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
|
||||||
<Player
|
<Player
|
||||||
ref={player} className="w-[360px] h-[636px] bg-white"
|
ref={player} className="w-[420px] h-[740px] bg-white"
|
||||||
muted={true}
|
muted={true}
|
||||||
onProgress={(progress) => {
|
onProgress={(progress) => {
|
||||||
setState({playProgress: progress})
|
setState({playProgress: progress})
|
||||||
@ -259,21 +204,7 @@ export default function LiveIndex() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className={'flex items-center text-gray-400 cursor-pointer select-none'}
|
|
||||||
onClick={handleConfirm}>
|
|
||||||
<span>{editable ? t('live.edit_unlock') : t('live.edit_locked')}</span>
|
|
||||||
<span className="ml-2 text-sm">
|
|
||||||
{editable ? <IconUnlock/> : <IconLocked/>}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="check-all ml-10">
|
|
||||||
<button disabled={editable} className={`${editable?'':'hover:text-blue-300'} text-gray-400`}
|
|
||||||
onClick={handleAllCheckedChange}>
|
|
||||||
<span className="text-sm mr-2 whitespace-nowrap">{t('select.select_all')}</span>
|
|
||||||
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
|
|
||||||
</button>
|
|
||||||
<Checkbox disabled={editable} checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="list-header">
|
<div className="list-header">
|
||||||
@ -282,7 +213,6 @@ export default function LiveIndex() {
|
|||||||
<div className="col cover">{t('video.title_thumb')}</div>
|
<div className="col cover">{t('video.title_thumb')}</div>
|
||||||
<div className="col title">{t('video.title')}</div>
|
<div className="col title">{t('video.title')}</div>
|
||||||
<div className="col generated-time">{t('video.title_generated_time')}</div>
|
<div className="col generated-time">{t('video.title_generated_time')}</div>
|
||||||
<div className="col operation">{t('video.title_operation')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="">
|
||||||
@ -296,18 +226,6 @@ export default function LiveIndex() {
|
|||||||
>
|
>
|
||||||
{videoData.length == 0 && <div className="m-auto py-16"><Empty/></div>}
|
{videoData.length == 0 && <div className="m-auto py-16"><Empty/></div>}
|
||||||
<div className="sort-list-container flex-1">
|
<div className="sort-list-container flex-1">
|
||||||
<DndContext onDragEnd={(e) => {
|
|
||||||
const {active, over} = e;
|
|
||||||
if (over && active.id !== over.id) {
|
|
||||||
let oldIndex = -1, newIndex = -1;
|
|
||||||
setVideoData((items) => {
|
|
||||||
oldIndex = items.findIndex(s => s.id == active.id);
|
|
||||||
newIndex = items.findIndex(s => s.id == over.id);
|
|
||||||
return arrayMove(items, oldIndex, newIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<SortableContext items={videoData}>
|
|
||||||
{videoData.map((v, index) => (
|
{videoData.map((v, index) => (
|
||||||
<VideoListItem
|
<VideoListItem
|
||||||
video={v}
|
video={v}
|
||||||
@ -316,8 +234,9 @@ export default function LiveIndex() {
|
|||||||
key={index}
|
key={index}
|
||||||
active={state.playId == v.id}
|
active={state.playId == v.id}
|
||||||
playing={state.playId == v.id}
|
playing={state.playId == v.id}
|
||||||
className={`list-index-${index} list-item-${v.id} mt-3 mb-2`}
|
className={`list-index-${index} list-item-${v.id} video-item-${v.id} mt-3 mb-2`}
|
||||||
checked={checkedIdArray.includes(v.id)}
|
checked={checkedIdArray.includes(v.id)}
|
||||||
|
operationRender={<></>}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
const newIdArray = checked ? checkedIdArray.concat(v.id) : checkedIdArray.filter(id => id != v.id);
|
const newIdArray = checked ? checkedIdArray.concat(v.id) : checkedIdArray.filter(id => id != v.id);
|
||||||
setState({checkedAll: newIdArray.length == videoData.length})
|
setState({checkedAll: newIdArray.length == videoData.length})
|
||||||
@ -326,36 +245,14 @@ export default function LiveIndex() {
|
|||||||
// return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
// return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
||||||
// })
|
// })
|
||||||
}}
|
}}
|
||||||
onRemove={() => processDeleteVideo([v.id])}
|
editable={false}
|
||||||
editable={!editable && state.playId != v.id}
|
sortable={false}
|
||||||
sortable={editable && state.playId != v.id}
|
|
||||||
/>))}
|
/>))}
|
||||||
</SortableContext>
|
|
||||||
</DndContext>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[100px]"></div>
|
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-action">
|
|
||||||
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
|
||||||
{currentSelectedVideoIds.length > 0 && <ButtonBatch
|
|
||||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
|
||||||
selected={currentSelectedVideoIds}
|
|
||||||
emptyMessage={t('video.delete_empty')}
|
|
||||||
confirmMessage={currentSelectedVideoIds.length > 1?
|
|
||||||
t('video.delete_description_count',{count:currentSelectedVideoIds.length})
|
|
||||||
:
|
|
||||||
t('video.delete_description',{count:currentSelectedVideoIds.length})}
|
|
||||||
onSuccess={loadList}
|
|
||||||
onProcess={processDeleteVideo}
|
|
||||||
>
|
|
||||||
<span className={'text'}>{t('delete_batch')}</span>
|
|
||||||
<IconDelete/>
|
|
||||||
</ButtonBatch>}
|
|
||||||
</div>
|
|
||||||
{contextHolder}
|
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
@ -1,29 +1,31 @@
|
|||||||
import {Checkbox, Empty, Space} from "antd";
|
import {Empty} from "antd";
|
||||||
import React, {useEffect, useMemo, useRef, useState} from "react";
|
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
||||||
import {DndContext} from "@dnd-kit/core";
|
import {useGetState, useSetState} from "ahooks";
|
||||||
import {arrayMove, SortableContext} from "@dnd-kit/sortable";
|
|
||||||
import {useSetState} from "ahooks";
|
|
||||||
|
|
||||||
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||||
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
import {getList, VideoStatus} from "@/service/api/video.ts";
|
||||||
import {deleteFromList, getList, modifyOrder, regenerateById, VideoStatus} from "@/service/api/video.ts";
|
|
||||||
import {formatDuration} from "@/util/strings.ts";
|
import {formatDuration} from "@/util/strings.ts";
|
||||||
import ButtonBatch from "@/components/button-batch.tsx";
|
import {showErrorToast} from "@/components/message.ts";
|
||||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
|
||||||
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
||||||
import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
|
|
||||||
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
||||||
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
|
||||||
import {IconDelete} from "@/components/icons";
|
|
||||||
import {useLocation, useNavigate} from "react-router-dom";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useLastState} from "@/hooks/useLastState.ts";
|
||||||
|
|
||||||
|
function isFullyInViewport(ele: HTMLElement, container: HTMLElement) {
|
||||||
|
return ele.offsetTop >= container.scrollTop && ele.offsetTop + ele.offsetHeight <= container.scrollTop + container.offsetHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalVideoList(list: VideoInfo[]) {
|
||||||
|
return list?.filter(s => {
|
||||||
|
return s.status != VideoStatus.Generating && s.oss_video_url
|
||||||
|
}) || []
|
||||||
|
}
|
||||||
|
|
||||||
export default function VideoIndex() {
|
export default function VideoIndex() {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const [editId, setEditId] = useState(-1)
|
|
||||||
const loc = useLocation()
|
const [videoData, setVideoData, getLastVideoList] = useGetState<VideoInfo[]>([])
|
||||||
const navigate = useNavigate()
|
|
||||||
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
|
||||||
const player = useRef<PlayerInstance | null>(null)
|
const player = useRef<PlayerInstance | null>(null)
|
||||||
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
||||||
const [state, setState] = useSetState({
|
const [state, setState] = useSetState({
|
||||||
@ -38,11 +40,13 @@ export default function VideoIndex() {
|
|||||||
loading: false,
|
loading: false,
|
||||||
playVideoUrl: ''
|
playVideoUrl: ''
|
||||||
})
|
})
|
||||||
|
const {lastState} = useLastState(state);
|
||||||
|
|
||||||
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
|
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
|
||||||
const [refreshTimer, setTimer] = useState(0)
|
const [refreshTimer, setTimer] = useState(0)
|
||||||
|
|
||||||
// 加载列表
|
// 加载列表
|
||||||
const loadList = (needReset = true) => {
|
const loadList = (needReset = true, onLoad = false) => {
|
||||||
if (state.loading) return;
|
if (state.loading) return;
|
||||||
if (refreshTimer) {
|
if (refreshTimer) {
|
||||||
clearTimeout(refreshTimer)
|
clearTimeout(refreshTimer)
|
||||||
@ -61,6 +65,12 @@ export default function VideoIndex() {
|
|||||||
// 每5s重新获取一次最新数据
|
// 每5s重新获取一次最新数据
|
||||||
setTimer(() => setTimeout(() => loadList(false), 5000) as any);
|
setTimer(() => setTimeout(() => loadList(false), 5000) as any);
|
||||||
}
|
}
|
||||||
|
if(onLoad){
|
||||||
|
const _list = getNormalVideoList(list)
|
||||||
|
if(_list.length > 0){
|
||||||
|
playVideo(_list[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
}).catch(showErrorToast)
|
}).catch(showErrorToast)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setState({loading: false})
|
setState({loading: false})
|
||||||
@ -76,47 +86,27 @@ export default function VideoIndex() {
|
|||||||
|
|
||||||
// 播放视频
|
// 播放视频
|
||||||
const playVideo = (video: VideoInfo) => {
|
const playVideo = (video: VideoInfo) => {
|
||||||
console.log('play video',video)
|
|
||||||
if (state.playingId == video.id) {
|
if (state.playingId == video.id) {
|
||||||
player.current?.pause();
|
player.current?.pause();
|
||||||
setState({playingId: -1})
|
setState({playingId: -1})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (video.status == VideoStatus.Generating) 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) {
|
if (video.oss_video_url && video.status !== 1) {
|
||||||
setState({playingId: video.id})
|
setState({playingId: video.id})
|
||||||
player.current?.play(video.oss_video_url, 0)
|
player.current?.play(video.oss_video_url, 0)
|
||||||
|
const videoElement = document.querySelector(`.video-item-${video.id}`) as HTMLElement
|
||||||
|
const scroller = document.querySelector('.data-list-container') as HTMLElement;
|
||||||
|
if(videoElement && isFullyInViewport(videoElement, scroller)) {
|
||||||
|
videoElement.scrollIntoView({behavior: 'smooth'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理全选
|
|
||||||
const handleAllCheckedChange = () => {
|
|
||||||
setCheckedIdArray(state.checkedAll ? [] : videoData.filter(s=>s.status == VideoStatus.Generated).map(v => v.id))
|
|
||||||
setState({
|
|
||||||
checkedAll: !state.checkedAll
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const handleModifySort = (items: VideoInfo[]) => {
|
|
||||||
|
|
||||||
modifyOrder(items.map(s => s.id)).then(() => {
|
useEffect(() => {
|
||||||
showToast(t('video.sort_modify_success'), 'success')
|
loadList(true, true)
|
||||||
}).catch(() => {
|
}, [])
|
||||||
loadList();
|
|
||||||
showToast(t('video.sort_modify_failed'), 'warning')
|
|
||||||
})
|
|
||||||
|
|
||||||
return ()=>{
|
|
||||||
try{
|
|
||||||
Array.from(document.querySelectorAll('video')).forEach(v => v.pause())
|
|
||||||
}catch (e){
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
useEffect(loadList, [])
|
|
||||||
// const totalDuration = useMemo(() => {
|
// const totalDuration = useMemo(() => {
|
||||||
// if (!videoData || videoData.length == 0) return 0;
|
// if (!videoData || videoData.length == 0) return 0;
|
||||||
// const v = state.playingId == -1 ? null : videoData.find(s => s.id == state.playingId)
|
// const v = state.playingId == -1 ? null : videoData.find(s => s.id == state.playingId)
|
||||||
@ -127,48 +117,37 @@ export default function VideoIndex() {
|
|||||||
// //return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
|
// //return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
|
||||||
// }, [videoData, state.playingId])
|
// }, [videoData, state.playingId])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loc.state == 'push-success' && !state.showStatePos && videoData.length && scrollerRef.current) {
|
const handlePlayEnd = () => {
|
||||||
const generatingItem = document.querySelector(`.list-item-state-${VideoStatus.Generating}`)
|
const list = getLastVideoList();
|
||||||
if (generatingItem) {
|
if (!list?.length) return;
|
||||||
generatingItem.scrollIntoView({behavior: 'smooth'})
|
const _list = getNormalVideoList(list)
|
||||||
setState({showStatePos: true})
|
if (_list.length > 0) {
|
||||||
|
const _currentIndex = _list.findIndex(s => s.id == lastState.current.playingId)
|
||||||
|
const _next = _currentIndex != -1 && _currentIndex < _list.length - 1 ? _list[_currentIndex + 1] : _list[0];
|
||||||
|
playVideo(_next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [videoData, scrollerRef])
|
|
||||||
const processDeleteVideo = async (ids: Id[],action ?: string) => {
|
|
||||||
deleteFromList(ids).then(() => {
|
|
||||||
showToast(t('delete_success'), 'success')
|
|
||||||
if(action == 'rollback'){
|
|
||||||
navigate('/edit',{
|
|
||||||
state: {action: 'rollback',id: ids[0]},
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
}).catch(showErrorToast)
|
|
||||||
}
|
|
||||||
const processGenerateVideo = async (video: VideoInfo) => {
|
|
||||||
regenerateById(video.article_id).then(() => {
|
|
||||||
//showToast(t('delete_success'), 'success')
|
|
||||||
loadList()
|
|
||||||
}).catch(showErrorToast)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<div className="container py-5 page-live">
|
return (<div className="container py-5 page-live">
|
||||||
<div className="h-[36px]"></div>
|
<div className="h-[36px]"></div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="video-player-container mr-16 w-[360px] flex items-center">
|
<div className="video-player-container mr-16 flex items-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-center text-base text-gray-400">{t("generating.title")}</div>
|
<div className="text-center text-base text-gray-400">{t("generating.title")}</div>
|
||||||
<div className="video-player flex items-center mt-2">
|
<div className="video-player flex items-center mt-2">
|
||||||
<div className=" w-[360px] h-[636px] rounded overflow-hidden">
|
<div className=" w-[420px] h-[740px] rounded overflow-hidden">
|
||||||
{/*videoData[state.playingIndex]?.oss_video_url*/}
|
|
||||||
<Player
|
<Player
|
||||||
ref={player}
|
ref={player}
|
||||||
url={state.playVideoUrl}
|
url={state.playVideoUrl}
|
||||||
onChange={(state) => {
|
showControls={true}
|
||||||
if (state.end || state.error) setState({playingId: -1})
|
onChange={(_state) => {
|
||||||
|
console.log('onChange', _state)
|
||||||
|
if (_state.end) {
|
||||||
|
handlePlayEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_state.error) setState({playingId: -1})
|
||||||
}}
|
}}
|
||||||
onProgress={(current, duration) => {
|
onProgress={(current, duration) => {
|
||||||
setState({
|
setState({
|
||||||
@ -178,26 +157,18 @@ export default function VideoIndex() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className="w-[360px] h-[640px] bg-white"/>
|
className="w-[420px] h-[740px] bg-white"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center text-sm mt-4 text-gray-400">{formatDuration(state.playState.current)} / {formatDuration(state.playState.total)}</div>
|
<div
|
||||||
|
className="text-center text-sm mt-4 text-gray-400">{formatDuration(state.playState.current)} / {formatDuration(state.playState.total)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="video-list-container rounded mt-2 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="live-control flex justify-between">
|
||||||
<div className="pl-[70px]"></div>
|
<div className="pl-[70px]"></div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Space size={20}>
|
|
||||||
<span>{t('select.total',{count:videoData.length || 0})}</span>
|
|
||||||
<span className={'text-blue-500'}>{t('select.selected_some',{count:checkedIdArray.length})}</span>
|
|
||||||
</Space>
|
|
||||||
<button className="hover:text-blue-300 text-gray-400 ml-5"
|
|
||||||
onClick={handleAllCheckedChange}>
|
|
||||||
<span className="text-sm mr-2">{t("select.select_all")}</span>
|
|
||||||
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
|
|
||||||
</button>
|
|
||||||
<Checkbox checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'video-list-sort-container flex-1 mt-1'}>
|
<div className={'video-list-sort-container flex-1 mt-1'}>
|
||||||
@ -207,37 +178,12 @@ export default function VideoIndex() {
|
|||||||
<div className="col cover">{t('video.title_thumb')}</div>
|
<div className="col cover">{t('video.title_thumb')}</div>
|
||||||
<div className="col title">{t('video.title')}</div>
|
<div className="col title">{t('video.title')}</div>
|
||||||
<div className="col generated-time">{t('video.title_generated_time')}</div>
|
<div className="col generated-time">{t('video.title_generated_time')}</div>
|
||||||
<div className="col operation">{t('video.title_operation')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InfiniteScroller loading={state.loading} 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 py-16"><Empty/></div> :
|
videoData.length == 0 ? <div className="m-auto py-16"><Empty/></div> :
|
||||||
<div className="sort-list-container flex-1">
|
<div className="sort-list-container flex-1">
|
||||||
<DndContext onDragEnd={(e) => {
|
|
||||||
const {active, over} = e;
|
|
||||||
if (over && active.id !== over.id) {
|
|
||||||
let oldIndex = -1, newIndex = -1;
|
|
||||||
const originArr = [...videoData]
|
|
||||||
console.log(originArr.map(s => s.id))
|
|
||||||
setVideoData((items) => {
|
|
||||||
oldIndex = items.findIndex(s => s.id == active.id);
|
|
||||||
newIndex = items.findIndex(s => s.id == over.id);
|
|
||||||
const newSorts = arrayMove(items, oldIndex, newIndex);
|
|
||||||
handleModifySort(newSorts)
|
|
||||||
return newSorts;
|
|
||||||
});
|
|
||||||
// modal.confirm({
|
|
||||||
// title: '提示',
|
|
||||||
// content: '是否要移动到指定位置',
|
|
||||||
// onOk: handleModifySort,
|
|
||||||
// onCancel: () => {
|
|
||||||
// setVideoData(originArr);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<SortableContext items={videoData}>
|
|
||||||
{videoData.map((v, index) => (
|
{videoData.map((v, index) => (
|
||||||
<VideoListItem
|
<VideoListItem
|
||||||
video={v}
|
video={v}
|
||||||
@ -248,7 +194,7 @@ export default function VideoIndex() {
|
|||||||
active={checkedIdArray.includes(v.id)}
|
active={checkedIdArray.includes(v.id)}
|
||||||
playing={state.playingId == v.id}
|
playing={state.playingId == v.id}
|
||||||
checked={checkedIdArray.includes(v.id)}
|
checked={checkedIdArray.includes(v.id)}
|
||||||
className={`list-item-${index} mt-3 mb-2 list-item-state-${v.status} `}
|
className={`list-item-${index} video-item-${v.id} mt-3 mb-2 list-item-state-${v.status} `}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
setCheckedIdArray(idArray => {
|
setCheckedIdArray(idArray => {
|
||||||
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
||||||
@ -257,52 +203,22 @@ export default function VideoIndex() {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onItemClick={() => playVideo(v)}
|
onItemClick={() => playVideo(v)}
|
||||||
onRemove={(action) => processDeleteVideo([v.id],action)}
|
operationRender={<></>}
|
||||||
onEdit={v.status == VideoStatus.Generated ? () => {
|
onEdit={undefined}
|
||||||
setEditId(v.article_id)
|
|
||||||
}:undefined}
|
|
||||||
onRegenerate={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated ? () => {
|
onRegenerate={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated ? () => {
|
||||||
processGenerateVideo(v)
|
|
||||||
} : undefined}
|
} : undefined}
|
||||||
hideCheckBox={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated}
|
hideCheckBox={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated}
|
||||||
editable={v.status != VideoStatus.Generating}
|
editable={v.status != VideoStatus.Generating}
|
||||||
sortable={v.status == VideoStatus.Generated}
|
sortable={v.status == VideoStatus.Generated}
|
||||||
/>))}
|
/>))}
|
||||||
</SortableContext>
|
|
||||||
</DndContext>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className="h-[130px]"></div>
|
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-action">
|
<div className="page-action">
|
||||||
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
||||||
{checkedIdArray.length > 0 && <ButtonBatch
|
|
||||||
onProcess={deleteFromList}
|
|
||||||
selected={checkedIdArray}
|
|
||||||
emptyMessage={t('video.delete_empty')}
|
|
||||||
title={
|
|
||||||
checkedIdArray.length == 1 ? t('video.delete_description',{count:checkedIdArray.length}):
|
|
||||||
t('video.delete_description_count',{count:checkedIdArray.length})
|
|
||||||
}
|
|
||||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
|
||||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
|
||||||
__html:checkedIdArray.length == 1?
|
|
||||||
t('video.delete_confirm',{count:checkedIdArray.length}):
|
|
||||||
t('video.delete_confirm_count',{count:checkedIdArray.length})
|
|
||||||
}}></span>}
|
|
||||||
onSuccess={() => {
|
|
||||||
showToast(t('delete_success'), 'success')
|
|
||||||
loadList()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text">{t('delete_batch')}</span>
|
|
||||||
<IconDelete/>
|
|
||||||
</ButtonBatch>}
|
|
||||||
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ArticleEditModal type={'video'} id={editId} onClose={() => setEditId(-1)}/>
|
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
@ -5,18 +5,17 @@ import zhCN from 'antd/locale/zh_CN';
|
|||||||
// for date-picker i18n
|
// for date-picker i18n
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
import ErrorBoundary from "./error.tsx";
|
|
||||||
import Loader from "@/components/loader.tsx";
|
import Loader from "@/components/loader.tsx";
|
||||||
import routes from "@/routes/routes.tsx";
|
import routes from "@/routes/routes.tsx";
|
||||||
import {DocumentTitle} from "@/components/document.tsx";
|
import {DocumentTitle} from "@/components/document.tsx";
|
||||||
import useConfig from "@/hooks/useConfig.ts";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import useGlobalConfig from "@/hooks/useGlobalConfig.ts";
|
import useGlobalConfig from "@/hooks/useGlobalConfig.ts";
|
||||||
|
import VideoIndex from "@/pages/video";
|
||||||
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
...routes,
|
...routes,
|
||||||
{path: '*', element: <ErrorBoundary errorCode={404}/>}
|
{path: '*', element: <VideoIndex />}
|
||||||
], {
|
], {
|
||||||
basename: import.meta.env.VITE_APP_BASE_NAME,
|
basename: import.meta.env.VITE_APP_BASE_NAME,
|
||||||
future: {
|
future: {
|
||||||
|
@ -1,81 +1,19 @@
|
|||||||
import {Outlet, useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
import {Outlet, useSearchParams} from "react-router-dom";
|
||||||
import {Button, Divider, Dropdown, MenuProps} from "antd";
|
import {Button} from "antd";
|
||||||
import React, {useEffect} from "react";
|
import React from "react";
|
||||||
|
|
||||||
import AuthGuard from "@/routes/layout/auth-guard.tsx";
|
import AuthGuard from "@/routes/layout/auth-guard.tsx";
|
||||||
import {LogoText} from "@/components/icons/logo.tsx";
|
import {LogoText} from "@/components/icons/logo.tsx";
|
||||||
|
|
||||||
import {UserAvatar} from "@/components/icons/user-avatar.tsx";
|
|
||||||
import {DashboardNavigation} from "@/routes/layout/dashboard-navigation.tsx";
|
import {DashboardNavigation} from "@/routes/layout/dashboard-navigation.tsx";
|
||||||
|
|
||||||
import useAuth from "@/hooks/useAuth.ts";
|
|
||||||
import {hidePhone} from "@/util/strings.ts";
|
|
||||||
import {defaultCache} from "@/hooks/useCache.ts";
|
import {defaultCache} from "@/hooks/useCache.ts";
|
||||||
import {IconVideo} from "@/components/icons";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import useConfig from "@/hooks/useConfig.ts";
|
|
||||||
|
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavigationUserContainer = () => {
|
|
||||||
const {t } = useTranslation()
|
|
||||||
const {logout, user} = useAuth()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const handleLogout = ()=>{
|
|
||||||
logout().then(() => navigate('/user'))
|
|
||||||
}
|
|
||||||
const items: MenuProps['items'] = [
|
|
||||||
{
|
|
||||||
key: 'profile',
|
|
||||||
label: <div className="nav-item" onClick={() => navigate('/history')}>
|
|
||||||
<IconVideo />
|
|
||||||
<span className={"nav-text"}>{t('history.text')}</span>
|
|
||||||
</div>,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// key: 'logout',
|
|
||||||
// label: <div onClick={handleLogout}>退出</div>,
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
const UserButton = () => (<div
|
|
||||||
className={`flex items-center rounded-3xl ${user ? 'bg-[#e3eeff]' : 'bg-primary-blue'} p-1 pr-2 cursor-pointer rounded`}>
|
|
||||||
<UserAvatar className="user-avatar size-7"/>
|
|
||||||
{user ? <span className={"username ml-2 text-sm"}>{hidePhone(user.nickname)}</span> : (
|
|
||||||
<span className="text-sm mx-2 text-white">{t('login.title')}</span>
|
|
||||||
)}
|
|
||||||
</div>)
|
|
||||||
return (<div className={"flex items-center justify-between gap-2 ml-10"}>
|
|
||||||
{user ? <Dropdown
|
|
||||||
rootClassName={'z-[999999] userinfo-drop-menu'}
|
|
||||||
menu={{items}} placement="bottomRight"
|
|
||||||
dropdownRender={(menu)=>(
|
|
||||||
<div>
|
|
||||||
<div className="user-profile flex gap-4">
|
|
||||||
<div className="avatar"><UserAvatar className="user-avatar"/></div>
|
|
||||||
<div className="info">
|
|
||||||
<div>{user?.nickname}</div>
|
|
||||||
<div>ID: {user?.id}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Divider style={{ margin: 0 }} />
|
|
||||||
<div className="menu-list-container">
|
|
||||||
{menu}
|
|
||||||
</div>
|
|
||||||
<Divider style={{ margin: 0 }} />
|
|
||||||
<div className="logout">
|
|
||||||
<div onClick={handleLogout}>{t('user.logout')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div><UserButton/></div>
|
|
||||||
</Dropdown> : <UserButton/>}
|
|
||||||
</div>)
|
|
||||||
}
|
|
||||||
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||||
const {i18n} = useTranslation();
|
const {i18n} = useTranslation();
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
@ -96,7 +34,6 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>}
|
</div>}
|
||||||
<NavigationUserContainer/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-content flex-1 box-sizing">
|
<div className="app-content flex-1 box-sizing">
|
||||||
@ -110,14 +47,6 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
|||||||
|
|
||||||
|
|
||||||
const DashboardLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
|
const DashboardLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
|
||||||
const loc = useLocation()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
useEffect(()=>{
|
|
||||||
if(!defaultCache.firstLoadPath && loc.pathname == '/live'){
|
|
||||||
defaultCache.firstLoadPath = loc.pathname;
|
|
||||||
navigate('/')
|
|
||||||
}
|
|
||||||
},[])
|
|
||||||
return <AuthGuard>
|
return <AuthGuard>
|
||||||
<div className="fixed">{defaultCache.firstLoadPath}</div>
|
<div className="fixed">{defaultCache.firstLoadPath}</div>
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
@ -11,35 +11,11 @@ export function DashboardNavigation() {
|
|||||||
const {t,i18n} = useTranslation()
|
const {t,i18n} = useTranslation()
|
||||||
const {user} = useAuth()
|
const {user} = useAuth()
|
||||||
const NavItems = useMemo(()=>([
|
const NavItems = useMemo(()=>([
|
||||||
{
|
|
||||||
key: 'news',
|
|
||||||
name: t('nav.materials'),
|
|
||||||
icon: 'news',
|
|
||||||
path: '/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'video',
|
|
||||||
name: t('nav.editing'),
|
|
||||||
icon: 'e',
|
|
||||||
path: '/edit'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'create',
|
|
||||||
name: t('nav.generating'),
|
|
||||||
icon: 'ai',
|
|
||||||
path: '/create'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// key: 'library',
|
|
||||||
// name: '视频库',
|
|
||||||
// icon: '+',
|
|
||||||
// path:'/library'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
key: 'live',
|
key: 'live',
|
||||||
name: t('nav.live'),
|
name: t('nav.live'),
|
||||||
icon: 'v',
|
icon: 'v',
|
||||||
path: '/live'
|
path: '/'
|
||||||
}
|
}
|
||||||
]),[i18n.language])
|
]),[i18n.language])
|
||||||
return (<div className={'flex app-main-navigation'}>
|
return (<div className={'flex app-main-navigation'}>
|
||||||
|
@ -1,49 +1,13 @@
|
|||||||
import {RouteObject} from "react-router-dom";
|
import {RouteObject} from "react-router-dom";
|
||||||
import ErrorBoundary from "@/routes/error.tsx";
|
import ErrorBoundary from "@/routes/error.tsx";
|
||||||
|
|
||||||
;
|
|
||||||
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import VideoIndex from "@/pages/video";
|
||||||
const UserAuth = React.lazy(() => import("@/pages/user"))
|
|
||||||
const CreateVideoIndex = React.lazy(() => import("@/pages/video"))
|
|
||||||
const LibraryIndex = React.lazy(() => import("@/pages/library"))
|
|
||||||
const LiveIndex = React.lazy(() => import("@/pages/live"))
|
|
||||||
const NewsIndex = React.lazy(() => import("@/pages/news"))
|
|
||||||
const NewsEdit = React.lazy(() => import("@/pages/news/edit.tsx"))
|
|
||||||
|
|
||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <DashboardLayout/>,
|
element: <VideoIndex/>,
|
||||||
errorElement: <ErrorBoundary/>,
|
errorElement: <ErrorBoundary/>,
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
element: <NewsIndex/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'user',
|
|
||||||
element: <UserAuth/>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'edit',
|
|
||||||
element: <NewsEdit/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
element: <CreateVideoIndex/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'history',
|
|
||||||
element: <LibraryIndex/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'live',
|
|
||||||
element: <LiveIndex/>
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
export default routes
|
export default routes
|
Loading…
x
Reference in New Issue
Block a user