Merge remote-tracking branch 'origin/dev' into pre

This commit is contained in:
Xin Wang 2025-07-04 22:02:48 +08:00
commit 1683befd12
5 changed files with 265 additions and 34 deletions

View File

@ -586,10 +586,9 @@ const ImageQueue = ({ shouldStart, onComplete }: { shouldStart: boolean; onCompl
const containers = Array.from(imagesRef.current.children) as HTMLElement[];
const videos = containers.map(container => container.querySelector('video')).filter(Boolean) as HTMLVideoElement[];
if (videos.length > 0) {
const finalVideoContainer = document.getElementById('final-video-container') as HTMLDivElement;
if (videos.length > 0 && finalVideoContainer) {
// 创建最终的大视频容器
const finalVideoContainer = document.createElement('div');
finalVideoContainer.className = 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] rounded-lg overflow-hidden shadow-2xl opacity-0';
const finalVideo = document.createElement('video');
finalVideo.src = finalVideoUrl;
@ -600,7 +599,6 @@ const ImageQueue = ({ shouldStart, onComplete }: { shouldStart: boolean; onCompl
finalVideo.className = 'w-full h-full object-cover';
finalVideoContainer.appendChild(finalVideo);
document.body.appendChild(finalVideoContainer);
finalVideoContainerRef.current = finalVideoContainer;
// 等待最终视频准备就绪
@ -821,7 +819,7 @@ const ImageQueue = ({ shouldStart, onComplete }: { shouldStart: boolean; onCompl
};
// 主要的空状态动画组件
export const EmptyStateAnimation = () => {
export const EmptyStateAnimation = ({className}: {className: string}) => {
const [showText, setShowText] = useState(false);
const [showImages, setShowImages] = useState(false);
const [animationCycle, setAnimationCycle] = useState(0);
@ -829,21 +827,40 @@ export const EmptyStateAnimation = () => {
// 全局清理函数
const globalCleanup = () => {
// 清理所有可能的最终视频容器
const existingFinalVideos = document.querySelectorAll('div[class*="fixed"][class*="top-1/2"][class*="w-[400px]"]');
existingFinalVideos.forEach(container => {
if (container.parentNode === document.body) {
container.remove();
// 清理最终视频容器中的视频元素
const finalVideoContainer = document.getElementById('final-video-container') as HTMLDivElement;
if (finalVideoContainer) {
const finalVideo = finalVideoContainer.querySelector('video');
if (finalVideo) {
finalVideo.remove();
}
// 重置容器透明度
finalVideoContainer.style.opacity = '0';
}
// 清理所有图片容器中的视频元素
const allVideoElements = document.querySelectorAll('video');
allVideoElements.forEach(video => {
// 确保视频不是在final-video-container中已经处理过了
if (!finalVideoContainer?.contains(video)) {
// 停止播放并移除
video.pause();
video.removeAttribute('src');
video.load(); // 释放资源
video.remove();
}
});
// 清理所有body下的视频相关元素
const bodyChildren = Array.from(document.body.children);
bodyChildren.forEach(child => {
if (child instanceof HTMLElement &&
(child.querySelector('video') || child.tagName === 'VIDEO')) {
child.remove();
}
// 清理任何可能遗留的视频容器
const imageContainers = document.querySelectorAll('[class*="w-[200px]"][class*="h-[150px]"]');
imageContainers.forEach(container => {
const videos = container.querySelectorAll('video');
videos.forEach(video => {
video.pause();
video.removeAttribute('src');
video.load();
video.remove();
});
});
};
@ -899,7 +916,7 @@ export const EmptyStateAnimation = () => {
}
return (
<div className='flex flex-col justify-center items-center'>
<div className={`${className} flex flex-col justify-center items-center`}>
{showText && (
<AnimatedText
key={`text-${animationCycle}`}
@ -915,6 +932,8 @@ export const EmptyStateAnimation = () => {
onComplete={handleImagesComplete}
/>
)}
<div id="final-video-container" className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] rounded-lg overflow-hidden shadow-2xl opacity-0"></div>
</div>
);
};

View File

@ -79,7 +79,17 @@ export function CreateToVideo2() {
const [episodeId, setEpisodeId] = useState<number>(0);
const [isCreating, setIsCreating] = useState(false);
const [generatedVideoList, setGeneratedVideoList] = useState<any[]>([]);
const [projectName, setProjectName] = useState(localStorage.getItem('projectName') || '默认名称');
const [projectName, setProjectName] = useState('默认名称');
// 在客户端挂载后读取localStorage
useEffect(() => {
if (typeof window !== 'undefined') {
const savedProjectName = localStorage.getItem('projectName');
if (savedProjectName) {
setProjectName(savedProjectName);
}
}
}, []);
const handleUploadVideo = async () => {
console.log('upload video');
@ -344,15 +354,19 @@ export function CreateToVideo2() {
if (status === 'finished' || status === 'skipped') {
setRunTour(false);
// 可以在这里存储用户已完成引导的状态
localStorage.setItem('hasCompletedTour', 'true');
if (typeof window !== 'undefined') {
localStorage.setItem('hasCompletedTour', 'true');
}
}
};
// 检查是否需要显示引导
useEffect(() => {
const hasCompletedTour = localStorage.getItem('hasCompletedTour');
if (hasCompletedTour) {
setRunTour(false);
if (typeof window !== 'undefined') {
const hasCompletedTour = localStorage.getItem('hasCompletedTour');
if (hasCompletedTour) {
setRunTour(false);
}
}
}, []);
@ -363,8 +377,7 @@ export function CreateToVideo2() {
return (
<div
ref={containerRef}
className="container mx-auto overflow-hidden custom-scrollbar"
style={isExpanded ? { height: 'calc(100vh - 12rem)' } : { height: 'calc(100vh - 20rem)' }}
className="container mx-auto overflow-hidden custom-scrollbar h-[calc(100vh-10rem)]"
>
{isClient && (
<JoyrideNoSSR
@ -398,10 +411,10 @@ export function CreateToVideo2() {
)}
<div className='min-h-[100%] flex flex-col justify-center items-center'>
{/* 空状态 */}
<EmptyStateAnimation />
<EmptyStateAnimation className='h-[calc(100vh - 20rem)]' />
{/* 工具栏 */}
<div className='video-tool-component relative w-[1080px]'>
<div className='video-storyboard-tools grid gap-4 rounded-[20px] bg-[#0C0E11] backdrop-blur-[15px]'>
<div className='video-storyboard-tools grid gap-4 rounded-[20px] bg-white/[0.08] backdrop-blur-[20px] border border-white/[0.12] shadow-[0_8px_32px_rgba(0,0,0,0.3)]'>
{isExpanded ? (
<div className='absolute top-0 bottom-0 left-0 right-0 z-[1] grid justify-items-center place-content-center rounded-[20px] bg-[#191B1E] bg-opacity-[0.3] backdrop-blur-[1.5px] cursor-pointer' onClick={() => setIsExpanded(false)}>
{/* 图标 展开按钮 */}

View File

@ -299,4 +299,203 @@
.login-logo:hover::before {
opacity: 1;
}
/* 响应式设计 - 移动端和平板端适配 */
@media (max-width: 1024px) {
.main-container {
position: relative;
}
.left-panel {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.right-panel {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 2;
background: transparent;
backdrop-filter: none;
justify-content: flex-end;
align-items: center;
padding: 1rem;
padding-right: 2rem;
}
.auth-container {
/* max-width: 350px; */
margin-right: 0;
margin-left: auto;
}
.login-logo {
top: 1.5rem;
left: 1.5rem;
font-size: 1.5rem;
}
/* 移动端文字颜色调整 */
.auth-header h2 {
background: linear-gradient(135deg, #000, #333);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #000;
}
.auth-header p {
color: rgba(0, 0, 0, 0.7);
}
.form-label {
color: rgba(0, 0, 0, 0.8);
}
.form-control {
color: #000;
}
.form-control::placeholder {
color: rgba(0, 0, 0, 0.4);
}
/* 注册链接改为橙色 */
.auth-link {
color: #35daff;
}
.auth-link:hover {
color: #47dafb;
}
/* 其他文字元素 */
.text-red-500 {
color: #dc2626 !important;
}
.text-green-300 {
color: #86efac !important;
}
/* 底部提示文字 */
.text-center p {
color: rgba(0, 0, 0, 0.6) !important;
}
/* 忘记密码链接也改为橙色 */
.auth-link.small {
color: #35daff;
}
.auth-link.small:hover {
color: #47dafb;
}
/* Google登录按钮文字保持白色 */
button[type="button"] span {
color: white;
}
/* 分割线文字 */
.text-gray-400 {
color: rgba(0, 0, 0, 0.5) !important;
}
}
@media (max-width: 768px) {
.right-panel {
justify-content: center;
align-items: center;
padding: 1rem;
}
.auth-container {
max-width: 100%;
width: calc(100% - 2rem);
margin: 0;
padding: 2rem 1.5rem;
}
.login-logo {
top: 1rem;
left: 1rem;
font-size: 1.3rem;
}
.auth-header h2 {
font-size: 1.5rem;
}
}
@media (max-width: 480px) {
.auth-container {
padding: 1.5rem 1rem;
border-radius: 1rem;
}
.login-logo {
font-size: 1.2rem;
}
.auth-header {
margin-bottom: 1.5rem;
}
.auth-header h2 {
font-size: 1.3rem;
}
.auth-header p {
font-size: 0.8rem;
}
.form-control {
padding: 0.6rem 0.8rem;
}
.btn-primary {
padding: 0.6rem;
}
}
/* 确保背景在所有尺寸下都能正确显示 */
@media (max-width: 1024px) {
#vanta-background {
width: 100vw !important;
height: 100vh !important;
}
}
/* iPad横屏特殊处理 */
@media (max-width: 1024px) and (min-width: 769px) and (orientation: landscape) {
.right-panel {
justify-content: flex-end;
align-items: center;
padding-right: 3rem;
}
.auth-container {
max-width: 380px;
}
}
/* 安全区域适配,适用于有刘海的设备 */
@media (max-width: 1024px) {
.right-panel {
padding-top: max(1rem, env(safe-area-inset-top));
padding-bottom: max(1rem, env(safe-area-inset-bottom));
padding-left: max(1rem, env(safe-area-inset-left));
padding-right: max(2rem, env(safe-area-inset-right));
}
.login-logo {
top: max(1rem, calc(env(safe-area-inset-top) + 0.5rem));
left: max(1rem, calc(env(safe-area-inset-left) + 0.5rem));
}
}

View File

@ -246,7 +246,7 @@ export function useWorkflowData() {
setIsLoading(true);
setCurrentLoadingText('正在初始化工作流...');
const taskId = localStorage.getItem("taskId") || "taskId-123";
const taskId = (typeof window !== 'undefined' ? localStorage.getItem("taskId") : null) || "taskId-123";
// 首先加载数据
await loadMockData();

View File

@ -122,11 +122,11 @@ const VantaHaloBackground = memo(({ onLoaded }: VantaHaloBackgroundProps) => {
<div
ref={vantaRef}
style={{
width: '61.8vw',
height: '100vh',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
// position: 'fixed',
// top: 0,
// left: 0,
zIndex: -1,
willChange: 'transform', // 优化图层合成
transform: 'translateZ(0)', // 启用硬件加速