forked from 77media/video-flow
优化细节
This commit is contained in:
parent
e0fbbb0f1e
commit
9dc168f22e
@ -151,7 +151,7 @@ export function HomePage2() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Create Project Button */}
|
{/* Create Project Button */}
|
||||||
<div className="fixed bottom-[5rem] left-[50%] -translate-x-1/2 z-50">
|
<div className="fixed bottom-[3rem] left-[50%] -translate-x-1/2 z-50">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
whileHover={!isCreating ? { scale: 1.05 } : {}}
|
whileHover={!isCreating ? { scale: 1.05 } : {}}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
width: 190px;
|
width: 8rem;
|
||||||
height: 128px;
|
height: 7rem;
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
}
|
}
|
||||||
@ -49,9 +49,12 @@ export function MediaViewer({
|
|||||||
// 最终视频控制状态
|
// 最终视频控制状态
|
||||||
const [isFinalVideoPlaying, setIsFinalVideoPlaying] = useState(true);
|
const [isFinalVideoPlaying, setIsFinalVideoPlaying] = useState(true);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [finalVideoReady, setFinalVideoReady] = useState(false);
|
||||||
|
const [userHasInteracted, setUserHasInteracted] = useState(false);
|
||||||
|
|
||||||
// 音量控制函数
|
// 音量控制函数
|
||||||
const toggleMute = () => {
|
const toggleMute = () => {
|
||||||
|
setUserHasInteracted(true);
|
||||||
setIsMuted(!isMuted);
|
setIsMuted(!isMuted);
|
||||||
if (mainVideoRef.current) {
|
if (mainVideoRef.current) {
|
||||||
mainVideoRef.current.muted = !isMuted;
|
mainVideoRef.current.muted = !isMuted;
|
||||||
@ -62,6 +65,7 @@ export function MediaViewer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleVolumeChange = (newVolume: number) => {
|
const handleVolumeChange = (newVolume: number) => {
|
||||||
|
setUserHasInteracted(true);
|
||||||
setVolume(newVolume);
|
setVolume(newVolume);
|
||||||
if (mainVideoRef.current) {
|
if (mainVideoRef.current) {
|
||||||
mainVideoRef.current.volume = newVolume;
|
mainVideoRef.current.volume = newVolume;
|
||||||
@ -79,20 +83,63 @@ export function MediaViewer({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (finalVideoRef.current && finalVideoReady) {
|
||||||
|
if (isFinalVideoPlaying) {
|
||||||
|
finalVideoRef.current.play().catch(error => {
|
||||||
|
console.log('最终视频自动播放被阻止:', error);
|
||||||
|
// 如果自动播放被阻止,将状态设置为暂停
|
||||||
|
setIsFinalVideoPlaying(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
finalVideoRef.current.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isFinalVideoPlaying, finalVideoReady]);
|
||||||
|
|
||||||
// 最终视频播放控制
|
// 最终视频播放控制
|
||||||
const toggleFinalVideoPlay = () => {
|
const toggleFinalVideoPlay = () => {
|
||||||
|
setUserHasInteracted(true);
|
||||||
|
setIsFinalVideoPlaying(!isFinalVideoPlaying);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理最终视频加载完成
|
||||||
|
const handleFinalVideoLoaded = () => {
|
||||||
if (finalVideoRef.current) {
|
if (finalVideoRef.current) {
|
||||||
|
setFinalVideoReady(true);
|
||||||
|
applyVolumeSettings(finalVideoRef.current);
|
||||||
|
|
||||||
|
// 如果当前状态是应该播放的,尝试播放
|
||||||
if (isFinalVideoPlaying) {
|
if (isFinalVideoPlaying) {
|
||||||
finalVideoRef.current.pause();
|
finalVideoRef.current.play().catch(error => {
|
||||||
} else {
|
console.log('最终视频自动播放被阻止:', error);
|
||||||
finalVideoRef.current.play();
|
setIsFinalVideoPlaying(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setIsFinalVideoPlaying(!isFinalVideoPlaying);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理视频点击 - 首次交互时尝试播放
|
||||||
|
const handleVideoClick = () => {
|
||||||
|
if (!userHasInteracted && finalVideoRef.current && finalVideoReady) {
|
||||||
|
setUserHasInteracted(true);
|
||||||
|
if (isFinalVideoPlaying) {
|
||||||
|
finalVideoRef.current.play().catch(error => {
|
||||||
|
console.log('视频播放失败:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 包装编辑按钮点击事件
|
||||||
|
const handleEditClick = (tab: string) => {
|
||||||
|
setUserHasInteracted(true);
|
||||||
|
onEditModalOpen(tab);
|
||||||
|
};
|
||||||
|
|
||||||
// 全屏控制
|
// 全屏控制
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
|
setUserHasInteracted(true);
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
// 进入全屏
|
// 进入全屏
|
||||||
if (finalVideoRef.current) {
|
if (finalVideoRef.current) {
|
||||||
@ -164,6 +211,17 @@ export function MediaViewer({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 组件卸载时清理视频状态
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
// 清理最终视频状态
|
||||||
|
setFinalVideoReady(false);
|
||||||
|
if (finalVideoRef.current) {
|
||||||
|
finalVideoRef.current.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 渲染音量控制组件
|
// 渲染音量控制组件
|
||||||
const renderVolumeControls = () => (
|
const renderVolumeControls = () => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -228,7 +286,6 @@ export function MediaViewer({
|
|||||||
<video
|
<video
|
||||||
className="w-full h-full rounded-lg object-cover object-center"
|
className="w-full h-full rounded-lg object-cover object-center"
|
||||||
src={taskVideos[currentSketchIndex]?.url}
|
src={taskVideos[currentSketchIndex]?.url}
|
||||||
autoPlay
|
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
muted
|
muted
|
||||||
@ -249,13 +306,13 @@ export function MediaViewer({
|
|||||||
ref={finalVideoRef}
|
ref={finalVideoRef}
|
||||||
className="w-full h-full object-cover rounded-lg"
|
className="w-full h-full object-cover rounded-lg"
|
||||||
src={finalVideo.url}
|
src={finalVideo.url}
|
||||||
poster={taskSketch[currentSketchIndex]?.url}
|
|
||||||
autoPlay={isFinalVideoPlaying}
|
autoPlay={isFinalVideoPlaying}
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
onLoadedData={() => applyVolumeSettings(finalVideoRef.current!)}
|
onLoadedData={handleFinalVideoLoaded}
|
||||||
onPlay={() => setIsFinalVideoPlaying(true)}
|
onPlay={() => setIsFinalVideoPlaying(true)}
|
||||||
onPause={() => setIsFinalVideoPlaying(false)}
|
onPause={() => setIsFinalVideoPlaying(false)}
|
||||||
|
onClick={handleVideoClick}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -272,7 +329,7 @@ export function MediaViewer({
|
|||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={Edit3}
|
icon={Edit3}
|
||||||
tooltip="Edit sketch"
|
tooltip="Edit sketch"
|
||||||
onClick={() => onEditModalOpen('4')}
|
onClick={() => handleEditClick('4')}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -506,7 +563,7 @@ export function MediaViewer({
|
|||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={Edit3}
|
icon={Edit3}
|
||||||
tooltip="Edit sketch"
|
tooltip="Edit sketch"
|
||||||
onClick={() => onEditModalOpen('3')}
|
onClick={() => handleEditClick('3')}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -692,7 +749,7 @@ export function MediaViewer({
|
|||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={Edit3}
|
icon={Edit3}
|
||||||
tooltip="Edit sketch"
|
tooltip="Edit sketch"
|
||||||
onClick={() => onEditModalOpen('1')}
|
onClick={() => handleEditClick('1')}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -737,20 +794,6 @@ export function MediaViewer({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 播放进度指示器 */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{isPlaying && (
|
|
||||||
<motion.div
|
|
||||||
className="absolute bottom-0 left-0 right-0 h-1 bg-blue-500/20"
|
|
||||||
initial={{ scaleX: 0 }}
|
|
||||||
animate={{ scaleX: 1 }}
|
|
||||||
exit={{ scaleX: 0 }}
|
|
||||||
transition={{ duration: 2, repeat: Infinity }}
|
|
||||||
style={{ transformOrigin: "left" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import {
|
||||||
|
Image,
|
||||||
|
Video,
|
||||||
|
CheckCircle,
|
||||||
|
Music,
|
||||||
|
Loader2,
|
||||||
|
User,
|
||||||
|
Scissors,
|
||||||
|
Tv
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
interface TaskInfoProps {
|
interface TaskInfoProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -10,7 +20,32 @@ interface TaskInfoProps {
|
|||||||
currentLoadingText: string;
|
currentLoadingText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据加载文本返回对应的图标
|
||||||
|
const getStageIcon = (loadingText: string) => {
|
||||||
|
const text = loadingText.toLowerCase();
|
||||||
|
|
||||||
|
if (text.includes('sketch')) {
|
||||||
|
return Image;
|
||||||
|
} else if (text.includes('video')) {
|
||||||
|
return Video;
|
||||||
|
} else if (text.includes('character')) {
|
||||||
|
return User;
|
||||||
|
} else if (text.includes('audio')) {
|
||||||
|
return Music;
|
||||||
|
} else if (text.includes('post')) {
|
||||||
|
return Scissors;
|
||||||
|
} else if (text.includes('final')) {
|
||||||
|
return Tv;
|
||||||
|
} else if (text.includes('complete')) {
|
||||||
|
return CheckCircle;
|
||||||
|
} else {
|
||||||
|
return Loader2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfoProps) {
|
export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfoProps) {
|
||||||
|
const StageIcon = getStageIcon(currentLoadingText);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -46,7 +81,7 @@ export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfo
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex items-center gap-1.5"
|
className="flex items-center gap-2"
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
variants={{
|
variants={{
|
||||||
@ -54,6 +89,16 @@ export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfo
|
|||||||
visible: { opacity: 1 }
|
visible: { opacity: 1 }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<motion.div
|
||||||
|
className="text-emerald-500"
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, scale: 0.8 },
|
||||||
|
visible: { opacity: 1, scale: 1 }
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
>
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
</motion.div>
|
||||||
<motion.span
|
<motion.span
|
||||||
className="text-emerald-500 font-medium"
|
className="text-emerald-500 font-medium"
|
||||||
variants={{
|
variants={{
|
||||||
@ -98,93 +143,117 @@ export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfo
|
|||||||
repeatDelay: 0.2
|
repeatDelay: 0.2
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 阶段图标 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative"
|
className="flex items-center gap-2"
|
||||||
key={currentLoadingText}
|
key={currentLoadingText}
|
||||||
initial={{ opacity: 0, x: -10 }}
|
initial={{ opacity: 0, x: -10 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
exit={{ opacity: 0, x: 10 }}
|
exit={{ opacity: 0, x: 10 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
{/* 背景发光效果 */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-cyan-400 to-purple-400 blur-sm"
|
className="text-blue-500"
|
||||||
animate={{
|
animate={{
|
||||||
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
rotate: [0, 360],
|
||||||
|
scale: [1, 1.1, 1]
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 2,
|
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
|
||||||
repeat: Infinity,
|
|
||||||
ease: "linear"
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundSize: "200% 200%",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="normalS400 subtitle-had8uE">{currentLoadingText}</span>
|
<StageIcon className="w-5 h-5" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* 主文字 - 颜色填充动画 */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative z-10"
|
className="relative"
|
||||||
animate={{
|
initial={{ opacity: 0, x: -10 }}
|
||||||
scale: [1, 1.02, 1],
|
animate={{ opacity: 1, x: 0 }}
|
||||||
}}
|
exit={{ opacity: 0, x: 10 }}
|
||||||
transition={{
|
transition={{ duration: 0.3 }}
|
||||||
duration: 1.5,
|
|
||||||
repeat: Infinity,
|
|
||||||
ease: "easeInOut"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<motion.span
|
{/* 背景发光效果 */}
|
||||||
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
|
<motion.div
|
||||||
|
className="absolute inset-0 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-cyan-400 to-purple-400 blur-sm"
|
||||||
animate={{
|
animate={{
|
||||||
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 3,
|
duration: 2,
|
||||||
repeat: Infinity,
|
repeat: Infinity,
|
||||||
ease: "linear"
|
ease: "linear"
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundSize: "300% 300%",
|
backgroundSize: "200% 200%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentLoadingText}
|
<span className="normalS400 subtitle-had8uE">{currentLoadingText}</span>
|
||||||
</motion.span>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 主文字 - 颜色填充动画 */}
|
||||||
|
<motion.div
|
||||||
|
className="relative z-10"
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.02, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
|
||||||
|
animate={{
|
||||||
|
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "linear"
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundSize: "300% 300%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentLoadingText}
|
||||||
|
</motion.span>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 动态光点效果 */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute left-0 top-1/2 transform -translate-y-1/2 w-2 h-2 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full blur-sm"
|
||||||
|
animate={{
|
||||||
|
x: [0, 200, 0],
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
scale: [0.5, 1, 0.5],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.5,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 文字底部装饰线 */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500"
|
||||||
|
animate={{
|
||||||
|
width: ["0%", "100%", "0%"],
|
||||||
|
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
width: { duration: 2, repeat: Infinity, ease: "easeInOut" },
|
||||||
|
backgroundPosition: { duration: 1.5, repeat: Infinity, ease: "linear" }
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundSize: "200% 200%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* 动态光点效果 */}
|
|
||||||
<motion.div
|
|
||||||
className="absolute left-0 top-1/2 transform -translate-y-1/2 w-2 h-2 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full blur-sm"
|
|
||||||
animate={{
|
|
||||||
x: [0, 200, 0],
|
|
||||||
opacity: [0, 1, 0],
|
|
||||||
scale: [0.5, 1, 0.5],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 2.5,
|
|
||||||
repeat: Infinity,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 文字底部装饰线 */}
|
|
||||||
<motion.div
|
|
||||||
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500"
|
|
||||||
animate={{
|
|
||||||
width: ["0%", "100%", "0%"],
|
|
||||||
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
width: { duration: 2, repeat: Infinity, ease: "easeInOut" },
|
|
||||||
backgroundPosition: { duration: 1.5, repeat: Infinity, ease: "linear" }
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundSize: "200% 200%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-1.5 h-1.5 rounded-full bg-blue-500"
|
className="w-1.5 h-1.5 rounded-full bg-blue-500"
|
||||||
animate={{
|
animate={{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { getRandomMockData, STEP_MESSAGES } from '@/components/work-flow/constants';
|
import { getRandomMockData, STEP_MESSAGES, MOCK_DELAY_TIME } from '@/components/work-flow/constants';
|
||||||
|
|
||||||
// 当前选择的mock数据
|
// 当前选择的mock数据
|
||||||
let selectedMockData = getRandomMockData();
|
let selectedMockData = getRandomMockData();
|
||||||
@ -9,6 +9,7 @@ let selectedMockData = getRandomMockData();
|
|||||||
export function useWorkflowData() {
|
export function useWorkflowData() {
|
||||||
const [taskObject, setTaskObject] = useState<any>(null);
|
const [taskObject, setTaskObject] = useState<any>(null);
|
||||||
const [taskSketch, setTaskSketch] = useState<any[]>([]);
|
const [taskSketch, setTaskSketch] = useState<any[]>([]);
|
||||||
|
const [taskRoles, setTaskRoles] = useState<any[]>([]);
|
||||||
const [taskVideos, setTaskVideos] = useState<any[]>([]);
|
const [taskVideos, setTaskVideos] = useState<any[]>([]);
|
||||||
const [sketchCount, setSketchCount] = useState(0);
|
const [sketchCount, setSketchCount] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -49,7 +50,7 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
// 模拟分批获取分镜草图
|
// 模拟分批获取分镜草图
|
||||||
for (let i = 0; i < totalSketches; i++) {
|
for (let i = 0; i < totalSketches; i++) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000)); // 10s
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.sketch)); // 10s
|
||||||
|
|
||||||
const newSketch = {
|
const newSketch = {
|
||||||
id: `sketch-${i}`,
|
id: `sketch-${i}`,
|
||||||
@ -76,17 +77,37 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
// 模拟接口请求 每次获取一个角色 轮询获取
|
// 模拟接口请求 每次获取一个角色 轮询获取
|
||||||
const getTaskRole = async (taskId: string) => {
|
const getTaskRole = async (taskId: string) => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000 * selectedMockData.roles.length)); // 延长到30秒
|
setTaskRoles([]);
|
||||||
|
const roleData = selectedMockData.roles;
|
||||||
|
const totalRoles = roleData.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < totalRoles; i++) {
|
||||||
|
// 先更新loading文字显示当前正在生成的角色
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.newCharacter(i, totalRoles));
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.character)); // 2s 一个角色
|
||||||
|
|
||||||
|
// 添加角色到列表
|
||||||
|
setTaskRoles(prev => [...prev, roleData[i]]);
|
||||||
|
|
||||||
|
// 更新loading文字显示已完成的角色数量
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.newCharacter(i + 1, totalRoles));
|
||||||
|
|
||||||
|
// 如果不是最后一个角色,稍微延迟一下让用户看到更新
|
||||||
|
if (i < totalRoles - 1) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟接口请求 获取背景音
|
// 模拟接口请求 获取背景音
|
||||||
const getTaskBackgroundAudio = async (taskId: string) => {
|
const getTaskBackgroundAudio = async (taskId: string) => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000)); // 10s
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.audio)); // 10s
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟接口请求 获取最终成品
|
// 模拟接口请求 获取最终成品
|
||||||
const getTaskFinalProduct = async (taskId: string) => {
|
const getTaskFinalProduct = async (taskId: string) => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000)); // 50s
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.final)); // 50s
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟接口请求 每次获取一个分镜视频 轮询获取
|
// 模拟接口请求 每次获取一个分镜视频 轮询获取
|
||||||
@ -99,7 +120,7 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
// 模拟分批获取分镜视频
|
// 模拟分批获取分镜视频
|
||||||
for (let i = 0; i < totalVideos; i++) {
|
for (let i = 0; i < totalVideos; i++) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 6000)); // 60s
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.video)); // 60s
|
||||||
|
|
||||||
const newVideo = {
|
const newVideo = {
|
||||||
id: `video-${i}`,
|
id: `video-${i}`,
|
||||||
@ -130,6 +151,8 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalSketches = selectedMockData.sketch.length;
|
const totalSketches = selectedMockData.sketch.length;
|
||||||
|
const totalVideos = selectedMockData.video.length;
|
||||||
|
const totalCharacters = selectedMockData.roles.length;
|
||||||
|
|
||||||
if (currentStep === '1') {
|
if (currentStep === '1') {
|
||||||
if (isGeneratingSketch) {
|
if (isGeneratingSketch) {
|
||||||
@ -138,10 +161,14 @@ export function useWorkflowData() {
|
|||||||
setCurrentLoadingText(STEP_MESSAGES.sketchComplete);
|
setCurrentLoadingText(STEP_MESSAGES.sketchComplete);
|
||||||
}
|
}
|
||||||
} else if (currentStep === '2') {
|
} else if (currentStep === '2') {
|
||||||
setCurrentLoadingText(STEP_MESSAGES.character);
|
// 在角色生成阶段,loading文字已经在 getTaskRole 函数中直接管理
|
||||||
|
// 这里不需要额外设置,避免覆盖
|
||||||
|
if (taskRoles.length === totalCharacters) {
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.newCharacter(totalCharacters, totalCharacters));
|
||||||
|
}
|
||||||
} else if (currentStep === '3') {
|
} else if (currentStep === '3') {
|
||||||
if (isGeneratingVideo) {
|
if (isGeneratingVideo) {
|
||||||
setCurrentLoadingText(STEP_MESSAGES.video(taskVideos.length, totalSketches));
|
setCurrentLoadingText(STEP_MESSAGES.video(taskVideos.length, totalVideos));
|
||||||
} else {
|
} else {
|
||||||
setCurrentLoadingText(STEP_MESSAGES.videoComplete);
|
setCurrentLoadingText(STEP_MESSAGES.videoComplete);
|
||||||
}
|
}
|
||||||
@ -152,7 +179,7 @@ export function useWorkflowData() {
|
|||||||
} else {
|
} else {
|
||||||
setCurrentLoadingText(STEP_MESSAGES.complete);
|
setCurrentLoadingText(STEP_MESSAGES.complete);
|
||||||
}
|
}
|
||||||
}, [isLoading, currentStep, isGeneratingSketch, sketchCount, isGeneratingVideo, taskVideos.length, taskSketch.length]);
|
}, [isLoading, currentStep, isGeneratingSketch, sketchCount, isGeneratingVideo, taskVideos.length, taskSketch.length, taskRoles.length]);
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -194,7 +221,15 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
// 获取分镜视频后,开始获取背景音
|
// 获取分镜视频后,开始获取背景音
|
||||||
await getTaskBackgroundAudio(taskId);
|
await getTaskBackgroundAudio(taskId);
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
// 后期制作:抽卡中 对口型中 配音中 一致性处理中
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.postProduction('Selecting optimal frames'));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.postProduction));
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.postProduction('Aligning lip sync'));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.postProduction));
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.postProduction('Adding background audio'));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.postProduction));
|
||||||
|
setCurrentLoadingText(STEP_MESSAGES.postProduction('Consistency processing'));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, MOCK_DELAY_TIME.postProduction));
|
||||||
|
|
||||||
// 修改 taskObject 下的 taskStatus 为 '5'
|
// 修改 taskObject 下的 taskStatus 为 '5'
|
||||||
setTaskObject((prev: any) => ({
|
setTaskObject((prev: any) => ({
|
||||||
@ -202,15 +237,7 @@ export function useWorkflowData() {
|
|||||||
taskStatus: '5'
|
taskStatus: '5'
|
||||||
}));
|
}));
|
||||||
setCurrentStep('5');
|
setCurrentStep('5');
|
||||||
// 后期制作:抽卡中 对口型中 配音中 一致性处理中
|
|
||||||
setCurrentLoadingText(STEP_MESSAGES.postProduction('Selecting optimal frames'));
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
||||||
setCurrentLoadingText(STEP_MESSAGES.postProduction('Aligning lip sync'));
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
||||||
setCurrentLoadingText(STEP_MESSAGES.postProduction('Adding background audio'));
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
||||||
setCurrentLoadingText(STEP_MESSAGES.postProduction('Consistency processing'));
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
||||||
// 获取背景音后,开始获取最终成品
|
// 获取背景音后,开始获取最终成品
|
||||||
await getTaskFinalProduct(taskId);
|
await getTaskFinalProduct(taskId);
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|||||||
@ -139,7 +139,7 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-[600px] overflow-hidden bg-[var(--background)]">
|
<div className="relative w-full h-[360px] mt-[3rem] overflow-hidden bg-[var(--background)]">
|
||||||
{/* 视频面板容器 */}
|
{/* 视频面板容器 */}
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|||||||
@ -247,10 +247,20 @@ export const STEP_MESSAGES = {
|
|||||||
sketch: (count: number, total: number) => `Generating sketch ${count + 1 > total ? total : count + 1}/${total}...`,
|
sketch: (count: number, total: number) => `Generating sketch ${count + 1 > total ? total : count + 1}/${total}...`,
|
||||||
sketchComplete: 'Sketch generation complete',
|
sketchComplete: 'Sketch generation complete',
|
||||||
character: 'Drawing characters...',
|
character: 'Drawing characters...',
|
||||||
|
newCharacter: (count: number, total: number) => `Drawing character ${count + 1 > total ? total : count + 1}/${total}...`,
|
||||||
video: (count: number, total: number) => `Generating video ${count + 1 > total ? total : count + 1}/${total}...`,
|
video: (count: number, total: number) => `Generating video ${count + 1 > total ? total : count + 1}/${total}...`,
|
||||||
videoComplete: 'Video generation complete',
|
videoComplete: 'Video generation complete',
|
||||||
audio: 'Generating background audio...',
|
audio: 'Generating background audio...',
|
||||||
postProduction: (step: string) => `Post-production: ${step}...`,
|
postProduction: (step: string) => `Post-production: ${step}...`,
|
||||||
final: 'Generating final product...',
|
final: 'Generating final product...',
|
||||||
complete: 'Task completed'
|
complete: 'Task completed'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MOCK_DELAY_TIME = {
|
||||||
|
sketch: 5000, // 5s 一个草图
|
||||||
|
character: 2000, // 2s 一个角色
|
||||||
|
video: 6000, // 6s 一个分镜视频
|
||||||
|
audio: 2000, // 2s 一个音频
|
||||||
|
postProduction: 2000, // 2s 一个后期制作
|
||||||
|
final: 10000, // 10s 一个最终成品
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user