diff --git a/src/features/ecommerce/EcommerceVideoWorkspace.tsx b/src/features/ecommerce/EcommerceVideoWorkspace.tsx index 79c8a92..897a37c 100644 --- a/src/features/ecommerce/EcommerceVideoWorkspace.tsx +++ b/src/features/ecommerce/EcommerceVideoWorkspace.tsx @@ -1,5 +1,6 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { + CloseOutlined, CopyOutlined, DownloadOutlined, FolderAddOutlined, @@ -111,6 +112,7 @@ export default function EcommerceVideoWorkspace({ const [failedStep, setFailedStep] = useState(null); const [error, setError] = useState(null); const [actionNotice, setActionNotice] = useState(null); + const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null); const abortControllerRef = useRef(null); const renderAbortRef = useRef({ current: false }); const setView = useAppStore((s) => s.setView); @@ -145,24 +147,17 @@ export default function EcommerceVideoWorkspace({ saveEcommerceVideoState({ inputFingerprint, stage, completedSteps, planResult, planProgress, scenes, sourceImageUrls }); }, [inputFingerprint, stage, completedSteps, planResult, planProgress, scenes, sourceImageUrls]); - // ── Auto-advance: skip manual "next step" clicks ───────── - const autoAdvanceTriggeredRef = useRef(false); + // ── Auto-advance: automatically run the full pipeline ───────── useEffect(() => { - if (autoAdvanceTriggeredRef.current) return; const delay = 600; if (stage === "planned" && planResult && scenes.length > 0) { - autoAdvanceTriggeredRef.current = true; const timer = setTimeout(() => { void handleGenerateImages(); }, delay); return () => clearTimeout(timer); } if (stage === "imaged" && scenes.every((s) => s.imageUrl)) { - autoAdvanceTriggeredRef.current = true; const timer = setTimeout(() => { void handleRenderVideos(); }, delay); return () => clearTimeout(timer); } - if (stage === "idle" || stage === "cancelled") { - autoAdvanceTriggeredRef.current = false; - } }, [stage, scenes, planResult]); // ── Keep-alive: resume polling for running tasks ────────── @@ -638,11 +633,7 @@ export default function EcommerceVideoWorkspace({ {scenes.length > 0 ? scenes.map((s) => (
)) : ( - <> -
-
-
- +
)}
@@ -672,7 +663,7 @@ export default function EcommerceVideoWorkspace({
-
+
setPreviewMedia({ url: scene.imageUrl!, type: "image" }) : undefined} style={imgReady ? { cursor: "pointer" } : undefined}> {imgReady ? ( {`分镜${scene.sceneId}`} ) : ( @@ -688,7 +679,7 @@ export default function EcommerceVideoWorkspace({
-
+
setPreviewMedia({ url: scene.resultUrl!, type: "video" }) : undefined} style={vidReady ? { cursor: "pointer" } : undefined}> {vidReady ? (
); }) : ( - [1, 2, 3].map((n) => ( -
-
-
- 分镜文本{n} - {stage === "planning" ? "策划中..." : "等待策划"} -
-
-
@@ -753,6 +742,19 @@ export default function EcommerceVideoWorkspace({ ) : null} {actionNotice ?
{actionNotice}
: null} + + {previewMedia ? ( +
setPreviewMedia(null)}> + + {previewMedia.type === "image" ? ( + 预览 e.stopPropagation()} /> + ) : ( +
+ ) : null} ); } diff --git a/src/styles/pages/ecommerce-video.css b/src/styles/pages/ecommerce-video.css index 2230115..7355f58 100644 --- a/src/styles/pages/ecommerce-video.css +++ b/src/styles/pages/ecommerce-video.css @@ -1101,3 +1101,47 @@ 70% { opacity: 0.5; } 100% { opacity: 0; transform: translateX(100%); } } + +/* ── Preview lightbox overlay ────────────────────── */ +.ecom-video-preview-overlay { + position: fixed; + inset: 0; + z-index: 9999; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(8px); + cursor: zoom-out; + animation: ecom-preview-fade-in 200ms ease; +} + +.ecom-video-preview-overlay__close { + position: absolute; + top: 24px; + right: 24px; + z-index: 10; + display: grid; + width: 40px; + height: 40px; + place-items: center; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 999px; + background: rgba(0, 0, 0, 0.6); + color: #fff; + font-size: 18px; + cursor: pointer; +} + +.ecom-video-preview-overlay img, +.ecom-video-preview-overlay video { + max-width: 90vw; + max-height: 85vh; + border-radius: 8px; + object-fit: contain; + cursor: default; +} + +@keyframes ecom-preview-fade-in { + from { opacity: 0; } + to { opacity: 1; } +}