fix(ui): 批量栏黑底还原 + 图片生成页排版对齐HTML + 视频卡去多余上传

- .bulk-bar 全局裸类被 admin-page.css 白底版盖掉 → 收窄到 .admin-app;
  商品库/项目/资产库批量浮条恢复黑底
- 批量浮条显隐改绑「有无选中」(.show)而非编辑模式 → 退选即消失
- asset-factory 三卡:开始生成按钮挪回 factory-text 内(对齐HTML),
  按钮紧跟描述、卡片高度回正;tag/desc/价格字号对齐HTML(10.5/13.5/10.5)
- 全站内容区上内边距 48px→24px(对齐设计稿 #page-content),标题不再整体偏低
- 视频卡移除多余「上传」按钮(AI生成不需单卡上传,全局上传入口保留);
  video-card-head 顶端对齐改垂直居中

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-20 18:34:33 +08:00
co-authored by Claude Opus 4.8
parent ad96acfacd
commit b946a64409
12 changed files with 193 additions and 21 deletions
+4 -2
View File
@@ -220,7 +220,9 @@
font-size: 11px; font-size: 11px;
cursor: help; cursor: help;
} }
.bulk-bar { /* 收窄到 .admin-app 作用域:`.bulk-bar` 是全局裸类(商品库/项目/资产库各有黑底版),
不限定会随 main.tsx 末位加载盖掉那些黑底浮条 → 全站批量栏被染白。 */
.admin-app .bulk-bar {
position: fixed; position: fixed;
left: 50%; left: 50%;
bottom: 28px; bottom: 28px;
@@ -235,7 +237,7 @@
box-shadow: var(--shadow-floating); box-shadow: var(--shadow-floating);
z-index: 50; z-index: 50;
} }
.bulk-count { font-size: 13px; color: var(--accent-black); font-weight: 500; } .admin-app .bulk-count { font-size: 13px; color: var(--accent-black); font-weight: 500; }
/* ── 任务监控 ── */ /* ── 任务监控 ── */
.admin-anomaly-chip { height: 28px; padding: 0 14px; font-size: 12.5px; cursor: pointer; } .admin-anomaly-chip { height: 28px; padding: 0 14px; font-size: 12.5px; cursor: pointer; }
+3 -3
View File
@@ -44,7 +44,7 @@
.asset-factory .factory-tag { .asset-factory .factory-tag {
align-self: flex-start; align-self: flex-start;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 12px; font-size: 10.5px;
color: var(--black-alpha-48); color: var(--black-alpha-48);
letter-spacing: .06em; letter-spacing: .06em;
padding: 2px 8px; padding: 2px 8px;
@@ -62,7 +62,7 @@
} }
.asset-factory .factory-desc { .asset-factory .factory-desc {
margin-top: 8px; margin-top: 8px;
font-size: 14px; font-size: 13.5px;
color: var(--black-alpha-64); color: var(--black-alpha-64);
line-height: 1.55; line-height: 1.55;
} }
@@ -77,7 +77,7 @@
} }
.asset-factory .factory-cta .cost { .asset-factory .factory-cta .cost {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 12px; font-size: 10.5px;
color: var(--black-alpha-48); color: var(--black-alpha-48);
letter-spacing: .04em; letter-spacing: .04em;
} }
+2 -1
View File
@@ -554,7 +554,8 @@ main { position: relative; background: var(--background-base); min-width: 0; }
/* ─── Content ─── */ /* ─── Content ─── */
.content { .content {
padding: 48px 28px 72px; /* 对齐 HTML 设计稿 #page-content(24px 28px 60px):原 48px 顶距使全站标题整体偏低 24px */
padding: 24px 28px 60px;
position: relative; position: relative;
z-index: 1; z-index: 1;
min-height: calc(100vh - 64px); min-height: calc(100vh - 64px);
+2 -1
View File
@@ -54,7 +54,8 @@ body.edit-mode .library-page .asset-card .card-del-btn { opacity: 0 !important;
padding: 10px 14px 10px 18px; display: none; align-items: center; gap: 16px; padding: 10px 14px 10px 18px; display: none; align-items: center; gap: 16px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .18); z-index: 100; font-size: 13px; box-shadow: 0 8px 24px rgba(0, 0, 0, .18); z-index: 100; font-size: 13px;
} }
body.edit-mode .lib-bulk-bar { display: inline-flex; } /* 显隐绑定「是否有选中」而非编辑模式 → 退选即消失 */
.lib-bulk-bar.show { display: inline-flex; }
.lib-bulk-bar .ct { font-family: var(--font-mono); letter-spacing: .02em; } .lib-bulk-bar .ct { font-family: var(--font-mono); letter-spacing: .02em; }
.lib-bulk-bar .ct b { color: var(--heat); font-weight: 600; padding: 0 3px; } .lib-bulk-bar .ct b { color: var(--heat); font-weight: 600; padding: 0 3px; }
.lib-bulk-bar .sep { width: 1px; height: 18px; background: rgba(255, 255, 255, .16); } .lib-bulk-bar .sep { width: 1px; height: 18px; background: rgba(255, 255, 255, .16); }
+1 -1
View File
@@ -448,7 +448,7 @@
.video-thumb:hover .play { opacity: 1; } .video-thumb:hover .play { opacity: 1; }
.video-thumb .btn-play { width: 36px; height: 36px; background: rgba(0,0,0,.7); color: var(--accent-white); border-radius: 50%; display: grid; place-items: center; } .video-thumb .btn-play { width: 36px; height: 36px; background: rgba(0,0,0,.7); color: var(--accent-white); border-radius: 50%; display: grid; place-items: center; }
.video-card .body { padding: 12px 12px 14px; flex: 1 1 auto; min-height: 118px; display: flex; flex-direction: column; } .video-card .body { padding: 12px 12px 14px; flex: 1 1 auto; min-height: 118px; display: flex; flex-direction: column; }
.video-card-head { display: flex; align-items: flex-start; gap: 8px; } .video-card-head { display: flex; align-items: center; gap: 8px; }
.video-card-title { min-width: 0; flex: 1 1 auto; font-size: 13px; line-height: 1.4; font-weight: 600; color: var(--accent-black); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .video-card-title { min-width: 0; flex: 1 1 auto; font-size: 13px; line-height: 1.4; font-weight: 600; color: var(--accent-black); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.video-card-head .pill { flex: 0 0 auto; } .video-card-head .pill { flex: 0 0 auto; }
.video-meta { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); letter-spacing: .02em; margin-top: 5px; } .video-meta { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); letter-spacing: .02em; margin-top: 5px; }
+2 -1
View File
@@ -72,7 +72,8 @@ body.edit-mode .proj-card.selected { border-color: var(--heat); box-shadow: 0 0
padding: 10px 14px 10px 18px; display: none; align-items: center; gap: 16px; padding: 10px 14px 10px 18px; display: none; align-items: center; gap: 16px;
box-shadow: 0 8px 24px rgba(0,0,0,.18); z-index: 100; font-size: 13px; box-shadow: 0 8px 24px rgba(0,0,0,.18); z-index: 100; font-size: 13px;
} }
body.edit-mode .bulk-bar { display: inline-flex; } /* 显隐绑定「是否有选中」而非编辑模式 → 退选即消失 */
.bulk-bar.show { display: inline-flex; }
.bulk-bar .ct { font-family: var(--font-mono); letter-spacing: .02em; } .bulk-bar .ct { font-family: var(--font-mono); letter-spacing: .02em; }
.bulk-bar .ct b { color: var(--heat); font-weight: 700; padding: 0 3px; } .bulk-bar .ct b { color: var(--heat); font-weight: 700; padding: 0 3px; }
.bulk-bar .sep { width: 1px; height: 18px; background: rgba(255,255,255,.16); } .bulk-bar .sep { width: 1px; height: 18px; background: rgba(255,255,255,.16); }
+9 -7
View File
@@ -210,13 +210,15 @@ export function AssetFactoryPage({ navigate }: { navigate: (page: Page) => void
<span className="factory-tag">{card.tag}</span> <span className="factory-tag">{card.tag}</span>
<div className="factory-title">{card.title}</div> <div className="factory-title">{card.title}</div>
<div className="factory-desc">{card.desc}</div> <div className="factory-desc">{card.desc}</div>
</div> {/* CTA factory-text ( asset-factory.html):,
<div className="factory-cta"> factory-body height:100% + margin-top:auto */}
<button className="btn btn-primary btn-lg" type="button" onClick={() => navigate(card.page)}> <div className="factory-cta">
<button className="btn btn-primary btn-lg" type="button" onClick={() => navigate(card.page)}>
<ArrowRight size={13} />
</button> <ArrowRight size={14} />
<span className="cost">[ {card.cost} ]</span> </button>
<span className="cost">[ {card.cost} ]</span>
</div>
</div> </div>
</div> </div>
</article> </article>
+1 -1
View File
@@ -695,7 +695,7 @@ export function LibraryPage({ onUpload, onDelete }: { onUpload: (formData: FormD
<Pager page={curPage} total={total} pageSize={LIB_PAGE_SIZE} onChange={setPage} /> <Pager page={curPage} total={total} pageSize={LIB_PAGE_SIZE} onChange={setPage} />
{/* 编辑模式浮动批量操作栏(scope 到资产库;删除/清空/完成) */} {/* 编辑模式浮动批量操作栏(scope 到资产库;删除/清空/完成) */}
<div className="lib-bulk-bar" role="toolbar" aria-label="批量操作"> <div className={`lib-bulk-bar${selected.size > 0 ? " show" : ""}`} role="toolbar" aria-label="批量操作">
<span className="ct"> <b>{selected.size}</b> </span> <span className="ct"> <b>{selected.size}</b> </span>
<button className="clear-sel" type="button" onClick={() => setSelected(new Set())}></button> <button className="clear-sel" type="button" onClick={() => setSelected(new Set())}></button>
<span className="sep" /> <span className="sep" />
+1 -1
View File
@@ -2998,7 +2998,7 @@ export function PipelinePage(props: {
<div className="video-meta">{seg.target_duration_seconds}s · {timeline?.resolution || "1080×1920"} · ~¥0.45{seg.error_message ? ` · ${seg.error_message}` : ""}</div> <div className="video-meta">{seg.target_duration_seconds}s · {timeline?.resolution || "1080×1920"} · ~¥0.45{seg.error_message ? ` · ${seg.error_message}` : ""}</div>
<div className="video-actions"> <div className="video-actions">
<button className="btn btn-ghost btn-sm" type="button" data-vstop disabled={loading || busy} onClick={() => guardGen(shots.filter((s) => s.sort_order === seg.sort_order), () => onSubmitVideo(seg.id, `${videoPrompt}${seg.sort_order + 1} 段,时长 ${seg.target_duration_seconds}`))}>{busy ? "生成中…" : "重跑"}</button> <button className="btn btn-ghost btn-sm" type="button" data-vstop disabled={loading || busy} onClick={() => guardGen(shots.filter((s) => s.sort_order === seg.sort_order), () => onSubmitVideo(seg.id, `${videoPrompt}${seg.sort_order + 1} 段,时长 ${seg.target_duration_seconds}`))}>{busy ? "生成中…" : "重跑"}</button>
<button className="btn btn-ghost btn-sm" type="button" data-vstop disabled={loading} onClick={() => triggerVideoUpload(seg.id)}></button> {/* AI 生成片段不需单卡上传(自定义替换走 queue-bar 全局上传);移除多余「上传」按钮 */}
<span className="spacer"></span> <span className="spacer"></span>
{url {url
? <a className="btn btn-ghost btn-sm" href={url} target="_blank" rel="noreferrer" data-vstop></a> ? <a className="btn btn-ghost btn-sm" href={url} target="_blank" rel="noreferrer" data-vstop></a>
+1 -1
View File
@@ -254,7 +254,7 @@ export function ProductsPage({ products, projects = [], loading = false, navigat
</div> </div>
{/* 编辑模式浮动操作条 */} {/* 编辑模式浮动操作条 */}
<div className="bulk-bar" role="toolbar" aria-label="批量操作"> <div className={`bulk-bar${selected.size > 0 ? " show" : ""}`} role="toolbar" aria-label="批量操作">
<span className="ct"> <b>{selected.size}</b> </span> <span className="ct"> <b>{selected.size}</b> </span>
<button className="clear-sel" type="button" onClick={() => setSelected(new Set())}></button> <button className="clear-sel" type="button" onClick={() => setSelected(new Set())}></button>
<span className="sep" /> <span className="sep" />
+2 -2
View File
@@ -706,8 +706,8 @@ export function ProjectsPage({ products, projects, loading = false, navigate, op
<Pager page={curPage} total={filtered.length} pageSize={PROJ_PAGE_SIZE} onChange={setPage} /> <Pager page={curPage} total={filtered.length} pageSize={PROJ_PAGE_SIZE} onChange={setPage} />
{filtered.length === 0 && <EmptyPanel title="当前筛选下没有项目" action="新建视频项目" onAction={() => navigate("projectWizard")} />} {filtered.length === 0 && <EmptyPanel title="当前筛选下没有项目" action="新建视频项目" onAction={() => navigate("projectWizard")} />}
{/* 批量管理栏 · 编辑态吸底浮动(显隐由 body.edit-mode 控)· 蓝本同 products-page */} {/* 批量管理栏 · 吸底浮动(显隐绑「有无选中」)· 蓝本同 products-page */}
<div className="bulk-bar" role="toolbar" aria-label="批量管理"> <div className={`bulk-bar${selected.size > 0 ? " show" : ""}`} role="toolbar" aria-label="批量管理">
<span className="ct"> <b>{selected.size}</b> </span> <span className="ct"> <b>{selected.size}</b> </span>
<button className="clear-sel" type="button" disabled={selected.size === 0} onClick={() => setSelected(new Set())}></button> <button className="clear-sel" type="button" disabled={selected.size === 0} onClick={() => setSelected(new Set())}></button>
<span className="sep" /> <span className="sep" />
+165
View File
@@ -0,0 +1,165 @@
# UI 还原核对清单 · 2026-06-20
> 用户(UI设计师)实测走查反馈,5 个只读代理对照 React 真代码 vs HTML 标准答案核实。
> **本轮只核对+对齐,未改任何代码。** 对齐后再按批开工。
图例:🔴 明显错/影响使用 · 🟡 视觉不达标 · 🟢 小细节 · ❓ 需用户拍板
---
## A. 商品库(products.tsx / products-page.css)
| # | 现象 | 真因 | 怎么改 |
|---|---|---|---|
| A1 🔴 | 选中商品后底部批量菜单是**白底**,HTML 是黑底 | `.bulk-bar` 全局裸类,`admin-page.css:232` 又定义一份白底(`--surface-raised`)且 main.tsx 最后加载,盖掉了商品库/项目/资产库三处黑底 | 把 admin-page.css 的 `.bulk-bar` 收窄作用域(`.admin-app .bulk-bar` 或改名),还原三处黑底 |
| A2 🔴 | 退出选择后,批量菜单**一直不消失** | 批量栏显隐绑的是「编辑模式 `body.edit-mode`」,跟「有没有选中」无关;清空只清 selected 不退编辑态 | 改成「选中数 >0 才显示」(对齐 admin-reviews 的 `selected.size>0` 写法),或退出编辑态时收起 |
## B. 新建商品(product-create-drawer.tsx)
| # | 现象 | 真因 | 怎么改 |
|---|---|---|---|
| B1 🔴 | 上传图片没传上、无任何提示,连点 2-3 次才成 | 选图阶段只做本地预览;真实上传在「创建商品后」逐张串行 await,**无 loading、无 catch**,失败被静默吞掉 | 上传加 loading 态 + 失败 toast + 失败可重试 |
| B2 🔴 | 明明加了图,点创建却提示「没有图片/未创建成功」 | 创建商品后才异步传图,**没等传完就回调** `onCreated`;某张传失败时商品已建但无图 | 改 `Promise.all` 等全部传完再回调;失败整体提示+不静默 |
## C. 商品详情 · AI 素材分类乱(products.tsx:550-555 + 后端 assets)
现象:分镜图→标成「平台套图」、场景图→标成「平台套图」、人物三视图→标成「模特上身图」。
真因(两层):
1. **后端分类枚举太粗**:`Asset.Category` 只有 person/scene/product_image/video_clip/final_video/upload/uncategorized,**没有** tri_view(三视图)/storyboard(分镜)/background。于是三视图被塞进 person、分镜/场景都塞进 scene。
2. **前端标签文案也不对**:`scene→"平台套图"`(用户要「场景图」)、`person→"模特上身图"`(三视图不该叫这个)。
怎么改:❓需先和你定**分类口径**——平台到底要哪几类素材标签(建议:商品图/场景图/分镜图/人物三视图/模特图/背景图/视频),然后后端补 category 枚举 + 生成时打对标,前端标签表跟着改。**这条要后端+前端一起动,改前必须对齐。**
## D. 归档概念 → 删除 + 垃圾桶(全平台)❓大改
现状:底层**本就是软删**(Product.status=archived / Asset.is_deleted / Notification.archived_at),只是 UI 文案/交互叫「归档」。命中点:
- 商品详情素材三态 `通过/不通过/归档`(products.tsx:524-540)— 注:这其实是借审核状态凑的三态,「归档」语义勉强
- 消息中心「归档」按钮(messages.tsx:227,389)— 真软删,但**没有恢复入口**
- 商品「删除」确认文案写「不可撤销」(其实是软删)
要你拍板的范围:
- 是只把文案从「归档」改成「删除」+ 加个「垃圾桶/回收站」可恢复?
- 还是要做完整回收站页(列已删 + 还原 + 彻底删)?后端要补「垃圾桶列表 + 恢复」端点。
- 素材那个三态(通过/不通过/归档)里的「归档」要不要也去掉、改两态?
## E. 视频项目流水线(pipeline.tsx / pipeline-page.css)
| # | 现象 | 真因 | 怎么改 |
|---|---|---|---|
| E1 🟡 | 每场卡样式跟 HTML 不一样 | 场景名字号 12/字重 600(应 11.5/500)、时间标签字号偏大 | 调字号字重对齐 HTML |
| E2 🟡 | 故事板舞台多了**灰底大框**,HTML 是干净白底 | `.sb-canvas` 带 border+圆角,外层 content 灰底透出 | 去 border/圆角,舞台用白底、对齐 HTML padding |
| E3 🟡 | 每场显示 `#1 #2`,HTML 显示**多少秒~多少秒** | 渲染写死序号 `#{sort+1}`,HTML 用时间区间 | 改显示 start~end 秒(需 frame 带时间字段) |
| E4 🟡 | 「绑定资产」前是个白圆点,像能点 | 渲染用 `.dotc` 占位圆点 | 改成**已引用资产的缩略图**(数据里已有 adopted url) |
| E5 🔴 | 视频卡上多了「上传」按钮(AI 生成不该有) | pipeline.tsx:3001 多渲染了上传按钮 | 删掉单卡上传(全局上传入口保留) |
| E6 🟡 | 视频卡标题和右侧状态顶端对齐、高度不齐 | `.video-card-head``align-items:flex-start` | 改 `center` 垂直居中 |
## F. 基础资产页(library.tsx / library-page.css / actor-library.tsx)
| # | 现象 | 真因 | 怎么改 |
|---|---|---|---|
| F1 🟡 | 「缺三视图」小标签样式不对 | `.tri-missing-badge` 现为橙底白字 r-sm;应白底琥珀字+琥珀边+胶囊 | 改回白底琥珀描边胶囊 |
| F2 🟡 | 人物提示词框高度随字数变 | textarea `resize:vertical` + 高度自适应 | 固定到「3 行字」高度、`resize:none` |
| F3 🟡 | 资产详情**例图容器比例错**(应 9:16 完整展示) | `.adx-lead-img``aspect-ratio:3/4` + cover(裁切) | 改 9/16 + contain 完整展示 |
| F4 🟡 | **三视图容器比例错**(应 16:9 完整) | `.adx-tri-cell` 默认 3/4 | 改默认 16/9 |
| F5 🟢 | 「重跑例会」按钮要放右边 | 现靠左 | 容器右对齐 `margin-left:auto` |
| F6 🟡 | 角色例会提示词栏能拖动改大小 | `.ad-detail-prompt` `resize:vertical` | 改 `resize:none` 固定 |
| F7 🟡 | 三视图上方两按钮 hover 反色后 **icon 没变白** | `.ad-icon-btn:hover` 只改字色,没给底色反转,icon 跟随 currentColor 但底也没变橙 | hover 给橙底+白 icon(`background:heat;color:white`) |
| F8 🟡 | 商品卡 vs 人物卡**块大小不一** | 资产网格 180px/14gap/1:1;人物网格 140px/12gap/3:4 | 同页统一尺寸口径 |
## G. 图片生成页(ai-tools.tsx)🟢 基本达标
逐区块对照后 >99% 对齐 HTML(image-optimize)。唯一细节:结果图右上操作按钮 28px,HTML 是 26px。
> 注:用户感觉「完全不一样」,但代码层面高度一致——可能用户看的是**另一个模式**(模特图/平台封面表单态)或某个空态。**建议改前先跟用户确认是哪个画面**,再针对性截图比对。❓
## H. 拼接导出 → 雪藏到 V2(stage-config.ts / pipeline.tsx)❓
要做:阶段菜单去掉「拼接导出」选项 + 整页不渲染(**不删代码**,留 V2)。最小改动:菜单数组去掉 export 项 + Stage5 渲染条件关掉 + Stage4→5 的进入按钮隐藏。组件文件全保留。
---
---
## 📊 平台产出物盘点(测试库实测 · 2026-06-20,共 505 个 Asset)
| 实际产出物 | 落在 category(数量) | 现显示标签 | 由哪条流程产 | 名称样本 |
|---|---|---|---|---|
| 商品基础图 + 图片创作 + **平台套图** | product_image(154) | 商品图 | 商品基础资产 / 图片创作(image) / **平台套图(cover→此桶)** | 杂鱼煲-主图、AI生成·平台套图·4、AI生成·图片创作·1 |
| 人物基础图 + 模特上身图 + **三视图** | person(131) | 模特上身图 | 人物基础资产 / 模特上身(model) / **三视图(services.py:854 塞此)** | AI生成·模特上身图·1、v1-三视图 |
| 场景图 + **故事板分镜** | scene(130) | 平台套图 | 场景基础资产 / **故事板每镜(services.py:1147 塞此)** | v1-storyboard-4 |
| 视频片段 | video_clip(49) | — | 视频阶段每段 | v1-segment-4 |
| 最终成片 | final_video(24) | — | 拼接导出(export.py:508) | v1-final.mp4 |
| 配音 + BGM(音频) | uncategorized(14)+upload(3) | — | 配音 / BGM | 配音·场4·句2、BGM·轻电子节奏 |
**乱点总结:**
- `person` 桶混了 人物基础图 / 模特上身图 / **三视图** 三种 → 三视图被叫"模特上身图"。
- `scene` 桶混了 场景图 / **故事板分镜** → 都被叫"平台套图"。
- **名实错位**:平台套图(cover)落进 `product_image` 桶(services.py:1499)显示"商品图";而 `scene` 的标签却叫"平台套图"。
- 音频(配音/BGM)散落 uncategorized + upload,没有独立类。
- 现 7 个枚举太粗、且一桶多用 → 标签必然错。要么细化 category,要么靠 name/metadata 二次区分。
## 归档逻辑现状(三处"归档"各不相同)
1. **消息中心「归档」= 真软删**:点了 `archived_at` 打时间戳,列表查询直接过滤掉(ops/views.py:256),消息从 UI **消失且无任何恢复/回收站入口**,只有后台 Django admin 能看到。实测库 0 条已归档(功能在、没人用过)。
2. **商品「归档」(status=archived)= 定义了但前端没用**:商品实际走「删除」(确认文案写"不可撤销"=按硬删理解);实测 35 个全 active、0 archived。
3. **商品详情素材卡「归档」= 假归档**:只是审核状态映射的中性标签(active→通过/failed→不通过/其它→归档),不动任何数据。
## 图片生成页(asset-factory)真实差异(订正:不是像素间距)
对照截图,真问题在**任务中心的任务卡内容**,不是排版间距(排版 99% 对):
- 任务卡标题渲染 `TASK_TYPE_LABEL[task_type] || task_type`(ai-tools.tsx:313),但库里 `task_type` 实际是 `product_image/person_image` 等,**不在标签表(只有 model/platform/image…)** → 直接显示生英文 "product_image // product_image"。
- HTML 标准:任务卡标题是「商品名 × 模特/平台」+ 副标「类型 · N张」(如"南卡 Lite Pro 蓝牙耳机 × 天猫 / 平台套图·4张")。
- 默认 `view="grid"` 网格(ai-tools.tsx:152)+ 失败任务无图 → 一片占位斜纹块;HTML 默认列表、干净。
---
---
# ★ 对齐结论(2026-06-20 用户拍板,以下为准)
## 素材分类方案(取代 C/D 旧讨论)
**电商物料类(图片生成产出):**
1. 模特上身图 — 独立类
2. 平台套图 — 独立类
3. 自由创作图 — 独立类(含详情图/海报等,合并到一起)
**视频项目类:**
4. **模特** — 实体类(命名最终定为「模特」,不叫人物/演员)。我自己生成的 + 官方给的**都叫模特**,只用「官方」徽章区分来源。
- 一个模特实体 = 立绘 + 三视图 +(将来)声音,**都挂在该模特身上**。
- 模特两个用途:① 生成模特上身图(配具体商品)② 演剧(视频项目当角色)。
5. 分镜图 — 独立类
6. 视频片段 — 独立类
7. ~~最终成片~~ — 拼接 1.0 已隐藏,此类**先隐藏**,以后再加
**基础资产 / 参考元素:**
8. 模特声音 — 每个模特一个声音,**挂在模特实体下**(不再纠结独立 or 打包,就跟着模特走)。**回头做**(还要在"参考元素"里加"声音"选项,方便统一生成);本轮先知道有这个类,不动。
> 注:「模特」(实体/人)与「模特上身图」(产出物=模特+具体商品、带商品信息、可能成套)是两码事,不冲突。模特上身图名字以后可能再改。
## 演员库("模特库")现状三个毛病(待改)
弹窗入口:视频项目流程第4步 人物卡替换/新增、图片生成→模特上身图→选择模特。判定"谁是模特"= 凡 category=person 有图的资产。问题:
1. 🔴 **三视图混进模特卡**:三视图也打 person 类(services.py:854),被当成独立"模特"卡和立绘平级。应:三视图单独成类、从属于模特。
2. 🔴 **官方/自定义分错**:`isPreset` 把"凡 ai_generated"都算官方预设(actor-library.tsx:13),用户自建也是 ai_generated → 全进"平台预设","我的演员"几乎永空。应:按真实来源标记(官方 badge)区分。
3. 🟡 **立绘↔三视图父子关系弱**:仅靠 metadata.triview_of 弱挂,演员库没用它,平铺所有 person。应:建明确父子,模特库只列模特本体(立绘)。
**砍掉:** 配音 — 暂不做(原 uncategorized 里的配音/BGM 先不管)。
**资产库改名:** "成片" tab → 改为 **"素材"**。
> 落地影响:后端 `Asset.Category` 枚举要按上面细化,各生成流程(三视图 854 / 故事板 1147 / cover 1499 等)打对标,前端 `PD_ASSET_TYPE_LABEL` 跟着改。最终成片类前端先隐藏。
## 归档 → 删除 + 垃圾桶
- **商品删除 → 进垃圾桶**;**左侧主菜单新增「垃圾桶」入口**(可恢复)。
- 商品详情素材卡的「归档」**不是用户可点的按钮** → 改为**审核状态展示**:团队管理员审核 → 通过 / 不通过;用户侧只读看状态(对接已有 review_status 绿/红盾语义)。
- **消息中心:本轮先别动**(用户觉得现状有较大问题,回头单独改)。
## 图片生成页(asset-factory)— 排版还原(取代 G 旧结论)
确认是 asset-factory 落地页,任务中心布局 OK(之前看到的差异是切了视图没切回),要改:
- A. 标题「图片生成」+ 下方小字**整体位置偏低** → 上移对齐 HTML。
- B. 三张卡(模特上身图/平台套图/图片创作)**未左对齐 + 间距不对 + 卡片高度偏高** → 对齐 HTML(统一左对齐、统一高度、收间距)。
- C. 「开始生成」按钮与上方描述行**间距过宽** → 收紧。
- D. 任务卡标题/副标内容应为「**商品名 × 模特/平台**」+「类型 · N 张」,现在显示生英文 `product_image` → 改(内容层,跟分类一起理)。
## 本轮可立即开工(不依赖进一步拍板)
A1 白底菜单、A2 菜单不消失、B1/B2 上传、E1-E6 流水线、F1-F8 基础资产、H 拼接导出雪藏、图片生成页 A/B/C 排版、左侧加垃圾桶 + 商品删除进垃圾桶、素材卡归档改审核状态。
## 待后续单独排期(本轮不碰)
人物声音类 + 参考元素加声音、模特库完整功能(官方模特库)、消息中心重做、最终成片类恢复(随拼接 2.0)。