feat(storyboard): 整张生成时每张分镜转圈(不全屏遮挡)+ 未生成占位回 2:3 竖屏

- 每张分镜占位(缩略图 + 大图)叠半透明遮罩 + spinner,样式同基础资产趴;不再全屏遮挡
- 中间大图空占位由 16:9 横框改 2:3 竖屏,与成品(1024×1536)同形;fh-flat 下加兜底高度居中
- 生成结束(成功/失败)spinner 自动停

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-23 13:16:16 +08:00
co-authored by Claude Opus 4.8
parent 9c2a3bc365
commit dbd1382fb1
2 changed files with 15 additions and 5 deletions
+7 -4
View File
@@ -481,7 +481,7 @@ export function PipelinePage(props: {
onGenerateTriview: (portraitGroupId: string) => Promise<{ id: string } | null>;
// 流程步骤4 · 添加人物工作台命名:把名字写回该人物资产
onRenameActor?: (assetId: string, name: string) => Promise<unknown>;
onGenerateStoryboard: (prompt: string) => void;
onGenerateStoryboard: (prompt: string) => void | Promise<unknown>;
onSkipStoryboard: () => Promise<unknown>;
onSubmitVideo: (segmentId: string, prompt: string) => void;
onSubmitAllVideos: (prompt: string) => void;
@@ -846,6 +846,8 @@ export function PipelinePage(props: {
// 让用户一眼看出「这里本该有几张」,出图后逐个填真图。无采用版脚本则为 0(显示「暂无」)。
const sbAdoptedScript = scripts.find((s) => s.is_adopted) || null;
const sbExpectedShots = sbAdoptedScript ? (sbAdoptedScript.segments?.length ?? 0) : 0;
// 整张故事板生成中:不全屏遮挡,只在每张分镜占位上转个 spinner(样式同基础资产趴)
const [sbGenerating, setSbGenerating] = useState(false);
// 没生成故事板时点「确认故事板」→ 弹气泡提示先去生成(3.5s 自动收起)
const [sbConfirmHint, setSbConfirmHint] = useState(false);
useEffect(() => {
@@ -2943,7 +2945,7 @@ export function PipelinePage(props: {
const url = frameUrl(frame);
return (
<div className={`sb-scene-thumb${idx === sbSelected ? " selected" : ""}`} key={frame.id} data-sid={frame.id} onClick={() => setSbSelected(idx)}>
<div className={`placeholder${url ? " has-mock-media" : ""}`} style={url ? mediaStyle(url) : undefined}><span className="ph-frame"> {idx + 1}</span></div>
<div className={`placeholder${url ? " has-mock-media" : ""}`} style={url ? mediaStyle(url) : undefined}><span className="ph-frame"> {idx + 1}</span>{sbGenerating && <span className="sb-gen-veil" aria-hidden="true"><span className="spinner" /></span>}</div>
<div className="nm"> {idx + 1}</div>
<div className="sub">{times[idx] || `#${frame.sort_order + 1}`}</div>
</div>
@@ -2953,7 +2955,7 @@ export function PipelinePage(props: {
/* 还没出图:按采用版脚本镜头数铺等量空占位,标「待生成」,让用户看出本该有几张 */
Array.from({ length: sbExpectedShots }, (_, idx) => (
<div className={`sb-scene-thumb${idx === sbSelected ? " selected" : ""}`} key={`ph-${idx}`} onClick={() => setSbSelected(idx)}>
<div className="placeholder"><span className="ph-frame"> {idx + 1}</span></div>
<div className="placeholder"><span className="ph-frame"> {idx + 1}</span>{sbGenerating && <span className="sb-gen-veil" aria-hidden="true"><span className="spinner" /></span>}</div>
<div className="nm"> {idx + 1}</div>
<div className="sub">// 待生成</div>
</div>
@@ -2965,6 +2967,7 @@ export function PipelinePage(props: {
return (
<div className={`placeholder sb-main-img${url ? " has-mock-media" : ""}`} id="sb-main-img" style={url ? { ...mediaStyle(url), cursor: "zoom-in" } : undefined} role={url ? "button" : undefined} tabIndex={url ? 0 : undefined} title={url ? "点击放大" : undefined} onClick={url ? () => setPreview({ src: url, kind: "image", name: `${sbSelected + 1}` }) : undefined} onKeyDown={url ? (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setPreview({ src: url, kind: "image", name: `${sbSelected + 1}` }); } } : undefined}>
<span className="ph-frame">{sbActiveFrame ? `${sbSelected + 1}` : sbExpectedShots ? `${sbSelected + 1} · 待生成` : "// 故事板未生成"}</span>
{sbGenerating && <span className="sb-gen-veil" aria-hidden="true"><span className="spinner" /></span>}
</div>
);
})()}
@@ -2999,7 +3002,7 @@ export function PipelinePage(props: {
onChange={(v) => setStoryboardPrompt(v.trim())}
/>
<div className="sb-stage-actions">
<button className="pill-cta heat" type="button" id="sb-rerun-btn" disabled={loading} onClick={() => guardGen(shots, () => onGenerateStoryboard(storyboardPrompt || SB_PROMPT_DEFAULT))}>
<button className="pill-cta heat" type="button" id="sb-rerun-btn" disabled={loading} onClick={() => guardGen(shots, () => { setSbGenerating(true); void Promise.resolve(onGenerateStoryboard(storyboardPrompt || SB_PROMPT_DEFAULT)).finally(() => setSbGenerating(false)); })}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12a8 8 0 0 1 14-5.5L21 9" /><path d="M21 4v5h-5" /><path d="M20 12a8 8 0 0 1-14 5.5L3 15" /><path d="M3 20v-5h5" /></svg>
{adoptedStoryboard ? "整张重跑" : "生成故事板"}
</button>