From f06e583c9ba60d43aa1a8146623e3fc70bc65e14 Mon Sep 17 00:00:00 2001 From: xz Date: Wed, 26 Aug 2026 20:56:06 +0800 Subject: [PATCH] feat: polish director studio responsive layout --- src/components/ProductionPages.jsx | 117 +++++++++++++++++- src/styles.css | 185 ++++++++++++++++++++++++++++- 2 files changed, 298 insertions(+), 4 deletions(-) diff --git a/src/components/ProductionPages.jsx b/src/components/ProductionPages.jsx index d386619..e64004b 100644 --- a/src/components/ProductionPages.jsx +++ b/src/components/ProductionPages.jsx @@ -32,6 +32,7 @@ import { XCircle } from "lucide-react"; import { workflowTemplates } from "../data/sampleProject"; +import { ScenePreview } from "./ScenePreview"; import { addProductionReviewComment, activateDeliveryBatch, @@ -107,6 +108,22 @@ function StatusTag({ status }) { return {labels[status] || status || "未设置"}; } +function StudioPill({ label, value, tone = "neutral" }) { + return
{label}{value}
; +} + +function formatStudioValue(value) { + if (Array.isArray(value)) return value.filter(Boolean).join(" · "); + if (value && typeof value === "object") return JSON.stringify(value); + return String(value || ""); +} + +function continuityEntries(shot) { + return Object.entries(shot?.continuity || {}) + .filter(([, value]) => value !== undefined && value !== null && value !== "") + .slice(0, 8); +} + function useProductionGraph(contextOverrides) { const [graph, setGraph] = useState(null); const [loading, setLoading] = useState(true); @@ -473,8 +490,26 @@ export function DirectorProductionPage({ project, contextOverrides, canCreateJob const [busy, setBusy] = useState(false); const [notice, setNotice] = useState(""); const [showCreate, setShowCreate] = useState(false); + const [stageTab, setStageTab] = useState("preview"); useEffect(() => { const next = graph?.shots?.find((shot) => shot.id === selectedId) || graph?.shots?.[0]; if (next) { setSelectedId(next.id); setDraft(next); } }, [graph, selectedId]); - const selected = graph?.shots?.find((shot) => shot.id === selectedId) || graph?.shots?.[0]; + const shots = graph?.shots || []; + const selected = shots.find((shot) => shot.id === selectedId) || shots[0]; + const totalDuration = shots.reduce((sum, shot) => sum + Number(shot.durationSec || 0), 0); + const voiceLineCount = selected?.voiceLines?.length || 0; + const bridgeLabel = selected?.transitionFromPrevious === "actual-last-frame" ? "实际末帧" : selected?.transitionFromPrevious === "insert-bridge-shot" ? "过渡镜头" : selected?.transitionFromPrevious || "本集开场"; + const stageTabs = [ + { id: "preview", label: "预览与提示词" }, + { id: "continuity", label: "连续性锁" }, + { id: "versions", label: "版本记录" }, + { id: "queue", label: "辅助与生成" } + ]; + const summaryPills = [ + { label: "镜头", value: `${shots.length} 个` }, + { label: "时长", value: `${totalDuration}s` }, + { label: "对白", value: `${voiceLineCount} 句` }, + { label: "版本", value: `v${selected?.versionNumber || 1}` }, + { label: "衔接", value: bridgeLabel } + ]; const updateField = (key, value) => setDraft((current) => ({ ...current, [key]: key === "durationSec" || key === "seed" ? (value === "" ? "" : Number(value)) : value })); async function loadVersions(shotId = selected?.id) { if (!shotId) return; @@ -518,7 +553,85 @@ export function DirectorProductionPage({ project, contextOverrides, canCreateJob const form = new FormData(event.currentTarget); try { const result = await createProductionShot({ episodeId: graph.episode.id, title: form.get("title"), durationSec: Number(form.get("durationSec") || 6), action: form.get("action") }, contextOverrides); setGraph(result.graph); setSelectedId(result.shot.id); setDraft(result.shot); setShowCreate(false); setNotice(`已创建 ${result.shot.id},请继续补齐角色、场景、道具和 Prompt。`); } catch (error) { setNotice(error.message); } finally { setBusy(false); } } - return
} />{notice && setNotice("")}>{notice}}{error && 导演图谱读取失败:{error}}
{graph?.shots?.map((shot) => )}
} />{selected && draft ?