AirShelf/电商AI平台/product-create.html
iye cae935588b init: 电商AI平台 v2.1 静态设计稿
- 10 个页面 (工作台/项目/商品/流水线/资产/账户/创建向导)
- V2.1 Restraint 设计规范 (冷灰底 + #FA5D19 + 8px 圆角)
- 完整 design-system.html 组件库参考
- SVG line icon · stroke 1.5 全合规

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:55:11 +08:00

1595 lines
67 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>新建商品 · 流·Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/restraint.css">
<style>
/* ============================================================
V2.1 product-create · 全屏新建商品工作台
- 顶部模式切换:[ 我有商品图 ] / [ AI 帮我生成图 ]
- 左侧 step 导航 + 已选素材
- 中央 canvas
- 右侧 controls
- Step 3 内嵌子 Tab(① 挑选模特 → ② 生成上身图)
============================================================ */
body { background: var(--background-base); }
.wb {
height: 100vh;
display: grid;
grid-template-rows: 56px auto 1fr;
background: var(--background-base);
}
/* ─── Top bar ─── */
.wb-top {
padding: 0 24px;
background: var(--surface);
border-bottom: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 18px;
}
.wb-top .home {
display: flex; align-items: center; gap: 8px;
color: var(--black-alpha-56);
font-size: 13px;
cursor: pointer;
transition: color var(--t-base);
}
.wb-top .home:hover { color: var(--accent-black); }
.wb-top .home svg { width: 14px; height: 14px; }
.wb-top .ti {
font-size: 15px; font-weight: 600;
color: var(--accent-black);
display: flex; align-items: center; gap: 10px;
}
.wb-top .ti .name-edit {
background: transparent;
border: 1px solid transparent;
border-radius: var(--r-md);
padding: 4px 10px;
font-size: 15px; font-weight: 600;
color: var(--accent-black);
font-family: inherit;
width: 280px;
transition: border-color var(--t-base), background var(--t-base);
}
.wb-top .ti .name-edit:hover { border-color: var(--black-alpha-12); background: var(--black-alpha-3); }
.wb-top .ti .name-edit:focus { border-color: var(--heat-40); background: var(--surface); box-shadow: inset 0 0 0 1px var(--heat-40); }
.wb-top .ti .mode-pill {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--heat);
background: var(--heat-12);
border: 1px solid var(--heat-20);
border-radius: var(--r-sm);
padding: 2px 8px;
letter-spacing: .04em;
font-weight: 500;
}
.wb-top .right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.wb-top .x {
width: 36px; height: 36px;
display: grid; place-items: center;
cursor: pointer;
color: var(--black-alpha-56);
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
}
.wb-top .x:hover { color: var(--accent-crimson); background: var(--crimson-bg); border-color: var(--crimson-bd); }
.wb-top .x svg { width: 14px; height: 14px; }
/* ─── Mode switch row ─── */
.mode-row {
padding: 14px 24px;
background: var(--background-lighter);
border-bottom: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 14px;
}
.mode-row .lbl {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .04em;
flex-shrink: 0;
}
.mode-toggle {
display: inline-flex;
background: var(--surface);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-md);
padding: 3px;
gap: 2px;
}
.mode-toggle button {
padding: 6px 14px;
background: transparent;
border: 0;
border-radius: 6px;
font-size: 12.5px; font-weight: 500;
color: var(--black-alpha-56);
cursor: pointer;
font-family: inherit;
display: flex; align-items: center; gap: 6px;
transition: background var(--t-base), color var(--t-base);
}
.mode-toggle button:hover { color: var(--accent-black); }
.mode-toggle button.active { background: var(--heat); color: var(--accent-white); font-weight: 600; }
.mode-toggle button svg { width: 13px; height: 13px; }
.mode-row .mode-hint {
margin-left: auto;
font-size: 12.5px;
color: var(--black-alpha-56);
display: flex; align-items: center; gap: 8px;
}
.mode-row .mode-hint .mono {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .04em;
}
/* ─── Main split ─── */
.wb-main { display: grid; grid-template-columns: 280px 1fr 360px; min-height: 0; }
/* ─── Left sidebar ─── */
.wb-side {
border-right: 1px solid var(--border-faint);
background: var(--background-base);
overflow-y: auto;
padding: 18px 0;
}
.step-item {
padding: 14px 20px;
display: flex; align-items: flex-start; gap: 12px;
cursor: pointer;
border-left: 3px solid transparent;
position: relative;
transition: background var(--t-base);
}
.step-item:hover { background: var(--black-alpha-4); }
.step-item.active { background: var(--heat-12); border-left-color: var(--heat); }
.step-item.locked { opacity: .55; cursor: not-allowed; }
.step-item.locked:hover { background: transparent; }
.step-item .num {
width: 26px; height: 26px;
border: 1px solid var(--black-alpha-12);
background: var(--surface);
color: var(--black-alpha-48);
font-family: var(--font-mono); font-size: 11px; font-weight: 700;
border-radius: var(--r-md);
display: grid; place-items: center;
flex-shrink: 0;
}
.step-item.done .num { background: var(--accent-black); color: var(--accent-white); border-color: var(--accent-black); }
.step-item.active .num { background: var(--heat); color: var(--accent-white); border-color: var(--heat); }
.step-item .info { flex: 1; min-width: 0; }
.step-item .ti2 { font-size: 13.5px; font-weight: 600; color: var(--black-alpha-56); }
.step-item.active .ti2 { color: var(--accent-black); }
.step-item.done .ti2 { color: var(--accent-black); }
.step-item .sub {
font-size: 11.5px; color: var(--black-alpha-48);
margin-top: 4px;
font-family: var(--font-mono);
letter-spacing: .02em;
line-height: 1.5;
}
.step-item.done .sub { color: var(--black-alpha-56); font-family: var(--font-sans); letter-spacing: 0; }
/* Sub items under active step */
.substep-list { padding: 4px 20px 8px 56px; }
.substep-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
cursor: pointer;
font-size: 12.5px; color: var(--black-alpha-48);
border-left: 2px solid var(--border-faint);
margin-left: -4px;
transition: color var(--t-base), border-color var(--t-base);
}
.substep-item:hover { color: var(--black-alpha-56); border-left-color: var(--black-alpha-24); }
.substep-item.active { color: var(--heat); border-left-color: var(--heat); font-weight: 600; }
.substep-item.done { color: var(--accent-black); }
.substep-item .num-mini {
width: 18px; height: 18px;
border: 1px solid currentColor;
border-radius: var(--r-sm);
font-family: var(--font-mono); font-size: 9.5px; font-weight: 700;
display: grid; place-items: center;
flex-shrink: 0;
}
.substep-item.active .num-mini, .substep-item.done .num-mini {
background: currentColor;
color: var(--accent-white);
}
.substep-item.done .num-mini { background: var(--accent-black); border-color: var(--accent-black); }
.side-divider { height: 1px; background: var(--border-faint); margin: 14px 20px; }
.side-section-h {
padding: 4px 20px 8px;
font-family: var(--font-mono);
font-size: 10.5px; color: var(--black-alpha-48);
letter-spacing: .08em; text-transform: uppercase;
font-weight: 600;
}
.selected-assets-side { padding: 0 20px; }
.sel-asset-label {
font-size: 11.5px;
color: var(--black-alpha-48);
margin-bottom: 6px;
font-family: var(--font-mono);
letter-spacing: .02em;
}
.sel-asset-thumb { aspect-ratio: 4/5; margin-bottom: 14px; }
.sel-asset-thumb.square { aspect-ratio: 1; }
/* ─── Center canvas ─── */
.wb-canvas {
background: var(--background-base);
overflow-y: auto;
padding: 0;
display: flex; flex-direction: column;
min-width: 0;
}
/* Sub-tab strip on top (only in Step 3) */
.subtab-strip {
padding: 0 32px;
border-bottom: 1px solid var(--border-faint);
background: var(--surface);
display: flex; align-items: center; gap: 0;
position: sticky; top: 0; z-index: 5;
}
.subtab {
padding: 14px 18px;
font-size: 13px; font-weight: 500;
color: var(--black-alpha-56);
border-bottom: 2px solid transparent;
margin-bottom: -1px;
cursor: pointer;
display: flex; align-items: center; gap: 8px;
transition: color var(--t-base), background var(--t-base);
border-radius: var(--r-md) var(--r-md) 0 0;
}
.subtab:hover { color: var(--accent-black); background: var(--black-alpha-4); }
.subtab.active { color: var(--accent-black); border-bottom-color: var(--heat); }
.subtab.disabled { color: var(--black-alpha-32); cursor: not-allowed; }
.subtab.disabled:hover { background: transparent; color: var(--black-alpha-32); }
.subtab .num-circ {
width: 20px; height: 20px;
border: 1px solid currentColor;
border-radius: 50%;
font-family: var(--font-mono); font-size: 10px; font-weight: 700;
display: grid; place-items: center;
}
.subtab.done .num-circ { background: var(--accent-black); color: var(--accent-white); border-color: var(--accent-black); }
.subtab.active .num-circ { background: var(--heat); color: var(--accent-white); border-color: var(--heat); }
.subtab .arrow { color: var(--black-alpha-32); padding: 0 6px; }
.subtab-meta {
margin-left: auto;
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--black-alpha-48);
letter-spacing: .04em;
}
.subtab-meta .accent { color: var(--heat); font-weight: 700; }
/* Canvas inner */
.canvas-inner { padding: 32px; flex: 1; max-width: 1280px; }
.canvas-h {
margin-bottom: 24px;
display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
}
.canvas-h h2 { font-size: 24px; font-weight: 600; letter-spacing: -.018em; line-height: 1.25; color: var(--accent-black); }
.canvas-h p { font-size: 13.5px; color: var(--black-alpha-56); margin-top: 8px; line-height: 1.6; }
.canvas-h .step-tag {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--black-alpha-48);
padding: 4px 10px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
letter-spacing: .04em;
flex-shrink: 0;
}
/* Step pane switching */
.step-pane { display: none; }
.step-pane.active { display: block; animation: fade .2s ease; }
@keyframes fade {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.sub-pane { display: none; }
.sub-pane.active { display: block; animation: fade .2s ease; }
/* ─── Step 1: 商品信息 + 上传原图 ─── */
.form-card {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 28px;
max-width: 920px;
}
.s1-grid { display: grid; grid-template-columns: 280px 1fr; gap: 28px; }
.upload-c {
aspect-ratio: 4/5;
background: var(--surface);
border: 1.5px dashed var(--black-alpha-24);
border-radius: var(--r-md);
display: grid; place-items: center;
text-align: center;
cursor: pointer;
color: var(--black-alpha-56);
margin-bottom: 12px;
transition: border-color var(--t-base), background var(--t-base), color var(--t-base);
padding: 24px;
}
.upload-c:hover {
border-color: var(--heat);
background: var(--heat-8);
color: var(--heat);
}
.upload-c.has-file {
border-style: solid; padding: 0;
border-color: var(--heat-40);
background: var(--background-lighter);
}
.upload-c.has-file .placeholder { width: 100%; height: 100%; }
.upload-c .ic-cam {
width: 44px; height: 44px;
border: 1px solid currentColor;
border-radius: var(--r-md);
display: grid; place-items: center;
margin: 0 auto 14px;
}
.upload-c .ic-cam svg { width: 22px; height: 22px; }
.up-hint {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
text-align: center;
letter-spacing: .02em;
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.bullet-list { list-style: none; padding: 0; }
.bullet-list li {
display: flex; gap: 10px; align-items: center;
padding: 10px 12px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
margin-bottom: 6px;
font-size: 13px;
color: var(--accent-black);
}
.bullet-list .num {
width: 20px; height: 20px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
font-size: 11px; color: var(--black-alpha-56);
display: grid; place-items: center;
flex-shrink: 0;
font-family: var(--font-mono);
font-weight: 600;
}
/* ─── Step 2: 头图 4 选 1 ─── */
.big-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
max-width: 760px;
}
.big-card {
aspect-ratio: 1;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
cursor: pointer;
position: relative;
overflow: hidden;
transition: border-color var(--t-base), transform var(--t-fast);
}
.big-card:hover { border-color: var(--black-alpha-32); }
.big-card .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
.big-card.selected {
border: 2px solid var(--heat);
box-shadow: 0 0 0 4px var(--heat-12);
}
.big-card.selected::after {
content: '';
position: absolute; top: 12px; right: 12px;
width: 28px; height: 28px;
background: var(--heat);
color: var(--accent-white);
border-radius: 50%;
display: grid; place-items: center;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3.5 8.5l3 3 6-6' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-size: 16px;
background-repeat: no-repeat;
background-position: center;
}
.big-card .corner-info {
position: absolute; bottom: 12px; left: 12px;
background: rgba(255,255,255,.95);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
padding: 4px 10px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: .04em;
color: var(--black-alpha-56);
}
.regen-line {
margin-top: 20px;
display: flex; align-items: center; gap: 12px;
}
.regen-line .info {
margin-left: auto;
font-family: var(--font-mono);
font-size: 11.5px;
color: var(--black-alpha-48);
letter-spacing: .02em;
}
/* ─── Step 3 SUB 1: 模特库网格 ─── */
.filter-bar {
display: flex; align-items: center; gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-input { position: relative; width: 280px; }
.search-input svg {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
color: var(--black-alpha-56); width: 14px; height: 14px;
z-index: 2; pointer-events: none;
}
.search-input input {
padding-left: 36px;
height: 32px;
font-size: 12.5px;
background: var(--surface);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-md);
color: var(--accent-black);
outline: none;
width: 100%;
transition: border-color var(--t-base);
}
.search-input input:focus { border-color: var(--heat-40); box-shadow: inset 0 0 0 1px var(--heat-40); }
.filter-chip {
height: 32px; padding: 0 12px;
border: 1px solid var(--border-faint);
background: var(--surface);
border-radius: var(--r-md);
font-size: 12.5px;
color: var(--black-alpha-56);
display: inline-flex; align-items: center; gap: 5px;
cursor: pointer;
font-family: inherit;
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
}
.filter-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); color: var(--accent-black); }
.filter-chip.active { border-color: var(--heat-40); color: var(--heat); background: var(--heat-12); font-weight: 600; }
.filter-chip svg { width: 11px; height: 11px; }
.results-meta {
margin-left: auto;
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .04em;
}
.results-meta .accent { color: var(--heat); font-weight: 700; }
.model-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 12px;
}
@media (max-width: 1500px) { .model-grid { grid-template-columns: repeat(5, 1fr); } }
@media (max-width: 1280px) { .model-grid { grid-template-columns: repeat(4, 1fr); } }
.model-card {
aspect-ratio: 3/4;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
cursor: pointer;
position: relative;
overflow: hidden;
transition: border-color var(--t-base);
}
.model-card:hover { border-color: var(--black-alpha-32); }
.model-card .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
.model-card.selected {
border: 2px solid var(--heat);
box-shadow: 0 0 0 3px var(--heat-12);
}
.model-card.selected::after {
content: '';
position: absolute; top: 8px; right: 8px;
width: 22px; height: 22px;
background: var(--heat);
color: var(--accent-white);
border-radius: 50%;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3.5 8.5l3 3 6-6' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-size: 14px;
background-repeat: no-repeat;
background-position: center;
z-index: 2;
}
.model-card .lbl-bottom {
position: absolute; bottom: 0; left: 0; right: 0;
background: rgba(255,255,255,.96);
border-top: 1px solid var(--border-faint);
padding: 8px 10px;
display: flex; flex-direction: column; gap: 3px;
}
.model-card .lbl-bottom .nm {
font-size: 12.5px; font-weight: 600;
color: var(--accent-black);
}
.model-card .lbl-bottom .tags {
font-family: var(--font-mono);
font-size: 9.5px;
color: var(--black-alpha-48);
letter-spacing: .02em;
}
.model-card .pose-tag {
position: absolute; top: 8px; left: 8px;
background: rgba(0,0,0,.6);
color: var(--accent-white);
font-family: var(--font-mono);
font-size: 9px;
border-radius: var(--r-sm);
padding: 2px 6px;
letter-spacing: .04em;
z-index: 1;
}
.scroll-loader {
margin-top: 20px;
padding: 14px 18px;
background: var(--surface);
border: 1px dashed var(--border-faint);
border-radius: var(--r-md);
text-align: center;
font-family: var(--font-mono);
font-size: 11.5px;
color: var(--black-alpha-48);
letter-spacing: .02em;
}
/* ─── Step 3 SUB 2: 上身图 + 模特切换 ─── */
.model-switcher {
display: flex; align-items: center; gap: 8px;
margin-bottom: 20px;
padding: 12px 16px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
flex-wrap: wrap;
}
.model-switcher .lbl {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .02em;
}
.model-switcher .hint-right {
margin-left: auto;
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .02em;
}
/* ─── Step 4: 完成确认 ─── */
.review-box {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 32px;
max-width: 800px;
}
.review-box h3 { font-size: 22px; font-weight: 600; letter-spacing: -.012em; color: var(--accent-black); }
.review-box .review-meta {
font-family: var(--font-mono);
font-size: 11.5px;
color: var(--black-alpha-48);
margin-top: 6px;
letter-spacing: .02em;
}
.review-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-top: 20px;
}
.review-grid .placeholder { aspect-ratio: 1; }
.review-summary {
margin-top: 24px;
padding: 16px 20px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
font-size: 13px;
color: var(--black-alpha-72);
line-height: 1.7;
}
.review-summary dl > div {
display: flex;
padding: 6px 0;
border-bottom: 1px solid var(--border-faint);
}
.review-summary dl > div:last-child { border-bottom: 0; }
.review-summary dl dt {
width: 80px;
color: var(--black-alpha-48);
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: .04em;
}
.review-summary dl dd {
flex: 1;
color: var(--accent-black);
font-weight: 500;
}
/* ─── Right panel ─── */
.wb-controls {
border-left: 1px solid var(--border-faint);
background: var(--background-base);
overflow-y: auto;
padding: 22px 22px 140px;
display: flex; flex-direction: column;
position: relative;
}
.ctrl-section { margin-bottom: 24px; }
.ctrl-section h3 {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--black-alpha-48);
letter-spacing: .08em;
text-transform: uppercase;
font-weight: 600;
margin-bottom: 10px;
display: flex; align-items: center; gap: 6px;
}
.ctrl-section h3 .accent {
color: var(--heat);
font-weight: 700;
}
.hover-detail {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 16px;
}
.hover-detail .hd-name {
font-size: 16px; font-weight: 600;
color: var(--accent-black);
}
.hover-detail .hd-tags {
display: flex; gap: 5px; flex-wrap: wrap;
margin-top: 8px;
}
.hover-detail .hd-tags span {
font-family: var(--font-mono);
font-size: 10px;
color: var(--black-alpha-56);
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
padding: 2px 6px;
letter-spacing: .02em;
}
.hover-detail .hd-views {
display: grid; grid-template-columns: repeat(3, 1fr);
gap: 5px; margin-top: 14px;
}
.hover-detail .hd-views .placeholder { aspect-ratio: 3/4; }
.hover-detail .hd-meta {
display: flex; flex-direction: column; gap: 7px;
margin-top: 14px; padding-top: 14px;
border-top: 1px solid var(--border-faint);
font-size: 12px;
color: var(--black-alpha-72);
}
.hover-detail .hd-meta div { display: flex; gap: 10px; }
.hover-detail .hd-meta .k {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--black-alpha-48);
width: 60px;
letter-spacing: .04em;
flex-shrink: 0;
}
.selected-list { display: flex; flex-direction: column; gap: 6px; }
.sel-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
transition: border-color var(--t-base);
}
.sel-row:hover { border-color: var(--black-alpha-24); }
.sel-row .placeholder { width: 30px; height: 38px; flex-shrink: 0; border-radius: var(--r-sm); }
.sel-row .nm { flex: 1; font-size: 12.5px; font-weight: 600; color: var(--accent-black); }
.sel-row .x-btn {
width: 22px; height: 22px;
border-radius: var(--r-sm);
display: grid; place-items: center;
cursor: pointer;
color: var(--black-alpha-48);
transition: color var(--t-base), background var(--t-base);
}
.sel-row .x-btn:hover { color: var(--accent-crimson); background: var(--crimson-bg); }
.sel-empty {
padding: 18px 14px;
text-align: center;
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
border: 1px dashed var(--border-faint);
border-radius: var(--r-md);
letter-spacing: .02em;
}
.tip-mini {
padding: 12px 14px;
background: var(--heat-8);
border: 1px solid var(--heat-20);
border-radius: var(--r-md);
font-size: 12px;
color: var(--black-alpha-72);
line-height: 1.6;
}
.tip-mini strong {
color: var(--heat);
display: block;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: .04em;
margin-bottom: 6px;
font-weight: 600;
}
/* ─── Bottom bar (sticky in right column) ─── */
.wb-bottom {
position: absolute;
left: 0; right: 0; bottom: 0;
padding: 14px 22px;
background: var(--surface);
border-top: 1px solid var(--border-faint);
display: flex; flex-direction: column; gap: 10px;
}
.wb-bottom .row1 {
display: flex; align-items: center; justify-content: space-between;
}
.cost-info {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
letter-spacing: .04em;
}
.cost-info .price { color: var(--heat); font-weight: 700; }
/* ─── "我有图" 模式专用样式 ─── */
.single-mode {
display: none;
max-width: 920px;
margin: 0 auto;
}
.single-mode.active { display: block; }
.upload-grid {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 8px; margin-top: 12px;
}
.upload-grid .placeholder { aspect-ratio: 1; }
.upload-zone {
border: 1.5px dashed var(--black-alpha-24);
border-radius: var(--r-md);
padding: 28px;
text-align: center;
background: var(--background-lighter);
color: var(--black-alpha-56);
font-size: 13.5px;
cursor: pointer;
transition: border-color var(--t-base), background var(--t-base);
}
.upload-zone:hover { border-color: var(--heat); background: var(--heat-8); }
.upload-zone strong { color: var(--heat); font-weight: 600; }
</style>
</head>
<body>
<div class="wb">
<!-- ============ TOP ============ -->
<header class="wb-top">
<a class="home" href="products.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m15 18-6-6 6-6"/></svg>
返回商品库
</a>
<div class="ti">
<input class="name-edit" value="透真玻尿酸补水面膜" placeholder="点击编辑商品名">
<span class="mode-pill" id="mode-pill">[ AI 生成模式 ]</span>
</div>
<div class="right">
<button class="btn btn-sm" onclick="Shell.toast('已保存草稿', '/products/draft')">保存草稿</button>
<div class="x" onclick="if(confirm('确定退出?未保存的内容会丢失。')) location.href='products.html'">
<svg viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
</div>
</header>
<!-- ============ MODE SWITCH ============ -->
<div class="mode-row">
<span class="lbl">// 创建模式</span>
<div class="mode-toggle" id="mode-toggle">
<button data-mode="upload">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
我有商品图
</button>
<button class="active" data-mode="ai">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9.663 17h4.673M12 3v1M3.34 7l.7.7M20.66 7l-.7.7M2 12h1M21 12h1M12 19a7 7 0 100-14 7 7 0 000 14z"/></svg>
AI 帮我生成图
</button>
</div>
<div class="mode-hint" id="mode-hint">
<span class="mono">// AI 模式 ·</span> 上传 1 张原图,AI 生成头图 + 模特上身图
</div>
</div>
<!-- ============ MAIN ============ -->
<div class="wb-main">
<!-- LEFT SIDEBAR -->
<aside class="wb-side" id="wb-side">
<!-- AI mode steps -->
<div id="ai-steps">
<div class="side-section-h">// 创建流程 · 4 步</div>
<div class="step-item done" data-step="1"><div class="num"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg></div><div class="info"><div class="ti2">商品信息</div><div class="sub">名称、品类、价格、卖点</div></div></div>
<div class="step-item done" data-step="2"><div class="num"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg></div><div class="info"><div class="ti2">生成头图</div><div class="sub">已选 1 / 4 · 候选 A</div></div></div>
<div class="step-item active" data-step="3">
<div class="num">3</div>
<div class="info"><div class="ti2">模特上身图</div><div class="sub">// 子流程 · ① 挑模特 → ② 生上身</div></div>
</div>
<div class="substep-list" id="sub-nav">
<div class="substep-item active" data-sub="1">
<span class="num-mini"></span>
<span>挑选模特</span>
<span style="margin-left:auto; font-family:var(--font-mono); font-size:10px; opacity:.7;" id="sub1-meta">2 / 50+</span>
</div>
<div class="substep-item" data-sub="2">
<span class="num-mini"></span>
<span>生成上身图</span>
<span style="margin-left:auto; font-family:var(--font-mono); font-size:10px; opacity:.6;">待开始</span>
</div>
</div>
<div class="step-item locked" data-step="4"><div class="num">4</div><div class="info"><div class="ti2">完成创建</div><div class="sub">// 预览 · 提交</div></div></div>
</div>
<!-- Upload mode steps -->
<div id="upload-steps" style="display:none;">
<div class="side-section-h">// 创建流程 · 单步</div>
<div class="step-item active" data-step="u1">
<div class="num">1</div>
<div class="info"><div class="ti2">商品信息 + 图册</div><div class="sub">// 一次性填完即可创建</div></div>
</div>
</div>
<div class="side-divider"></div>
<!-- Selected assets -->
<div class="side-section-h">// 已选素材</div>
<div class="selected-assets-side">
<div class="sel-asset-label">原图 · 1 张</div>
<div class="placeholder sel-asset-thumb"><span class="ph-frame">补水面膜<br>1200×1500</span></div>
<div id="ai-assets-side" style="display:block;">
<div class="sel-asset-label">头图 · 候选 A</div>
<div class="placeholder sel-asset-thumb square"><span class="ph-frame">A · 白底简约</span></div>
</div>
</div>
</aside>
<!-- CENTER CANVAS -->
<main class="wb-canvas" id="wb-canvas">
<!-- ====== AI MODE: 4 steps ====== -->
<div id="ai-mode-content">
<!-- Sub-tab strip (visible only in Step 3) -->
<div class="subtab-strip" id="subtab-strip">
<div class="subtab active" data-sub="1">
<span class="num-circ"></span>
挑选模特
</div>
<span class="arrow"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></span>
<div class="subtab disabled" data-sub="2">
<span class="num-circ"></span>
生成上身图
</div>
<span class="subtab-meta" id="subtab-meta">// SUB-STEP 1 / 2 · 已选模特 <span class="accent" id="meta-count">2</span></span>
</div>
<div class="canvas-inner">
<!-- ============= STEP 1 · 商品信息 ============= -->
<section class="step-pane" data-pane="1">
<div class="canvas-h">
<div>
<h2>商品信息 · 上传原图</h2>
<p>上传一张你拍的商品图(任何角度都行,AI 会优化),并填写商品基本信息。</p>
</div>
<span class="step-tag">// STEP 1 / 4 · INFO + UPLOAD</span>
</div>
<div class="form-card">
<div class="s1-grid">
<div>
<div class="upload-c has-file"><div class="placeholder"><span class="ph-frame">补水面膜.jpg<br>1200×1500</span></div></div>
<div class="up-hint">// 已上传 · 点击重新选择</div>
</div>
<div>
<div class="field">
<label class="field-label">商品名称<span class="req">*</span></label>
<input class="input" value="透真玻尿酸补水面膜">
</div>
<div class="field-row">
<div class="field">
<label class="field-label">品类</label>
<select class="select"><option>美妆个护</option><option>数码 3C</option><option>食品饮料</option></select>
</div>
<div class="field">
<label class="field-label">参考价</label>
<input class="input" value="¥39.9">
</div>
</div>
<div class="field">
<label class="field-label">核心卖点<span class="req">*</span></label>
<ul class="bullet-list">
<li><span class="num">1</span> 透明质酸 + B5,敷完不黏不闷</li>
<li><span class="num">2</span> 30g 大容量精华液</li>
<li><span class="num">+</span> <input class="input" style="height:24px; border:0; padding:0 4px; background:transparent;" placeholder="添加新卖点"></li>
</ul>
</div>
<div class="field" style="margin-bottom:0;">
<label class="field-label">目标人群</label>
<input class="input" value="22-32 岁女性、熬夜党、敏感肌">
</div>
</div>
</div>
</div>
</section>
<!-- ============= STEP 2 · 生成头图 ============= -->
<section class="step-pane" data-pane="2">
<div class="canvas-h">
<div>
<h2>选一张你最满意的头图</h2>
<p>AI 已基于你上传的原图生成 4 张候选,点击你想要的那张。可重新生成。</p>
</div>
<span class="step-tag">// STEP 2 / 4 · HEAD IMAGES</span>
</div>
<div class="big-grid" id="head-grid">
<div class="big-card selected" data-id="h1"><div class="placeholder"><span class="ph-frame">候选 A · 白底简约</span></div><span class="corner-info">[ A · 1920×1920 · WHITE-BG ]</span></div>
<div class="big-card" data-id="h2"><div class="placeholder"><span class="ph-frame">候选 B · 木纹背景</span></div><span class="corner-info">[ B · 1920×1920 · WOOD ]</span></div>
<div class="big-card" data-id="h3"><div class="placeholder"><span class="ph-frame">候选 C · 浅米石材</span></div><span class="corner-info">[ C · 1920×1920 · STONE ]</span></div>
<div class="big-card" data-id="h4"><div class="placeholder"><span class="ph-frame">候选 D · 暖光氛围</span></div><span class="corner-info">[ D · 1920×1920 · AMBIENT ]</span></div>
</div>
<div class="regen-line">
<button class="btn" onclick="regen('head')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 4v4h-4"/></svg>
全部重新生成
</button>
<span class="info">// ~¥0.20 / 4 张 · 不满意不扣费</span>
</div>
</section>
<!-- ============= STEP 3 · 模特上身图(子 Tab) ============= -->
<section class="step-pane active" data-pane="3">
<!-- SUB 1 · 挑选模特 -->
<div class="sub-pane active" data-sub-pane="1">
<div class="canvas-h">
<div>
<h2>挑选模特 · 子步骤 ①</h2>
<p>从 AI 模特库选 1 个或多个,统一白 T + 白短裤立绘。悬浮看详情和三视图。选好后点右下「下一步」自动跳到 ②。</p>
</div>
<span class="step-tag">// STEP 3 / 4 · SUB 1 · MODEL</span>
</div>
<div class="filter-bar">
<div class="search-input">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" placeholder="搜索模特名称、风格…">
</div>
<button class="filter-chip active">全部 <span style="font-family:var(--font-mono); opacity:.7;">52</span></button>
<button class="filter-chip">女性</button>
<button class="filter-chip">男性</button>
<button class="filter-chip">25-30 岁</button>
<button class="filter-chip">都市白领</button>
<button class="filter-chip">学生</button>
<button class="filter-chip">居家</button>
<button class="filter-chip">+ 更多 <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
<span class="results-meta">// 12 / 52 · 已选 <span class="accent" id="sel-count">2</span></span>
</div>
<div class="model-grid" id="model-grid">
<div class="model-card selected" data-id="m1" data-name="林夕" data-base="女 · 25-30" data-role="都市白领" data-tags="温柔,日常,OL,通勤">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">林夕 · 都市女</span></div>
<div class="lbl-bottom"><div class="nm">林夕</div><div class="tags">女 · 25-30 · 都市白领</div></div>
</div>
<div class="model-card" data-id="m2" data-name="阿楠" data-base="女 · 25-30" data-role="姐妹/同事" data-tags="精致,干练,短发">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">阿楠</span></div>
<div class="lbl-bottom"><div class="nm">阿楠</div><div class="tags">女 · 25-30 · 同事</div></div>
</div>
<div class="model-card selected" data-id="m3" data-name="小七" data-base="女 · 18-22" data-role="学生/Z世代" data-tags="青春,元气,校园">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">小七 · 学生</span></div>
<div class="lbl-bottom"><div class="nm">小七</div><div class="tags">女 · 18-22 · 学生</div></div>
</div>
<div class="model-card" data-id="m4" data-name="王姐" data-base="女 · 38-45" data-role="妈妈/居家" data-tags="亲和,成熟,温暖">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">王姐 · 居家</span></div>
<div class="lbl-bottom"><div class="nm">王姐</div><div class="tags">女 · 38-45 · 妈妈</div></div>
</div>
<div class="model-card" data-id="m5" data-name="阿杰" data-base="男 · 28-35" data-role="通勤男" data-tags="商务,干练,西装">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">阿杰 · 通勤男</span></div>
<div class="lbl-bottom"><div class="nm">阿杰</div><div class="tags">男 · 28-35 · 通勤</div></div>
</div>
<div class="model-card" data-id="m6" data-name="阿强" data-base="男 · 22-28" data-role="健身男" data-tags="阳光,运动,肌肉">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">阿强 · 健身男</span></div>
<div class="lbl-bottom"><div class="nm">阿强</div><div class="tags">男 · 22-28 · 健身</div></div>
</div>
<div class="model-card" data-id="m7" data-name="小苏" data-base="女 · 22-26" data-role="文艺研究生" data-tags="书卷气,静谧,文艺">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">小苏 · 文艺女</span></div>
<div class="lbl-bottom"><div class="nm">小苏</div><div class="tags">女 · 22-26 · 文艺</div></div>
</div>
<div class="model-card" data-id="m8" data-name="文文" data-base="女 · 30-38" data-role="母亲" data-tags="温柔,母性,温暖">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">文文 · 母亲</span></div>
<div class="lbl-bottom"><div class="nm">文文</div><div class="tags">女 · 30-38 · 母亲</div></div>
</div>
<div class="model-card" data-id="m9" data-name="老王" data-base="男 · 45-55" data-role="大叔" data-tags="沉稳,成熟,商务">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">老王 · 大叔</span></div>
<div class="lbl-bottom"><div class="nm">老王</div><div class="tags">男 · 45-55 · 大叔</div></div>
</div>
<div class="model-card" data-id="m10" data-name="萌萌" data-base="女 · 18-22" data-role="Z世代" data-tags="可爱,潮流,二次元">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">萌萌 · Z世代</span></div>
<div class="lbl-bottom"><div class="nm">萌萌</div><div class="tags">女 · 18-22 · Z 世代</div></div>
</div>
<div class="model-card" data-id="m11" data-name="Leo" data-base="男 · 30-38" data-role="健身教练" data-tags="专业,力量,健身房">
<span class="pose-tag">[ 立绘 · 肩上 ]</span>
<div class="placeholder"><span class="ph-frame">Leo · 教练</span></div>
<div class="lbl-bottom"><div class="nm">Leo</div><div class="tags">男 · 30-38 · 教练</div></div>
</div>
<div class="model-card" data-id="m12" data-name="闺蜜组合" data-base="双人 · 25-30" data-role="闺蜜" data-tags="亲密,日常,搭档">
<span class="pose-tag">[ 立绘 · 双人 ]</span>
<div class="placeholder"><span class="ph-frame">闺蜜双人</span></div>
<div class="lbl-bottom"><div class="nm">闺蜜组合</div><div class="tags">双人 · 25-30</div></div>
</div>
</div>
<div class="scroll-loader">
// 还有 40 个模特 · 滚动加载 / 用上方筛选缩小范围
</div>
</div>
<!-- SUB 2 · 生成上身图 -->
<div class="sub-pane" data-sub-pane="2">
<div class="canvas-h">
<div>
<h2>生成上身图 · 子步骤 ②</h2>
<p>已为你选的每个模特生成 4 张上身图,可多选保留。点 ← 回到「① 挑选模特」改人。</p>
</div>
<span class="step-tag">// STEP 3 / 4 · SUB 2 · WEAR</span>
</div>
<div class="model-switcher">
<span class="lbl">// 当前模特:</span>
<button class="filter-chip active">林夕 · 都市白领</button>
<button class="filter-chip">小七 · 学生</button>
<span class="hint-right">// 切换模特看不同上身效果</span>
</div>
<div class="big-grid" id="wear-grid">
<div class="big-card selected"><div class="placeholder"><span class="ph-frame">上身 A · 持物半身</span></div><span class="corner-info">[ A · 持物半身 ]</span></div>
<div class="big-card"><div class="placeholder"><span class="ph-frame">上身 B · 敷面膜中</span></div><span class="corner-info">[ B · 敷面膜中 ]</span></div>
<div class="big-card selected"><div class="placeholder"><span class="ph-frame">上身 C · 镜前自拍</span></div><span class="corner-info">[ C · 镜前自拍 ]</span></div>
<div class="big-card"><div class="placeholder"><span class="ph-frame">上身 D · 床边特写</span></div><span class="corner-info">[ D · 床边特写 ]</span></div>
</div>
<div class="regen-line">
<button class="btn" onclick="regen('wear')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 4v4h-4"/></svg>
全部重新生成
</button>
<span class="info">// ~¥0.40 / 4 张 · 已选 2 / 4 张</span>
</div>
</div>
</section>
<!-- ============= STEP 4 · 完成 ============= -->
<section class="step-pane" data-pane="4">
<div class="canvas-h">
<div>
<h2>预览 · 创建</h2>
<p>检查下面的素材和信息无误后,点击右下「✓ 创建商品」。</p>
</div>
<span class="step-tag">// STEP 4 / 4 · CONFIRM</span>
</div>
<div class="review-box">
<h3>透真玻尿酸补水面膜</h3>
<div class="review-meta">// 美妆个护 · ¥39.9 · AI 生成 · 模特: 林夕 + 小七</div>
<div class="review-grid">
<div class="placeholder"><span class="ph-frame">原图</span></div>
<div class="placeholder"><span class="ph-frame">头图 A</span></div>
<div class="placeholder"><span class="ph-frame">上身 A</span></div>
<div class="placeholder"><span class="ph-frame">上身 C</span></div>
</div>
<div class="review-summary">
<dl>
<div><dt>核心卖点</dt><dd>透明质酸 + B5 · 30g 大容量精华</dd></div>
<div><dt>目标人群</dt><dd>22-32 岁女性、熬夜党、敏感肌</dd></div>
<div><dt>原图</dt><dd>1 张(已上传)</dd></div>
<div><dt>头图</dt><dd>1 张(候选 A · 白底简约)</dd></div>
<div><dt>上身图</dt><dd>2 张(A + C · 模特林夕 / 小七)</dd></div>
</dl>
</div>
</div>
</section>
</div>
</div>
<!-- ====== UPLOAD MODE: single page ====== -->
<div class="single-mode" id="upload-mode-content">
<div class="canvas-inner">
<div class="canvas-h">
<div>
<h2>填写商品信息 · 上传图册</h2>
<p>已有现成商品图册(主图 + 细节图)的快速通道。一次性填完,直接创建商品。</p>
</div>
<span class="step-tag">// UPLOAD MODE · ALL-IN-ONE</span>
</div>
<div class="form-card">
<div class="field">
<label class="field-label">商品名称<span class="req">*</span></label>
<input class="input" value="透真玻尿酸补水面膜">
</div>
<div class="field-row">
<div class="field">
<label class="field-label">品类</label>
<select class="select"><option>美妆个护</option><option>数码 3C</option><option>食品饮料</option><option>服饰</option></select>
</div>
<div class="field">
<label class="field-label">参考价</label>
<input class="input" value="¥39.9">
</div>
</div>
<div class="field">
<label class="field-label">商品图册<span class="req">*</span></label>
<div class="upload-zone">
将图片拖到此处,或 <strong>点击上传</strong><br>
<span style="color:var(--black-alpha-48); font-size:11.5px;" class="mono">// 建议 3-6 张 · 1200×1200+ · JPG / PNG</span>
</div>
<div class="upload-grid">
<div class="placeholder"><span class="ph-frame">主图</span></div>
<div class="placeholder"><span class="ph-frame">细节</span></div>
<div class="placeholder"><span class="ph-frame">使用</span></div>
<div class="placeholder"><span class="ph-frame">+</span></div>
</div>
</div>
<div class="field">
<label class="field-label">核心卖点<span class="req">*</span></label>
<ul class="bullet-list">
<li><span class="num">1</span> 透明质酸 + B5,敷完不黏不闷</li>
<li><span class="num">2</span> 30g 大容量精华液</li>
<li><span class="num">3</span> 0 香精 0 酒精,敏感肌可用</li>
<li><span class="num">+</span> <input class="input" style="height:24px; border:0; padding:0 4px; background:transparent;" placeholder="添加新卖点"></li>
</ul>
</div>
<div class="field" style="margin-bottom:0;">
<label class="field-label">目标人群</label>
<input class="input" value="22-32 岁女性、熬夜党、敏感肌、办公室通勤">
</div>
</div>
</div>
</div>
</main>
<!-- RIGHT CONTROLS -->
<aside class="wb-controls">
<!-- AI mode controls -->
<div id="ai-controls">
<div class="ctrl-section">
<h3>// 当前悬浮 · DETAIL</h3>
<div class="hover-detail" id="hover-detail">
<div class="hd-name">林夕</div>
<div class="hd-tags" id="hd-tags">
<span></span><span>25-30</span><span>都市白领</span><span>温柔</span><span>OL</span><span>通勤</span>
</div>
<div class="hd-views">
<div class="placeholder"><span class="ph-frame">正面</span></div>
<div class="placeholder"><span class="ph-frame">侧面</span></div>
<div class="placeholder"><span class="ph-frame">背面</span></div>
</div>
<div class="hd-meta">
<div><span class="k">// 服装</span><span>白 T + 白短裤(默认立绘)</span></div>
<div><span class="k">// 年龄</span><span>25-30 岁 · 可微调 ±3</span></div>
<div><span class="k">// 身材</span><span>普通女 · 165cm / 50kg</span></div>
<div><span class="k">// 用过</span><span>4 个项目 · 平均评分 4.5</span></div>
</div>
</div>
</div>
<div class="ctrl-section">
<h3>// 已选模特 · <span class="accent" id="sel-count-3">2</span></h3>
<div class="selected-list" id="selected-list">
<div class="sel-row" data-id="m1">
<div class="placeholder"><span class="ph-frame"></span></div>
<div class="nm">林夕 · 都市白领</div>
<div class="x-btn" title="移除">
<svg width="12" height="12" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
</div>
<div class="sel-row" data-id="m3">
<div class="placeholder"><span class="ph-frame"></span></div>
<div class="nm">小七 · 学生</div>
<div class="x-btn" title="移除">
<svg width="12" height="12" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
</div>
</div>
</div>
<div class="ctrl-section">
<h3>// TIP</h3>
<div class="tip-mini">
<strong>多选会怎样</strong>
每选一个模特,② 会为其各自生成 4 张上身图。已选 2 个 = 8 张候选,可多选保留作素材。
</div>
</div>
</div>
<!-- Bottom action bar -->
<div class="wb-bottom">
<div class="row1">
<span class="cost-info" id="cost-info">// 预计 <span class="price">¥0.80</span> / 8 张</span>
<span class="cost-info" id="bottom-meta">SUB 1 / 2</span>
</div>
<div class="hstack" id="action-bar">
<button class="btn" style="flex:1;" id="btn-prev-sub"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> 上一步</button>
<button class="btn btn-primary" style="flex:1;" id="btn-next-sub">下一步 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg> 生成上身</button>
</div>
</div>
</aside>
</div>
</div>
<script src="assets/shell.js"></script>
<script>
// ============================================================
// STATE
// ============================================================
const state = {
mode: 'ai', // 'ai' | 'upload'
step: 3, // 1-4 (AI mode); only u1 in upload mode
sub: 1 // 1 | 2 (Step 3 sub-tab)
};
// ============================================================
// MODE TOGGLE
// ============================================================
document.querySelectorAll('#mode-toggle button').forEach(b => {
b.onclick = () => {
const mode = b.dataset.mode;
state.mode = mode;
document.querySelectorAll('#mode-toggle button').forEach(x => x.classList.remove('active'));
b.classList.add('active');
const isUpload = mode === 'upload';
document.getElementById('mode-pill').textContent = isUpload ? '[ 我有商品图模式 ]' : '[ AI 生成模式 ]';
document.getElementById('mode-hint').innerHTML = isUpload
? '<span class="mono">// 上传模式 ·</span> 已有商品图册的快速通道,一次性创建'
: '<span class="mono">// AI 模式 ·</span> 上传 1 张原图,AI 生成头图 + 模特上身图';
// sidebar steps
document.getElementById('ai-steps').style.display = isUpload ? 'none' : 'block';
document.getElementById('upload-steps').style.display = isUpload ? 'block' : 'none';
document.getElementById('ai-assets-side').style.display = isUpload ? 'none' : 'block';
// canvas
document.getElementById('ai-mode-content').style.display = isUpload ? 'none' : 'block';
document.getElementById('upload-mode-content').classList.toggle('active', isUpload);
// controls panel
document.getElementById('ai-controls').style.display = isUpload ? 'none' : 'block';
// bottom action bar
const ab = document.getElementById('action-bar');
const ci = document.getElementById('cost-info');
const bm = document.getElementById('bottom-meta');
if (isUpload) {
ab.innerHTML = `
<button class="btn" style="flex:1;" onclick="location.href='products.html'">取消</button>
<button class="btn btn-primary" style="flex:1;" onclick="alert('商品已创建!')"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg> 创建商品</button>
`;
ci.innerHTML = '// 上传模式 · 不消耗 token';
bm.textContent = '一次性创建';
} else {
ab.innerHTML = `
<button class="btn" style="flex:1;" id="btn-prev-sub"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> 上一步</button>
<button class="btn btn-primary" style="flex:1;" id="btn-next-sub">下一步 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg> 生成上身</button>
`;
ci.innerHTML = '// 预计 <span class="price">¥0.80</span> / 8 张';
bm.textContent = `SUB ${state.sub} / 2`;
bindStepButtons();
}
Shell.toast('已切换模式', isUpload ? '我有商品图' : 'AI 生成图');
};
});
// ============================================================
// STEP / SUB-STEP NAVIGATION (AI mode)
// ============================================================
function showStep(n) {
state.step = n;
document.querySelectorAll('.step-pane').forEach(p => p.classList.remove('active'));
document.querySelector(`[data-pane="${n}"]`)?.classList.add('active');
// sidebar steps
document.querySelectorAll('#ai-steps .step-item').forEach(s => {
s.classList.remove('active', 'done', 'locked');
const sn = +s.dataset.step;
if (sn < n) s.classList.add('done');
else if (sn === n) s.classList.add('active');
else s.classList.add('locked');
});
// sub-tab strip visible only in step 3
document.getElementById('subtab-strip').style.display = n === 3 ? 'flex' : 'none';
// hide sub-nav when not in step 3
document.getElementById('sub-nav').style.display = n === 3 ? 'block' : 'none';
// update bottom action bar
updateBottom();
document.querySelector('.wb-canvas').scrollTop = 0;
}
function showSub(n) {
state.sub = n;
document.querySelectorAll('.sub-pane').forEach(p => p.classList.remove('active'));
document.querySelector(`[data-sub-pane="${n}"]`)?.classList.add('active');
document.querySelectorAll('.subtab').forEach(t => {
t.classList.remove('active', 'done', 'disabled');
const tn = +t.dataset.sub;
if (tn < n) t.classList.add('done');
else if (tn === n) t.classList.add('active');
});
document.querySelectorAll('.substep-item').forEach(t => {
t.classList.remove('active', 'done');
const tn = +t.dataset.sub;
if (tn < n) t.classList.add('done');
else if (tn === n) t.classList.add('active');
});
document.getElementById('subtab-meta').innerHTML = n === 1
? `// SUB-STEP 1 / 2 · 已选模特 <span class="accent">${selectedCount()}</span>`
: '// SUB-STEP 2 / 2 · 上身图 4 选 N · 多选保留';
updateBottom();
document.querySelector('.wb-canvas').scrollTop = 0;
}
function updateBottom() {
const ci = document.getElementById('cost-info');
const bm = document.getElementById('bottom-meta');
const btnPrev = document.getElementById('btn-prev-sub');
const btnNext = document.getElementById('btn-next-sub');
if (state.step === 3) {
bm.textContent = `SUB ${state.sub} / 2`;
if (state.sub === 1) {
ci.innerHTML = `// 预计 <span class="price">¥${(0.40 * selectedCount()).toFixed(2)}</span> / ${4 * selectedCount()}`;
const SVG_LEFT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>';
const SVG_RIGHT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>';
if (btnPrev) btnPrev.innerHTML = SVG_LEFT + ' 返回头图';
if (btnNext) btnNext.innerHTML = '下一步 ' + SVG_RIGHT + ' 生成上身';
} else {
ci.innerHTML = '// 已生成 4 张 / 模特 · 累计 ¥0.80';
const SVG_LEFT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>';
const SVG_RIGHT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>';
if (btnPrev) btnPrev.innerHTML = SVG_LEFT + ' 改模特';
if (btnNext) btnNext.innerHTML = '下一步 ' + SVG_RIGHT + ' 完成创建';
}
} else {
bm.textContent = `STEP ${state.step} / 4`;
if (state.step === 1) { ci.innerHTML = '// 填写信息 · 不消耗 token'; }
else if (state.step === 2) { ci.innerHTML = '// ~¥0.20 / 4 张候选'; }
else if (state.step === 4) { ci.innerHTML = '// 累计预估 <span class="price">¥1.00</span>'; }
const SVG_LEFT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>';
const SVG_RIGHT = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>';
const SVG_CHECK = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>';
if (btnPrev) btnPrev.innerHTML = state.step === 1 ? '取消' : SVG_LEFT + ' 上一步';
if (btnNext) btnNext.innerHTML = state.step === 4 ? SVG_CHECK + ' 创建商品' : '下一步 ' + SVG_RIGHT;
}
}
function bindStepButtons() {
const btnPrev = document.getElementById('btn-prev-sub');
const btnNext = document.getElementById('btn-next-sub');
if (!btnPrev || !btnNext) return;
btnPrev.onclick = () => {
if (state.step === 3 && state.sub === 2) showSub(1);
else if (state.step === 1) location.href = 'products.html';
else showStep(state.step - 1);
};
btnNext.onclick = () => {
if (state.step === 3 && state.sub === 1) showSub(2);
else if (state.step === 4) alert('商品已创建!');
else showStep(state.step + 1);
};
}
// sub-tab click
document.querySelectorAll('.subtab').forEach(t => {
t.onclick = () => {
if (state.sub === 2 || +t.dataset.sub === 1 || selectedCount() > 0) {
showSub(+t.dataset.sub);
}
};
});
// sidebar step click
document.querySelectorAll('#ai-steps .step-item').forEach(s => {
s.onclick = () => {
if (s.classList.contains('locked')) return;
const n = +s.dataset.step;
if (n) showStep(n);
};
});
// sub-nav click
document.querySelectorAll('#sub-nav .substep-item').forEach(t => {
t.onclick = () => showSub(+t.dataset.sub);
});
// ============================================================
// MODEL CARDS · multi select + hover detail
// ============================================================
function selectedCount() {
return document.querySelectorAll('.model-card.selected').length;
}
function refreshCounts() {
const n = selectedCount();
const els = ['sel-count', 'sel-count-3', 'meta-count', 'sub1-meta'];
document.getElementById('sel-count').textContent = n;
document.getElementById('sel-count-3').textContent = n;
document.getElementById('meta-count').textContent = n;
document.getElementById('sub1-meta').textContent = `${n} / 50+`;
if (state.step === 3 && state.sub === 1) updateBottom();
}
document.querySelectorAll('.model-card').forEach(c => {
c.onclick = () => {
c.classList.toggle('selected');
refreshCounts();
// also update selected-list in right panel
refreshSelectedList();
};
c.onmouseenter = () => {
const name = c.dataset.name;
const base = c.dataset.base;
const role = c.dataset.role;
const tags = (c.dataset.tags || '').split(',');
hoverModel(name, base, role, tags);
};
});
function hoverModel(name, base, role, tags) {
const hd = document.getElementById('hover-detail');
hd.querySelector('.hd-name').textContent = name;
const baseTags = base.split(' · ');
const all = baseTags.concat([role]).concat(tags).filter(Boolean);
hd.querySelector('#hd-tags').innerHTML = all.map(t => `<span>${t}</span>`).join('');
}
function refreshSelectedList() {
const list = document.getElementById('selected-list');
const sels = document.querySelectorAll('.model-card.selected');
if (sels.length === 0) {
list.innerHTML = '<div class="sel-empty">// 未选模特 · 在中间网格点选</div>';
return;
}
list.innerHTML = Array.from(sels).map(c => {
const id = c.dataset.id;
const name = c.dataset.name;
const role = c.dataset.role;
const initial = name.charAt(0);
return `
<div class="sel-row" data-id="${id}">
<div class="placeholder"><span class="ph-frame">${initial}</span></div>
<div class="nm">${name} · ${role}</div>
<div class="x-btn" title="移除" onclick="removeModel('${id}')">
<svg width="12" height="12" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
</div>
`;
}).join('');
}
window.removeModel = function(id) {
document.querySelector(`.model-card[data-id="${id}"]`)?.classList.remove('selected');
refreshCounts();
refreshSelectedList();
};
// ============================================================
// BIG CARD (head + wear) select
// ============================================================
document.querySelectorAll('#head-grid .big-card').forEach(c => {
c.onclick = () => {
document.querySelectorAll('#head-grid .big-card').forEach(x => x.classList.remove('selected'));
c.classList.add('selected');
};
});
document.querySelectorAll('#wear-grid .big-card').forEach(c => {
c.onclick = () => c.classList.toggle('selected');
});
// ============================================================
// REGEN
// ============================================================
function regen(t) {
const el = document.querySelector(t === 'head' ? '#head-grid' : '#wear-grid');
if (!el) return;
el.style.opacity = .35;
Shell.toast('正在重新生成', t === 'head' ? '~¥0.20 / 4 张' : '~¥0.40 / 4 张');
setTimeout(() => { el.style.opacity = 1; }, 800);
}
// ============================================================
// INIT
// ============================================================
bindStepButtons();
updateBottom();
</script>
</body>
</html>