fix: 测试清单一轮bug修复(商品库/视频项目/设置/消费/平台套图)

- 商品库编辑删图假成功+悬停无删除图标;商品三视图错显角色三视图
- 视频项目演员删除入口;角色三视图防呆;故事板审核失败原因透出
- 设置:管理团队死按钮/通知未接入项屏蔽/邮箱验证链路
- 消费:账单流水切换类型重置分页+独立总页数
- 资产库上传按钮隐藏;月限额两处对齐
- 平台套图:提示词框放大/选模型胶囊内嵌/未读任务角标/工作台记录持久化
- 新建商品独立添加卖点按钮;商品库超1920自适应

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-30 19:46:35 +08:00
co-authored by Claude Opus 4.8
parent b17a716c06
commit 22268d608b
14 changed files with 261 additions and 107 deletions
+65 -22
View File
@@ -852,24 +852,43 @@ export function PipelinePage(props: {
}
// ── 流程步骤4/5 · 兜底弹窗拦截:生成故事板/视频前,查被引用的角色/场景是否都有「按名字采用」的参考图 ──
// 缺了就弹窗(去补齐 / 仍要生成),不静默退化文生图、不让用户花冤枉钱。商品永远有主图、不算缺。
const [refGate, setRefGate] = useState<{ missing: Array<{ name: string; type: string }>; proceed: () => void } | null>(null);
// reason:"noref" = 还没参考图(立绘/场景图);"notri" = 角色有立绘但缺三视图(故事板合成需多角度参考)。
type RefMiss = { name: string; type: string; reason: "noref" | "notri" };
const [refGate, setRefGate] = useState<{ missing: RefMiss[]; proceed: () => void } | null>(null);
useBodyScrollLock(Boolean(refGate));
function missingRefsFor(segs: Array<{ entity_refs?: string[] }>): Array<{ name: string; type: string }> {
// 某角色(按名字)已采用的立绘有没有配套三视图:取该角色代表组的 adopted_asset → 查它的三视图组。
// 用与详情弹窗同款判定(triview 组有候选 / 资产 metadata 标记 / 模特库正面图)。
function personHasTriview(name: string): boolean {
const ent = buildEntities("person").find((e) => (e.name || "").trim() === (name || "").trim());
const portrait = ent?.group.adopted_asset;
if (!portrait) return false;
const tri = triGroupForAsset(portrait);
if (tri && (tri.adopted_asset || (tri.candidate_assets?.length ?? 0) > 0)) return true;
const m: Record<string, unknown> = byId.get(portrait)?.metadata || {};
const has = m.tri_view ?? m.triview ?? m.has_triview ?? m.three_view ?? m.tri_views;
if (has === true || (Array.isArray(has) && has.length > 0) || (typeof has === "string" && (has as string).trim())) return true;
if (m.view === "frontal") return true; // 模特库正面图与三视图同批生成
return false;
}
function missingRefsFor(segs: Array<{ entity_refs?: string[] }>): RefMiss[] {
const ents = project.metadata?.script_entities;
if (!Array.isArray(ents) || !ents.length) return []; // 没提取过实体就不拦(交给提取闸门)
const byId = new Map(ents.map((e) => [e.id, e]));
const entById = new Map(ents.map((e) => [e.id, e]));
const adopted = {
person: new Set(buildEntities("person").filter((e) => e.group.adopted_asset).map((e) => (e.name || "").trim())),
scene: new Set(buildEntities("scene").filter((e) => e.group.adopted_asset).map((e) => (e.name || "").trim())),
};
const miss = new Map<string, { name: string; type: string }>();
const miss = new Map<string, RefMiss>();
for (const seg of segs) {
for (const rid of seg.entity_refs || []) {
const ent = byId.get(rid);
const ent = entById.get(rid);
if (!ent || ent.type === "product") continue; // 商品永远有主图,不算缺
const kind = ent.type === "character" ? "person" : "scene";
const name = (ent.name || "").trim();
if (name && !adopted[kind].has(name)) miss.set(name, { name, type: ent.type });
if (!name) continue;
if (!adopted[kind].has(name)) { miss.set(name, { name, type: ent.type, reason: "noref" }); continue; }
// 已有立绘/场景图 → 角色再查三视图:故事板 @图 合成需正/侧/背多角度参考,缺则拦(ZWQ#3 防呆)
if (kind === "person" && !personHasTriview(name)) miss.set(name, { name, type: ent.type, reason: "notri" });
}
}
return [...miss.values()];
@@ -3870,28 +3889,52 @@ export function PipelinePage(props: {
);
})()}
{/* ── 流程步骤4/5 · 兜底弹窗拦截:参考图不齐时拦住生成,让用户去补 / 仍要生成(随他便) ── */}
{refGate && (
{refGate && (() => {
// 拆两类:noref=连参考图都没有;notri=有立绘但缺三视图。文案分别告诉用户「去哪里点什么」。
const noRef = refGate.missing.filter((m) => m.reason === "noref");
const noTri = refGate.missing.filter((m) => m.reason === "notri");
return (
<div className="ref-gate-mask" onClick={() => setRefGate(null)}>
<div className="ref-gate-modal" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
<div className="rg-title">参考图还没齐,先补一下?</div>
<div className="rg-body">
下面这些角色 / 场景<strong>还没生成参考图</strong>。现在直接生成,出来的画面会跟你设定的对不上(像纯文生图、白花钱):
</div>
<div className="rg-list">
{refGate.missing.map((m) => (
<span className="rg-chip" key={`${m.type}:${m.name}`}>
<span className={`rg-kind rg-kind-${m.type === "character" ? "person" : "scene"}`}>{m.type === "character" ? "角色" : "场景"}</span>
{m.name}
</span>
))}
</div>
<div className="rg-title">资产还没齐,先补一下?</div>
{noRef.length > 0 && (
<>
<div className="rg-body">
下面这些角色 / 场景<strong>还没生成参考图</strong>。请回「基础资产」页,点对应卡片进详情后<strong>「生成立绘 / 场景图」</strong>并采用,否则故事板会变成纯文生图、跟你的设定对不上(白花钱):
</div>
<div className="rg-list">
{noRef.map((m) => (
<span className="rg-chip" key={`noref:${m.type}:${m.name}`}>
<span className={`rg-kind rg-kind-${m.type === "character" ? "person" : "scene"}`}>{m.type === "character" ? "角色" : "场景"}</span>
{m.name}
</span>
))}
</div>
</>
)}
{noTri.length > 0 && (
<>
<div className="rg-body" style={{ marginTop: noRef.length > 0 ? 14 : 0 }}>
下面这些角色已有立绘,但<strong>还没生成三视图</strong>。故事板 @图 合成要靠正 / 侧 / 背多角度锁人物,缺三视图角色容易跑形。请回「基础资产」页,点角色卡进详情后点<strong>「AI 生成三视图」</strong>:
</div>
<div className="rg-list">
{noTri.map((m) => (
<span className="rg-chip" key={`notri:${m.type}:${m.name}`}>
<span className="rg-kind rg-kind-person">角色</span>
{m.name} <span className="rg-warn">缺三视图</span>
</span>
))}
</div>
</>
)}
<div className="rg-actions">
<button type="button" className="btn btn-primary" onClick={() => { setRefGate(null); goStage(2); }}>去补齐参考图</button>
<button type="button" className="btn btn-ghost rg-force" onClick={() => { const go = refGate.proceed; setRefGate(null); go(); }}>仍要生成 <span className="rg-warn">可能跟角色对不上</span></button>
<button type="button" className="btn btn-primary" onClick={() => { setRefGate(null); goStage(2); }}>去基础资产页补齐</button>
<button type="button" className="btn btn-ghost rg-force" onClick={() => { const go = refGate.proceed; setRefGate(null); go(); }}>仍要继续 <span className="rg-warn">可能跟角色对不上</span></button>
</div>
</div>
</div>
)}
);
})()}
{/* ── 流程步骤5 · 过审闸弹窗:含真人脸的人物/分镜未过审 → 列出哪一镜/哪个,先过审再生成(火山合规要求) ── */}
{reviewGate && (
<div className="ref-gate-mask" onClick={() => setReviewGate(null)}>