fix: 平台套图工作台 YYX 测试清单一轮修复(13 条)

- 平台改单选;结果分组/标签名对应所选平台
- 工作台结果区按当前商品隔离(productBatches)
- 平台套图去掉「详情排版」,只出平台头图
- 部分失败给失败占位 +「重跑这张」单张重跑;部分成功批次恢复加入资产库
- 提示词框加大;生图模型选择移入框内作胶囊靠左置底
- 去掉生成按钮下方说明文字
- 工作台「新建商品」改弹抽屉(复用 ProductCreateDrawer),不再跳商品库
- 右上搜索恒显全部平台、改为筛选结果图(按提示词)
- 结果卡参考即梦:显示提示词/平台/张数

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-27 15:18:21 +08:00
co-authored by Claude Opus 4.8
parent 8f32ad69d3
commit 217d24f316
4 changed files with 94 additions and 21 deletions
+2 -2
View File
@@ -692,7 +692,7 @@ export function App() {
function renderPage() {
switch (page) {
case "dashboard":
return <Dashboard products={products} projects={projects} productTotal={productTotal} projectTotal={projectTotal} billing={billing} userName={currentUser.username} loading={!dataLoaded} navigate={navigate} />;
return <Dashboard products={products} projects={projects} productTotal={productTotal} projectTotal={projectTotal} billing={billing} userName={currentUser.username} loading={!dataLoaded} navigate={navigate} onCreateProduct={(payload) => action(() => api.createProduct(payload), "")} />;
case "products":
return (
<ProductsPage
@@ -855,7 +855,7 @@ export function App() {
case "settingsNotify":
return <SettingsPage user={currentUser} team={currentTeam} initialSection="notify" preferences={preferences} sessions={sessions} onSavePreferences={savePreferences} onRevokeSession={revokeSession} onRevokeOthers={revokeOtherSessions} onSaveProfile={saveProfile} onChangePassword={changeOwnPassword} onUploadAvatar={uploadOwnAvatar} onResetAvatar={resetOwnAvatar} onNotify={(text) => setNotice({ type: "success", text })} onLogout={logout} />;
default:
return <Dashboard products={products} projects={projects} productTotal={productTotal} projectTotal={projectTotal} billing={billing} loading={!dataLoaded} navigate={navigate} />;
return <Dashboard products={products} projects={projects} productTotal={productTotal} projectTotal={projectTotal} billing={billing} loading={!dataLoaded} navigate={navigate} onCreateProduct={(payload) => action(() => api.createProduct(payload), "")} />;
}
}
+40
View File
@@ -616,6 +616,22 @@
.image-workbench .p-logo-amz { background: #ff9900; }
.image-workbench .p-logo-al { background: #2c4af1; }
/* ── 提示词框(YYX#7 加大)+ 框内生图模型胶囊(YYX#10 靠左置底)── */
.image-workbench .iw-prompt-box { position: relative; }
.image-workbench .iw-prompt-text {
min-height: 148px; /* 原共享 .textarea 88px → 工作台提示词框加大 */
padding-bottom: 42px; /* 给底部胶囊让位,文字不被遮 */
}
.image-workbench .iw-prompt-bar {
position: absolute;
left: 10px; bottom: 10px;
display: flex; align-items: center;
}
.image-workbench .iw-model-pill { position: relative; }
.image-workbench .iw-model-pill .chip { height: 26px; }
/* 胶囊在框底 → 下拉菜单向上弹,避免被框/CTA 遮住 */
.image-workbench .iw-model-pill .chip-menu { top: auto; bottom: calc(100% + 4px); }
/* 左栏底部 · 立即生成(主 CTA · 通栏) */
.image-workbench .iw-cta { margin-top: auto; padding-top: 14px; }
.image-workbench .iw-cta .btn {
@@ -669,6 +685,30 @@
font-weight: 500;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* YYX#13:提示词整段较长 → 顶对齐 + 最多两行省略,不挤掉平台/张数行 */
.image-workbench .iw-pv-h .pv-line .v.pv-prompt {
font-weight: 400; white-space: normal;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.image-workbench .iw-pv-h .pv-line:has(.pv-prompt) { align-items: flex-start; }
/* YYX#5:单张失败占位 —— 灰底 + ✕ + 「重跑这张」 */
.image-workbench .gen-image.failed .placeholder { background: var(--background-lighter); }
.image-workbench .gen-failed {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
color: var(--black-alpha-48);
}
.image-workbench .gen-failed .gf-text { font-size: 12px; }
.image-workbench .gen-failed .gf-retry {
display: inline-flex; align-items: center; gap: 4px;
height: 26px; padding: 0 10px;
border: 1px solid var(--border-faint); border-radius: 999px;
background: var(--surface); color: var(--accent-black);
font-size: 12px; cursor: pointer;
transition: background var(--t-base), border-color var(--t-base);
}
.image-workbench .gen-failed .gf-retry:hover { background: var(--heat-12); border-color: var(--heat); color: var(--heat); }
/* 结果区:复用 §4.18 .gen-card 规范结构(scoped 实现 · 仅 token) */
.image-workbench .gen-card {
+36 -17
View File
@@ -762,7 +762,6 @@ export function ImageWorkbenchPage({
(mode !== "model" || (!!product?.id && pickedIds.length > 0)) &&
// 平台套图:必须选商品 + 至少一个平台(P0③ 多选)
(mode !== "cover" || (!!product?.id && pickedIds.length > 0));
const anyGenerating = batches.some((b) => b.status === "generating");
/* (33): ai-tools key, App `airshelf:imgwb:{mode}`
;App ,"刷新时仍在跑"() */
@@ -1291,16 +1290,11 @@ export function ImageWorkbenchPage({
1 ·N×, */}
<div className="iw-pv-h">
<Quote className="quote-icon" />
{/* YYX#13:参考即梦 —— 显示用户输入的提示词 / 选择的平台 / 生成的张数 */}
<div className="pv-line">
<span className="k"></span>
<span className="v">{batchProductTitle(batch)}</span>
<span className="k"></span>
<span className="v pv-prompt">{batch.prompt || "—"}</span>
</div>
{mode === "model" && batch.modelName && (
<div className="pv-line">
<span className="k"></span>
<span className="v">{batch.modelName}</span>
</div>
)}
{mode === "cover" && (
<div className="pv-line">
<span className="k"></span>
@@ -1313,8 +1307,14 @@ export function ImageWorkbenchPage({
</span>
</div>
)}
{mode === "model" && batch.modelName && (
<div className="pv-line">
<span className="k"></span>
<span className="v">{batch.modelName}</span>
</div>
)}
<div className="pv-line">
<span className="k"></span>
<span className="k"></span>
<span className="v">
{batch.count} · {batch.ratio} · {generating ? "生成中" : failed ? "失败" : "已完成"}
{!generating && !failed && batch.results.some((a) => a.in_library)
@@ -1902,12 +1902,31 @@ export function ImageWorkbenchPage({
)}
<div className="iw-sub">
<div className="iw-sub-h">// 提示词 · 描述你想要的画面</div>
<textarea
className="textarea"
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
placeholder={`例如:${meta.promptTemplate(product?.title || "商品")}`}
/>
{/* YYX#7:提示词框加大;YYX#10:生图模型选择作胶囊嵌在框内靠左置底 */}
<div className="iw-prompt-box">
<textarea
className="textarea iw-prompt-text"
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
placeholder={`例如:${meta.promptTemplate(product?.title || "商品")}`}
/>
<div className="iw-prompt-bar">
<div className={`chip-wrap iw-model-pill${genModelOpen ? " open" : ""}`} data-filter="genmodel">
<button className="chip" type="button" title="生图模型" onClick={() => setGenModelOpen((v) => !v)}>
<Sparkles size={12} />
<span className="chip-label">{GEN_MODEL_OPTIONS.find((o) => o.value === genModel)?.label || "火山 Seedream"}</span>
<svg className="caret" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M4 6l4 4 4-4" /></svg>
</button>
<div className="chip-menu">
{GEN_MODEL_OPTIONS.map((o) => (
<div className={`mi${genModel === o.value ? " selected" : ""}`} key={o.value} role="button" tabIndex={0} onClick={() => { setGenModel(o.value); setGenModelOpen(false); }}>
<svg className="mi-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2"><path d="M3 8l3.5 3.5L13 5" /></svg>{o.label}
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
@@ -1917,7 +1936,7 @@ export function ImageWorkbenchPage({
<WandSparkles size={13} />
( ¥{(candidateCount * (mode === "model" ? 0.3 : 0.5)).toFixed(2)})
</button>
<div className="iw-cta-hint">// 生成即扣费 · 生成的图先在工作台 · 点「加入资产库」才进资产库列表{anyGenerating ? " · 有批次生成中,可继续提交" : ""}</div>
{/* YYX#9:去掉生成按钮下方那行说明文字 */}
</div>
</div>
+16 -2
View File
@@ -6,8 +6,9 @@ import { SkeletonRows } from "../components/loading";
import { money, stageMeta, statusPill } from "./stage-config";
import { Progress } from "../components/pipeline-stage";
import { IconKitSvg } from "../components/IconKitSvg";
import { ProductCreateDrawer, type ProductCreatePayload } from "../components/product-create-drawer";
export function Dashboard({ products, projects, productTotal, projectTotal, billing, userName, loading = false, navigate }: {
export function Dashboard({ products, projects, productTotal, projectTotal, billing, userName, loading = false, navigate, onCreateProduct }: {
products: Product[];
projects: Project[];
productTotal?: number; // 后端真实总数(分页 count),用于「N 个/SKU」展示
@@ -16,7 +17,11 @@ export function Dashboard({ products, projects, productTotal, projectTotal, bill
userName?: string;
loading?: boolean;
navigate: (page: Page) => void;
// YYX#11:工作台点「新建商品」直接弹抽屉(复用商品库同一组件),而非跳商品库
onCreateProduct?: (payload: ProductCreatePayload) => Promise<Product | null | undefined> | void;
}) {
// YYX#11:新建商品抽屉开关
const [createDrawer, setCreateDrawer] = useState(false);
const projCount = projectTotal ?? projects.length;
const prodCount = productTotal ?? products.length;
// 资产总数:走轻量 summary 接口(各 tab 计数之和),不再吃全局 assets 全量数组
@@ -58,7 +63,7 @@ export function Dashboard({ products, projects, productTotal, projectTotal, bill
<div className="sub"><span className="mono">// {dateMono}</span><span>·</span><span>你有 <b>{running} 个项目</b> 正在进行中</span></div>
</div>
<div className="actions">
<button className="btn" type="button" onClick={() => navigate("products")}>
<button className="btn" type="button" onClick={() => (onCreateProduct ? setCreateDrawer(true) : navigate("products"))}>
<IconKitSvg name="productPlus" size={16} />
</button>
@@ -70,6 +75,15 @@ export function Dashboard({ products, projects, productTotal, projectTotal, bill
</div>
<div className="stats with-corners"><span className="corner-tr">+</span><span className="corner-bl">+</span><KpiStat label="总项目" badge="ALL" value={projCount} delta={totalDelta} /><KpiStat label="进行中" badge="WIP" value={running} /><KpiStat label="成片" badge="DONE" value={completed} /><button className="stat" type="button" onClick={() => navigate("account")}><div className="lbl"> <span className="badge">¥</span></div><div className="v">{money(billing?.account.balance)}</div><div className="bar"><span style={{ width: `${reservedPct}%` }} /></div><div className="sub"> {money(reservedNum)} / {money(fundsTotal)}</div></button></div>
<div className="dash-grid"><div><div className="section-h"><h2></h2><button className="more" type="button" onClick={() => navigate("projects")}>[ ALL · {projCount} ] </button></div><div className="card-hard">{loading && projects.length === 0 ? <SkeletonRows count={4} /> : projects.slice(0, 6).map((project) => <button className="recent-row" key={project.id} type="button" onClick={() => navigate("pipeline")}><div className="placeholder thumb"><span className="ph-frame">9:16</span></div><div className="recent-meta"><div className="name">{project.name}</div><div className="sub">{[productTitle(project.product), "AI 全生", project.video_segment_count ? `${project.video_segment_count}` : null].filter(Boolean).join(" / ")}</div></div><Progress status={project.current_stage} /><span className={`pill ${statusPill(project.status)}`}><span className="dot" />{stageMeta[project.current_stage]?.label || project.current_stage}</span><span className="btn btn-sm"></span></button>)}</div></div><div style={{ display: "flex", flexDirection: "column", gap: 24 }}><div><div className="section-h"><h2></h2><span className="more">[ /shortcuts ]</span></div><div className="shortcuts"><Shortcut name="package" title="" desc={`${prodCount} SKU`} onClick={() => navigate("products")} /><Shortcut name="images" title="" desc={`${assetCount ?? ""} `} onClick={() => navigate("library")} /><Shortcut name="creditCard" title="" desc={money(billing?.account.balance)} onClick={() => navigate("account")} /><Shortcut name="clapperboard" title="" desc={`${projCount} `} onClick={() => navigate("projects")} /></div></div><div><div className="section-h"><h2></h2><span className="more">[ FAQ ]</span></div><div className="tip"><strong></strong> <span className="mono">[ ]</span> token </div></div></div></div>
{/* YYX#11:新建商品抽屉 · 复用商品库同一组件,创建成功后跳商品库看新商品 */}
{onCreateProduct && (
<ProductCreateDrawer
open={createDrawer}
close={() => setCreateDrawer(false)}
onCreate={onCreateProduct}
onCreated={() => { setCreateDrawer(false); navigate("products"); }}
/>
)}
</>
);
}