3884 lines
141 KiB
HTML
3884 lines
141 KiB
HTML
<!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?v=202605211643">
|
||
<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 1fr;
|
||
background: var(--background-base);
|
||
}
|
||
/* upload-mode-content 已废弃,被独立的 product-create-upload.html 替代 */
|
||
#upload-mode-content, #upload-steps { display: none !important; }
|
||
|
||
/* ─── 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; }
|
||
|
||
/* step-item 标签 (必须/附加) */
|
||
.step-tag-mini {
|
||
font-family: var(--font-mono);
|
||
font-size: 9.5px;
|
||
padding: 1px 6px;
|
||
border-radius: var(--r-sm);
|
||
margin-left: 4px;
|
||
letter-spacing: .04em;
|
||
font-weight: 500;
|
||
vertical-align: middle;
|
||
}
|
||
.step-tag-mini.req { background: var(--heat-12); color: var(--heat); }
|
||
.step-tag-mini.opt { background: var(--black-alpha-8); color: var(--black-alpha-56); }
|
||
.step-item.done .step-tag-mini.req { background: var(--forest-bg); color: var(--accent-forest); }
|
||
|
||
/* 附加资产 · 并列样式 (◇ 而非数字) */
|
||
.step-item.extra .num {
|
||
background: var(--background-lighter);
|
||
color: var(--black-alpha-48);
|
||
border: 1px dashed var(--black-alpha-24);
|
||
font-family: var(--font-mono);
|
||
font-size: 14px;
|
||
}
|
||
.step-item.extra.active {
|
||
background: var(--background-lighter);
|
||
border-left-color: var(--black-alpha-32);
|
||
}
|
||
.step-item.extra.active .num {
|
||
background: var(--accent-black);
|
||
color: var(--accent-white);
|
||
border-style: solid;
|
||
border-color: var(--accent-black);
|
||
}
|
||
.step-item.extra.done .num {
|
||
background: var(--forest-bg);
|
||
color: var(--accent-forest);
|
||
border: 1px solid var(--accent-forest);
|
||
}
|
||
.extra-hint {
|
||
padding: 0 20px 8px;
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* 资产库空态 */
|
||
.sel-asset-empty {
|
||
padding: 8px 20px;
|
||
font-size: 11px;
|
||
color: var(--black-alpha-32);
|
||
letter-spacing: .04em;
|
||
}
|
||
/* 原图缩略图横排 */
|
||
.side-orig-thumbs {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.side-orig-thumbs .ot {
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-sm);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
overflow: hidden;
|
||
}
|
||
.side-orig-thumbs .ot img { width: 100%; height: 100%; object-fit: cover; }
|
||
|
||
/* 资产库子分组 */
|
||
.asset-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
.asset-group .ag-h {
|
||
font-size: 10.5px;
|
||
font-family: var(--font-mono);
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
margin-bottom: 4px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
}
|
||
.asset-group .ag-h .v-count { color: var(--heat); }
|
||
.asset-group .ag-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 4px;
|
||
}
|
||
.asset-group .ag-item {
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-sm);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
overflow: hidden;
|
||
position: relative;
|
||
cursor: zoom-in;
|
||
}
|
||
.asset-group .ag-item.current { border-color: var(--heat); border-width: 1.5px; }
|
||
.asset-group .ag-item img { width: 100%; height: 100%; object-fit: cover; }
|
||
.asset-group .ag-item .v-tag {
|
||
position: absolute;
|
||
bottom: 2px; left: 2px;
|
||
background: rgba(0,0,0,.65);
|
||
color: #fff;
|
||
font-family: var(--font-mono);
|
||
font-size: 8.5px;
|
||
padding: 1px 4px;
|
||
border-radius: 2px;
|
||
letter-spacing: .04em;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
/* AI mode 容器也要 flex column 才能让 canvas-inner 撑满高度 */
|
||
#ai-mode-content {
|
||
flex: 1; min-height: 0;
|
||
display: flex; flex-direction: column;
|
||
}
|
||
|
||
/* 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 32px 0;
|
||
flex: 1; min-height: 0;
|
||
max-width: 1280px; width: 100%;
|
||
display: flex; flex-direction: column;
|
||
}
|
||
.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: flex; flex-direction: column;
|
||
flex: 1; min-height: 0;
|
||
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: flex; flex-direction: column;
|
||
flex: 1; min-height: 0;
|
||
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; }
|
||
.s1-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||
.s1-upload-card { display: flex; flex-direction: column; }
|
||
@media (max-width: 1100px) { .s1-2col { grid-template-columns: 1fr; } }
|
||
|
||
/* Step 1 / 4 共用 upload-zone */
|
||
.s1-upload-card .upload-zone, .step-pane .upload-zone {
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-md);
|
||
padding: 22px 20px;
|
||
text-align: center;
|
||
background: var(--background-lighter);
|
||
color: var(--black-alpha-56);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||
transition: border-color var(--t-base), background var(--t-base), color var(--t-base);
|
||
}
|
||
.step-pane .upload-zone:hover { border-color: var(--heat); background: var(--heat-8); color: var(--heat); }
|
||
.step-pane .upload-zone:hover .uz-ic { background: var(--heat); color: #fff; border-color: var(--heat); }
|
||
.step-pane .upload-zone strong { color: var(--heat); font-weight: 600; }
|
||
.step-pane .upload-zone .uz-ic {
|
||
width: 40px; height: 40px;
|
||
border-radius: var(--r-md);
|
||
background: var(--surface);
|
||
color: var(--heat);
|
||
border: 1px solid var(--heat-20);
|
||
display: grid; place-items: center;
|
||
margin-bottom: 6px;
|
||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||
}
|
||
.step-pane .upload-zone .uz-ic svg { width: 18px; height: 18px; }
|
||
.step-pane .upload-zone .uz-hint { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
.step-pane .upload-zone.full { cursor: not-allowed; opacity: .55; pointer-events: none; }
|
||
|
||
/* Step 1 上传 grid · 5 列复用 */
|
||
.step-pane .upload-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 5px;
|
||
}
|
||
.step-pane .up-thumb {
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden;
|
||
position: relative;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
cursor: zoom-in;
|
||
transition: transform var(--t-fast), border-color var(--t-base);
|
||
}
|
||
.step-pane .up-thumb:hover { transform: scale(1.02); border-color: var(--heat); }
|
||
.step-pane .up-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.step-pane .up-thumb .slot-x {
|
||
position: absolute; top: 3px; right: 3px;
|
||
width: 18px; height: 18px;
|
||
background: rgba(0,0,0,.7); color: #fff;
|
||
border-radius: 50%; display: none; place-items: center;
|
||
cursor: pointer; z-index: 3; border: 0; padding: 0;
|
||
transition: background var(--t-base), transform var(--t-fast);
|
||
}
|
||
.step-pane .up-thumb:hover .slot-x { display: grid; }
|
||
.step-pane .up-thumb .slot-x:hover { background: var(--accent-crimson); transform: scale(1.1); }
|
||
.step-pane .up-thumb .slot-x svg { width: 9px; height: 9px; }
|
||
|
||
/* Step 2 · 三视图大图区 */
|
||
.triview-stage {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 18px;
|
||
max-width: 960px;
|
||
}
|
||
.triview-display {
|
||
aspect-ratio: 3 / 1;
|
||
border-radius: var(--r-md);
|
||
overflow: hidden;
|
||
border: 1px solid var(--border-faint);
|
||
position: relative;
|
||
background: var(--background-lighter);
|
||
}
|
||
.triview-display .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
|
||
.triview-display img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.triview-vtag {
|
||
position: absolute;
|
||
top: 12px; right: 12px;
|
||
background: rgba(0,0,0,.7);
|
||
color: #fff;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
padding: 4px 10px;
|
||
border-radius: var(--r-sm);
|
||
letter-spacing: .04em;
|
||
}
|
||
|
||
/* 版本历史 */
|
||
.triview-versions {
|
||
margin-top: 24px;
|
||
max-width: 960px;
|
||
}
|
||
.triview-versions .tv-h {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
margin-bottom: 10px;
|
||
}
|
||
.triview-versions .tv-list {
|
||
display: flex;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 4px;
|
||
}
|
||
.triview-versions .tv-item {
|
||
flex-shrink: 0;
|
||
width: 180px;
|
||
aspect-ratio: 3 / 1;
|
||
border-radius: var(--r-md);
|
||
overflow: hidden;
|
||
border: 1px solid var(--border-faint);
|
||
position: relative;
|
||
cursor: pointer;
|
||
background: var(--background-lighter);
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.triview-versions .tv-item:hover { border-color: var(--black-alpha-32); }
|
||
.triview-versions .tv-item.current { border-color: var(--heat); border-width: 1.5px; box-shadow: 0 0 0 3px var(--heat-12); }
|
||
.triview-versions .tv-item .tv-vtag {
|
||
position: absolute; bottom: 4px; left: 4px;
|
||
background: rgba(0,0,0,.65); color: #fff;
|
||
font-family: var(--font-mono); font-size: 10px;
|
||
padding: 2px 6px; border-radius: 2px;
|
||
letter-spacing: .04em;
|
||
}
|
||
.triview-versions .tv-empty {
|
||
padding: 14px;
|
||
text-align: center;
|
||
color: var(--black-alpha-48);
|
||
font-size: 12px;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
font-family: var(--font-mono);
|
||
letter-spacing: .02em;
|
||
}
|
||
|
||
/* Step 4 · 平台选择 */
|
||
.platform-picker { max-width: 920px; }
|
||
.platform-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 10px;
|
||
margin: 10px 0 12px;
|
||
}
|
||
.plat-card {
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 12px 14px;
|
||
background: var(--surface);
|
||
border: 1.5px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
}
|
||
.plat-card:hover { border-color: var(--black-alpha-32); }
|
||
.plat-card input[type="checkbox"] { accent-color: var(--heat); width: 14px; height: 14px; cursor: pointer; }
|
||
.plat-card input[type="checkbox"]:checked + .plat-info .plat-name { color: var(--heat); }
|
||
.plat-card:has(input:checked) { border-color: var(--heat); background: var(--heat-8); }
|
||
.plat-info { display: flex; flex-direction: column; gap: 3px; }
|
||
.plat-name { font-size: 13px; font-weight: 600; color: var(--accent-black); }
|
||
.plat-spec { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
.platform-cost { font-size: 11px; color: var(--black-alpha-48); letter-spacing: .04em; }
|
||
|
||
/* Step 4 · 平台生成结果 */
|
||
.platform-results { margin-top: 18px; max-width: 920px; }
|
||
.platform-empty {
|
||
padding: 36px 20px;
|
||
text-align: center;
|
||
color: var(--black-alpha-48);
|
||
font-size: 12.5px;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
letter-spacing: .04em;
|
||
}
|
||
.platform-block {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 16px 18px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.platform-block .pb-h {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.platform-block .pb-h .pb-name { font-size: 14px; font-weight: 600; color: var(--accent-black); }
|
||
.platform-block .pb-h .pb-spec { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); }
|
||
.platform-block .pb-h .pb-regen {
|
||
margin-left: auto;
|
||
height: 28px; padding: 0 10px;
|
||
font-size: 12px;
|
||
background: transparent;
|
||
border: 1px solid var(--border-faint);
|
||
color: var(--black-alpha-72);
|
||
border-radius: var(--r-md);
|
||
cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
font-family: inherit;
|
||
}
|
||
.platform-block .pb-h .pb-regen:hover { border-color: var(--heat); color: var(--heat); }
|
||
.platform-block .pb-h .pb-regen svg { width: 11px; height: 11px; }
|
||
.platform-block .pb-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
}
|
||
.platform-block .pb-card {
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-sm);
|
||
border: 1px solid var(--border-faint);
|
||
background: var(--background-lighter);
|
||
position: relative;
|
||
overflow: hidden;
|
||
cursor: zoom-in;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.platform-block .pb-card:hover { border-color: var(--heat); }
|
||
.platform-block .pb-card .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
|
||
.platform-block .pb-card img { width: 100%; height: 100%; object-fit: cover; }
|
||
.platform-block .pb-card .pb-x {
|
||
position: absolute; top: 4px; right: 4px;
|
||
width: 22px; height: 22px;
|
||
background: rgba(0,0,0,.65); color: #fff;
|
||
border: 0; border-radius: 50%;
|
||
display: none; place-items: center;
|
||
cursor: pointer;
|
||
transition: background var(--t-base);
|
||
}
|
||
.platform-block .pb-card:hover .pb-x { display: grid; }
|
||
.platform-block .pb-card .pb-x:hover { background: var(--accent-crimson); }
|
||
.platform-block .pb-card .pb-x svg { width: 11px; height: 11px; }
|
||
.platform-block .pb-card .pb-vtag {
|
||
position: absolute; bottom: 4px; left: 4px;
|
||
background: rgba(0,0,0,.65); color: #fff;
|
||
font-family: var(--font-mono); font-size: 9.5px;
|
||
padding: 2px 5px; border-radius: 2px;
|
||
letter-spacing: .04em;
|
||
}
|
||
|
||
/* Step 5 · 资产分组清单 */
|
||
.rv-assets { margin: 18px 0 8px; }
|
||
.rv-asset-group { margin-bottom: 16px; }
|
||
.rv-asset-group .rvg-h {
|
||
display: flex; align-items: center; gap: 8px;
|
||
font-size: 13px; font-weight: 500;
|
||
color: var(--accent-black);
|
||
margin-bottom: 8px;
|
||
}
|
||
.rv-asset-group .rvg-h .count {
|
||
font-family: var(--font-mono); font-size: 11px;
|
||
color: var(--heat); font-weight: 600;
|
||
}
|
||
.rv-asset-group .rvg-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 6px;
|
||
}
|
||
.rv-asset-group .rvg-item {
|
||
aspect-ratio: 1;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden;
|
||
cursor: zoom-in;
|
||
position: relative;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.rv-asset-group .rvg-item:hover { border-color: var(--heat); }
|
||
.rv-asset-group .rvg-item img { width: 100%; height: 100%; object-fit: cover; }
|
||
.rv-asset-group .rvg-item .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
|
||
.rv-asset-group .rvg-item .rvg-tag {
|
||
position: absolute; bottom: 3px; left: 3px;
|
||
background: rgba(0,0,0,.65); color: #fff;
|
||
font-family: var(--font-mono); font-size: 9px;
|
||
padding: 1px 4px; border-radius: 2px;
|
||
letter-spacing: .04em;
|
||
}
|
||
.rv-asset-empty {
|
||
padding: 18px;
|
||
text-align: center;
|
||
color: var(--black-alpha-48);
|
||
font-family: var(--font-mono); font-size: 11.5px;
|
||
background: var(--background-lighter);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
letter-spacing: .04em;
|
||
}
|
||
.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);
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
}
|
||
.bullet-list li.bl-item { cursor: text; }
|
||
.bullet-list li.bl-item:hover { border-color: var(--black-alpha-24); }
|
||
.bullet-list li.bl-add { background: var(--surface); border-style: dashed; }
|
||
.bullet-list li.bl-add:focus-within { border-color: var(--heat-40); background: var(--surface); }
|
||
.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;
|
||
}
|
||
.bullet-list li.bl-add .num { background: transparent; color: var(--heat); border-color: var(--heat-40); }
|
||
.bullet-list .bl-text { flex: 1; min-width: 0; }
|
||
.bullet-list .bl-input {
|
||
flex: 1; min-width: 0;
|
||
height: 24px; border: 0; padding: 0 4px;
|
||
background: transparent; font-size: 13px;
|
||
color: var(--accent-black); font-family: inherit;
|
||
outline: none;
|
||
}
|
||
.bullet-list .bl-input::placeholder { color: var(--black-alpha-48); }
|
||
.bullet-list .bl-x {
|
||
width: 24px; height: 24px;
|
||
display: grid; place-items: center;
|
||
color: var(--black-alpha-32);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
opacity: 0;
|
||
transition: opacity var(--t-base), background var(--t-base), color var(--t-base);
|
||
}
|
||
.bullet-list li.bl-item:hover .bl-x { opacity: 1; }
|
||
.bullet-list .bl-x:hover { background: var(--crimson-bg); color: var(--accent-crimson); }
|
||
.bullet-list .bl-x svg { width: 11px; height: 11px; }
|
||
|
||
/* ─── 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 3 SUB 1: 模特来源切换 (平台库 / 我的) ─── */
|
||
.kind-toggle {
|
||
display: flex;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 4px;
|
||
gap: 2px;
|
||
margin-bottom: 14px;
|
||
width: fit-content;
|
||
}
|
||
.kind-toggle .kt-btn {
|
||
padding: 7px 16px;
|
||
font-size: 12.5px;
|
||
color: var(--black-alpha-56);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
background: transparent;
|
||
border: 0;
|
||
font-family: inherit;
|
||
font-weight: 500;
|
||
}
|
||
.kind-toggle .kt-btn:hover { color: var(--accent-black); }
|
||
.kind-toggle .kt-btn.active {
|
||
background: var(--accent-black);
|
||
color: var(--accent-white);
|
||
}
|
||
.kind-toggle .kt-btn .kt-num {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
opacity: .7;
|
||
letter-spacing: .04em;
|
||
}
|
||
|
||
/* ─── Step 3 · 模特卡片来源徽章 (替换 pose-tag) ─── */
|
||
.model-card .source-tag {
|
||
position: absolute; top: 8px; left: 8px;
|
||
background: rgba(0,0,0,.66);
|
||
color: var(--accent-white);
|
||
font-family: var(--font-mono);
|
||
font-size: 9px;
|
||
border-radius: var(--r-sm);
|
||
padding: 2px 7px;
|
||
letter-spacing: .04em;
|
||
z-index: 1;
|
||
}
|
||
.model-card.kind-mine .source-tag {
|
||
background: var(--heat);
|
||
color: var(--accent-white);
|
||
}
|
||
|
||
/* ─── Step 3 · "+ 上传新模特" 卡片 ─── */
|
||
.model-card-add {
|
||
aspect-ratio: 3/4;
|
||
background: var(--surface);
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-md);
|
||
cursor: pointer;
|
||
display: flex; flex-direction: column;
|
||
align-items: center; justify-content: center;
|
||
gap: 10px;
|
||
color: var(--black-alpha-56);
|
||
transition: border-color var(--t-base), background var(--t-base), color var(--t-base);
|
||
padding: 16px;
|
||
text-align: center;
|
||
}
|
||
.model-card-add:hover {
|
||
border-color: var(--heat);
|
||
background: var(--heat-8);
|
||
color: var(--heat);
|
||
}
|
||
.model-card-add .add-ic {
|
||
width: 38px; height: 38px;
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border-radius: 50%;
|
||
display: grid; place-items: center;
|
||
}
|
||
.model-card-add .add-ic svg { width: 18px; height: 18px; }
|
||
.model-card-add .add-t {
|
||
font-size: 13px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.model-card-add .add-d {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
line-height: 1.5;
|
||
}
|
||
.model-card-add:hover .add-t { color: var(--heat); }
|
||
|
||
/* ─── 空状态: 我的模特库 ─── */
|
||
.mine-empty {
|
||
grid-column: 1 / -1;
|
||
padding: 36px 24px;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
text-align: center;
|
||
color: var(--black-alpha-56);
|
||
}
|
||
.mine-empty .me-t { font-size: 13.5px; font-weight: 600; color: var(--accent-black); margin-bottom: 6px; }
|
||
.mine-empty .me-d { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
|
||
/* ─── Step 3 SUB 2: 多模特上身图 (重构) ─── */
|
||
.outfits-wrap { display: flex; flex-direction: column; gap: 20px; }
|
||
.outfit-block {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 18px 20px;
|
||
}
|
||
.outfit-block .ob-h {
|
||
display: flex; align-items: center; gap: 12px;
|
||
margin-bottom: 14px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
.outfit-block .ob-mname {
|
||
font-size: 14px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
}
|
||
.outfit-block .ob-mkind {
|
||
font-family: var(--font-mono);
|
||
font-size: 9.5px;
|
||
color: var(--black-alpha-48);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
padding: 2px 7px;
|
||
border-radius: var(--r-sm);
|
||
letter-spacing: .04em;
|
||
}
|
||
.outfit-block.kind-mine .ob-mkind {
|
||
color: var(--heat);
|
||
background: var(--heat-12);
|
||
border-color: var(--heat-40);
|
||
}
|
||
.outfit-block .ob-meta {
|
||
margin-left: auto;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
}
|
||
.outfit-block .ob-regen-all {
|
||
height: 30px; padding: 0 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
font-size: 12px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
color: var(--black-alpha-72);
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.outfit-block .ob-regen-all:hover { background: var(--heat-8); border-color: var(--heat-40); color: var(--heat); }
|
||
.outfit-block .ob-regen-all svg { width: 12px; height: 12px; }
|
||
.outfit-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 12px;
|
||
}
|
||
@media (max-width: 1280px) { .outfit-grid { grid-template-columns: repeat(3, 1fr); } }
|
||
.outfit-card {
|
||
aspect-ratio: 3/4;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.outfit-card:hover { border-color: var(--black-alpha-32); }
|
||
.outfit-card .placeholder { width: 100%; height: 100%; border: 0; border-radius: 0; }
|
||
.outfit-card.selected {
|
||
border: 2px solid var(--heat);
|
||
box-shadow: 0 0 0 3px var(--heat-12);
|
||
}
|
||
.outfit-card .oc-tag {
|
||
position: absolute; bottom: 8px; left: 8px;
|
||
background: rgba(255,255,255,.95);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
padding: 2px 8px;
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
color: var(--black-alpha-56);
|
||
letter-spacing: .04em;
|
||
pointer-events: none;
|
||
}
|
||
.outfit-card .oc-actions {
|
||
position: absolute; top: 8px; right: 8px;
|
||
display: flex; gap: 4px;
|
||
opacity: 0;
|
||
transition: opacity var(--t-base);
|
||
}
|
||
.outfit-card:hover .oc-actions { opacity: 1; }
|
||
.outfit-card .oc-btn {
|
||
width: 26px; height: 26px;
|
||
background: rgba(0,0,0,.7);
|
||
color: var(--accent-white);
|
||
border: 0;
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
transition: background var(--t-base);
|
||
}
|
||
.outfit-card .oc-btn:hover { background: var(--accent-black); }
|
||
.outfit-card .oc-btn.del:hover { background: var(--accent-crimson); }
|
||
.outfit-card .oc-btn svg { width: 12px; height: 12px; }
|
||
.outfit-empty-step {
|
||
padding: 36px 24px;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
text-align: center;
|
||
color: var(--black-alpha-56);
|
||
}
|
||
.outfit-empty-step .ee-t { font-size: 13.5px; font-weight: 600; color: var(--accent-black); margin-bottom: 6px; }
|
||
.outfit-empty-step .ee-d { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
.outfit-empty-step .ee-btn { margin-top: 14px; }
|
||
|
||
/* ─── 新模特上传弹窗 ─── */
|
||
.nm-modal-bg {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,.42);
|
||
z-index: 200;
|
||
display: none;
|
||
align-items: center; justify-content: center;
|
||
padding: 20px;
|
||
animation: nmFade .15s ease;
|
||
}
|
||
.nm-modal-bg.open { display: flex; }
|
||
@keyframes nmFade { from { opacity: 0; } to { opacity: 1; } }
|
||
.nm-modal {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-lg);
|
||
box-shadow: 0 20px 60px rgba(0,0,0,.18);
|
||
width: 100%;
|
||
max-width: 820px;
|
||
max-height: calc(100vh - 40px);
|
||
display: flex; flex-direction: column;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
.nm-modal-h {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 18px 24px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
.nm-modal-h h3 {
|
||
font-size: 17px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
display: inline-flex; align-items: center; gap: 10px;
|
||
}
|
||
.nm-modal-h .nm-tag {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--heat);
|
||
background: var(--heat-12);
|
||
border: 1px solid var(--heat-40);
|
||
border-radius: var(--r-sm);
|
||
padding: 3px 9px;
|
||
letter-spacing: .04em;
|
||
}
|
||
.nm-modal-h .nm-x {
|
||
width: 28px; height: 28px;
|
||
background: transparent;
|
||
border: 0;
|
||
color: var(--black-alpha-56);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
.nm-modal-h .nm-x:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
.nm-modal-h .nm-x svg { width: 16px; height: 16px; }
|
||
.nm-modal-b {
|
||
padding: 22px 24px;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
overflow-y: auto;
|
||
}
|
||
.nm-left .nm-up-zone {
|
||
aspect-ratio: 3/4;
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-md);
|
||
background: var(--background-lighter);
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
gap: 10px;
|
||
color: var(--black-alpha-56);
|
||
cursor: pointer;
|
||
text-align: center;
|
||
padding: 16px;
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.nm-left .nm-up-zone:hover { border-color: var(--heat); background: var(--heat-8); color: var(--heat); }
|
||
.nm-left .nm-up-zone.has-img { padding: 0; border-style: solid; }
|
||
.nm-left .nm-up-zone img { width: 100%; height: 100%; object-fit: cover; }
|
||
.nm-left .nm-up-zone .nm-uz-ic svg { width: 26px; height: 26px; }
|
||
.nm-left .nm-up-zone .nm-uz-t { font-size: 13px; font-weight: 600; color: var(--accent-black); }
|
||
.nm-left .nm-up-zone:hover .nm-uz-t { color: var(--heat); }
|
||
.nm-left .nm-up-zone .nm-uz-d { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; line-height: 1.6; }
|
||
.nm-left .nm-up-zone .nm-uz-replace {
|
||
position: absolute; top: 8px; right: 8px;
|
||
background: rgba(0,0,0,.7); color: white;
|
||
font-size: 11px; padding: 4px 10px;
|
||
border-radius: var(--r-sm); border: 0;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
opacity: 0;
|
||
transition: opacity var(--t-base);
|
||
}
|
||
.nm-left .nm-up-zone.has-img:hover .nm-uz-replace { opacity: 1; }
|
||
.nm-left .nm-uz-hint {
|
||
margin-top: 10px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
line-height: 1.6;
|
||
}
|
||
.nm-right { display: flex; flex-direction: column; gap: 14px; }
|
||
.nm-right .field-label {
|
||
font-size: 12.5px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
}
|
||
.nm-right .field-label .req { color: var(--heat); }
|
||
.nm-right .field-hint {
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
margin-bottom: 6px;
|
||
}
|
||
.nm-right .nm-input, .nm-right .nm-select {
|
||
width: 100%;
|
||
height: 36px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--black-alpha-12);
|
||
border-radius: var(--r-md);
|
||
padding: 0 12px;
|
||
font-size: 13px;
|
||
color: var(--accent-black);
|
||
outline: none;
|
||
font-family: inherit;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.nm-right .nm-input:focus, .nm-right .nm-select:focus { border-color: var(--heat-40); }
|
||
.nm-right .nm-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||
.nm-right .nm-tags {
|
||
display: flex; flex-wrap: wrap; gap: 6px;
|
||
margin-top: 4px;
|
||
}
|
||
.nm-right .nm-tag-chip {
|
||
height: 26px; padding: 0 10px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-72);
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.nm-right .nm-tag-chip:hover { border-color: var(--black-alpha-32); color: var(--accent-black); }
|
||
.nm-right .nm-tag-chip.active {
|
||
background: var(--heat-12);
|
||
border-color: var(--heat-40);
|
||
color: var(--heat);
|
||
font-weight: 600;
|
||
}
|
||
.nm-modal-f {
|
||
padding: 14px 24px;
|
||
border-top: 1px solid var(--border-faint);
|
||
background: var(--background-lighter);
|
||
display: flex; align-items: center; gap: 12px;
|
||
}
|
||
.nm-modal-f .nm-cost {
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
margin-right: auto;
|
||
}
|
||
.nm-modal-f .nm-cost .price { color: var(--heat); font-weight: 700; }
|
||
|
||
/* ─── 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);
|
||
display: flex; flex-direction: column;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
}
|
||
.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;
|
||
}
|
||
|
||
/* ─── 中央 step pane · 底部确认按钮区 (sticky 固定在视口底) ─── */
|
||
.pane-cta {
|
||
margin: auto -32px 0; /* 推到底 + 撑满 canvas-inner 左右 padding */
|
||
padding: 14px 32px;
|
||
border-top: 1px solid var(--border-faint);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
position: sticky;
|
||
bottom: 0;
|
||
background: var(--background-base);
|
||
z-index: 5;
|
||
}
|
||
/* 顶部加渐变模糊带,让 sticky 区分滚动内容 */
|
||
.pane-cta::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -16px; left: 0; right: 0;
|
||
height: 16px;
|
||
background: linear-gradient(to top, var(--background-base), transparent);
|
||
pointer-events: none;
|
||
}
|
||
.pane-cta .pane-cost {
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
margin-right: auto;
|
||
}
|
||
.pane-cta .pane-cost .price { color: var(--heat); font-weight: 700; }
|
||
.pane-cta .btn-lg {
|
||
padding: 10px 22px;
|
||
font-size: 13.5px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ─── 右栏 · 资产预览 (始终显示) ─── */
|
||
#right-asset-preview {
|
||
display: flex; flex-direction: column;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
.rap-head {
|
||
padding: 18px 22px 14px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
.rap-head h3 {
|
||
font-size: 16px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
letter-spacing: -.012em;
|
||
line-height: 1.3;
|
||
word-break: break-all;
|
||
}
|
||
.rap-head .rap-meta {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
margin-top: 6px;
|
||
letter-spacing: .02em;
|
||
}
|
||
.rap-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 14px 22px 18px;
|
||
}
|
||
.rap-group { margin-bottom: 18px; }
|
||
.rap-group:last-child { margin-bottom: 0; }
|
||
.rap-h {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
.rap-h-t {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-56);
|
||
letter-spacing: .04em;
|
||
font-weight: 600;
|
||
}
|
||
.rap-h-c {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--heat);
|
||
background: var(--heat-12);
|
||
padding: 1px 7px;
|
||
border-radius: var(--r-sm);
|
||
letter-spacing: .04em;
|
||
font-weight: 700;
|
||
}
|
||
.rap-group.empty .rap-h-c { color: var(--black-alpha-32); background: var(--black-alpha-4); }
|
||
.rap-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 6px;
|
||
}
|
||
.rap-item {
|
||
aspect-ratio: 1;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
position: relative;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.rap-item:hover { border-color: var(--black-alpha-32); }
|
||
.rap-item img, .rap-item .placeholder {
|
||
width: 100%; height: 100%; object-fit: cover;
|
||
border: 0; border-radius: 0;
|
||
}
|
||
.rap-item .rap-mtag {
|
||
position: absolute; bottom: 2px; left: 2px; right: 2px;
|
||
background: rgba(0,0,0,.6); color: var(--accent-white);
|
||
font-family: var(--font-mono);
|
||
font-size: 8.5px;
|
||
padding: 1px 4px;
|
||
border-radius: 2px;
|
||
letter-spacing: .02em;
|
||
text-align: center;
|
||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
.rap-empty {
|
||
grid-column: 1 / -1;
|
||
padding: 14px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
text-align: center;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-32);
|
||
letter-spacing: .02em;
|
||
}
|
||
.rap-cta-zone {
|
||
padding: 14px 22px 16px;
|
||
border-top: 1px solid var(--border-faint);
|
||
background: var(--surface);
|
||
}
|
||
.rap-cta-zone .btn-lg {
|
||
width: 100%;
|
||
justify-content: center;
|
||
font-size: 13.5px;
|
||
font-weight: 600;
|
||
padding: 12px 14px;
|
||
}
|
||
.rap-cta-zone .btn-primary[disabled] {
|
||
background: var(--black-alpha-8);
|
||
color: var(--black-alpha-32);
|
||
cursor: not-allowed;
|
||
pointer-events: none;
|
||
}
|
||
.rap-cta-hint {
|
||
margin-top: 8px;
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
text-align: center;
|
||
}
|
||
.rap-cta-hint.ready { color: var(--accent-forest); }
|
||
|
||
/* ─── 右栏 · 折叠按钮 + 状态 ─── */
|
||
#right-asset-preview .rap-head {
|
||
display: flex; align-items: flex-start; gap: 10px;
|
||
}
|
||
#right-asset-preview .rap-head > div { flex: 1; min-width: 0; }
|
||
.rap-collapse-btn {
|
||
width: 28px; height: 28px;
|
||
background: transparent; border: 0;
|
||
color: var(--black-alpha-48);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
flex-shrink: 0;
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
.rap-collapse-btn:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
.rap-collapse-btn svg { width: 16px; height: 16px; }
|
||
.wb-controls.collapsed { width: 56px !important; min-width: 56px !important; }
|
||
.wb-controls.collapsed #right-asset-preview > *:not(.rap-collapse-floating) { display: none; }
|
||
.rap-collapse-floating {
|
||
display: none;
|
||
width: 56px; height: 56px;
|
||
align-items: center; justify-content: center;
|
||
cursor: pointer;
|
||
color: var(--black-alpha-56);
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
.rap-collapse-floating svg { width: 18px; height: 18px; }
|
||
.wb-controls.collapsed .rap-collapse-floating { display: flex; }
|
||
.wb-controls.collapsed .rap-collapse-floating:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
|
||
/* ─── 资产 "加入商品" 勾选 chip ─── */
|
||
.pick-chip {
|
||
position: absolute; bottom: 8px; right: 8px;
|
||
height: 26px; padding: 0 10px;
|
||
background: rgba(255,255,255,.95);
|
||
border: 1px solid var(--black-alpha-24);
|
||
border-radius: 999px;
|
||
font-size: 11px; font-weight: 600;
|
||
color: var(--black-alpha-56);
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
cursor: pointer;
|
||
z-index: 4;
|
||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||
font-family: inherit;
|
||
}
|
||
.pick-chip:hover { border-color: var(--heat); color: var(--heat); }
|
||
.pick-chip svg { width: 12px; height: 12px; }
|
||
.pick-chip.picked {
|
||
background: var(--heat);
|
||
border-color: var(--heat);
|
||
color: var(--accent-white);
|
||
}
|
||
.pick-chip.picked:hover { background: var(--heat); }
|
||
|
||
/* ─── 空状态 (Step3/4: 还未触发生成) ─── */
|
||
.gen-empty {
|
||
padding: 60px 40px;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
text-align: center;
|
||
color: var(--black-alpha-56);
|
||
}
|
||
.gen-empty .ge-ic {
|
||
width: 56px; height: 56px;
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border-radius: 50%;
|
||
display: grid; place-items: center;
|
||
margin: 0 auto 16px;
|
||
}
|
||
.gen-empty .ge-ic svg { width: 24px; height: 24px; }
|
||
.gen-empty .ge-t { font-size: 14px; font-weight: 600; color: var(--accent-black); margin-bottom: 6px; }
|
||
.gen-empty .ge-d { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; line-height: 1.6; }
|
||
|
||
/* ─── 模特卡片 · 勾选 checkbox + 详情入口 ─── */
|
||
.model-card .mc-check {
|
||
position: absolute; top: 8px; right: 8px;
|
||
width: 24px; height: 24px;
|
||
background: rgba(255,255,255,.95);
|
||
border: 1.5px solid var(--black-alpha-32);
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
z-index: 3;
|
||
display: grid; place-items: center;
|
||
transition: background var(--t-base), border-color var(--t-base);
|
||
}
|
||
.model-card .mc-check:hover { border-color: var(--heat); }
|
||
.model-card .mc-check svg {
|
||
width: 14px; height: 14px;
|
||
color: transparent;
|
||
transition: color var(--t-base);
|
||
}
|
||
.model-card.selected .mc-check {
|
||
background: var(--heat);
|
||
border-color: var(--heat);
|
||
}
|
||
.model-card.selected .mc-check svg { color: var(--accent-white); }
|
||
/* 取消旧的勾标 (覆盖之前的 ::after) */
|
||
.model-card.selected::after { display: none; }
|
||
|
||
/* ─── 模特详情弹窗 ─── */
|
||
.md-modal-bg {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,.42);
|
||
z-index: 210;
|
||
display: none;
|
||
align-items: center; justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
.md-modal-bg.open { display: flex; }
|
||
.md-modal {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-lg);
|
||
box-shadow: 0 20px 60px rgba(0,0,0,.18);
|
||
width: 100%;
|
||
max-width: 760px;
|
||
max-height: calc(100vh - 40px);
|
||
display: flex; flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.md-h {
|
||
padding: 16px 22px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
}
|
||
.md-h h3 {
|
||
font-size: 17px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
display: inline-flex; align-items: center; gap: 10px;
|
||
}
|
||
.md-h .md-kind {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-56);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
padding: 3px 9px;
|
||
border-radius: var(--r-sm);
|
||
letter-spacing: .04em;
|
||
}
|
||
.md-h .md-kind.mine {
|
||
color: var(--heat); background: var(--heat-12); border-color: var(--heat-40);
|
||
}
|
||
.md-h .md-x {
|
||
width: 28px; height: 28px;
|
||
background: transparent; border: 0;
|
||
color: var(--black-alpha-56);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
.md-h .md-x:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
.md-h .md-x svg { width: 16px; height: 16px; }
|
||
.md-body {
|
||
padding: 22px;
|
||
overflow-y: auto;
|
||
flex: 1;
|
||
}
|
||
.md-views {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px;
|
||
}
|
||
.md-views .placeholder, .md-views img {
|
||
aspect-ratio: 3/4;
|
||
border-radius: var(--r-md);
|
||
width: 100%;
|
||
}
|
||
.md-views img { object-fit: cover; border: 1px solid var(--border-faint); }
|
||
.md-tags {
|
||
display: flex; flex-wrap: wrap; gap: 6px;
|
||
margin-top: 18px;
|
||
}
|
||
.md-tags .tag {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-72);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
padding: 4px 9px;
|
||
letter-spacing: .02em;
|
||
}
|
||
.md-meta {
|
||
margin-top: 16px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--border-faint);
|
||
display: flex; flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.md-meta .row {
|
||
display: flex; gap: 14px;
|
||
font-size: 13px;
|
||
color: var(--black-alpha-72);
|
||
}
|
||
.md-meta .k {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
width: 80px;
|
||
letter-spacing: .04em;
|
||
flex-shrink: 0;
|
||
}
|
||
.md-foot {
|
||
padding: 14px 22px;
|
||
border-top: 1px solid var(--border-faint);
|
||
background: var(--background-lighter);
|
||
display: flex; align-items: center; gap: 12px;
|
||
}
|
||
.md-foot .md-foot-info {
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
margin-right: auto;
|
||
letter-spacing: .02em;
|
||
}
|
||
.md-foot .md-foot-info.is-selected { color: var(--accent-forest); }
|
||
|
||
/* ─── Bottom bar (sticky in right column) ─── */
|
||
.wb-bottom {
|
||
flex-shrink: 0;
|
||
padding: 12px 22px 14px;
|
||
background: var(--surface);
|
||
border-top: 1px solid var(--border-faint);
|
||
display: flex; flex-direction: column; gap: 8px;
|
||
}
|
||
.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; }
|
||
|
||
/* ============================================================
|
||
新建商品 · 表单 view (参考图样式) · 默认显示
|
||
· 切到 AI workflow mode 时 .app 隐藏, .wb 显示
|
||
============================================================ */
|
||
.wb { display: none; }
|
||
body.workflow-mode .app { display: none !important; }
|
||
body.workflow-mode .wb { display: grid; }
|
||
body.workflow-mode .topbar { display: none; }
|
||
|
||
/* form mode 沿用 Shell 标准布局,
|
||
注意: Shell.render() 会把 #page 内容平铺到 #page-content 并删掉 #page,
|
||
所以限宽必须加在 .page-head 和 .pc-layout 上 (它们才是实际存在的元素) */
|
||
body:not(.workflow-mode) .page-head {
|
||
max-width: 1280px;
|
||
margin: 0 auto 24px;
|
||
}
|
||
body:not(.workflow-mode) .btn-guide {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
font-size: 13px;
|
||
color: var(--black-alpha-56);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
padding: 8px 10px;
|
||
border-radius: var(--r-md);
|
||
font-family: inherit;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
body:not(.workflow-mode) .btn-guide:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
body:not(.workflow-mode) .btn-guide svg { width: 14px; height: 14px; }
|
||
|
||
/* ─── 主区 · 两栏 grid (左主右辅) ─── */
|
||
.pc-layout {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 440px;
|
||
gap: 20px;
|
||
padding-bottom: 32px;
|
||
max-width: 1280px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* ─── 左栏 · 表单区 ─── */
|
||
.pc-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
||
.form-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 24px 28px;
|
||
}
|
||
.form-card .form-h {
|
||
font-size: 15px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
margin-bottom: 18px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
/* ─── 表单底部 action 行 (form-card 内) ─── */
|
||
.form-card .form-footer {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin-top: 24px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid var(--border-faint);
|
||
}
|
||
.form-card .form-footer .btn-guide { margin-right: auto; }
|
||
.form-card .field { margin-bottom: 16px; }
|
||
.form-card .field:last-child { margin-bottom: 0; }
|
||
.form-card .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 16px; }
|
||
.form-card .field-label {
|
||
display: block;
|
||
font-size: 13px; font-weight: 500;
|
||
color: var(--accent-black);
|
||
margin-bottom: 6px;
|
||
}
|
||
.form-card .field-label .req { color: var(--heat); margin-left: 2px; }
|
||
.form-card .field-label .opt {
|
||
color: var(--black-alpha-48);
|
||
font-weight: 400;
|
||
font-size: 12px;
|
||
margin-left: 6px;
|
||
}
|
||
.form-card .input,
|
||
.form-card .select,
|
||
.form-card .textarea {
|
||
width: 100%;
|
||
height: 38px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--black-alpha-12);
|
||
border-radius: var(--r-md);
|
||
padding: 0 14px;
|
||
font-size: 13.5px;
|
||
color: var(--accent-black);
|
||
outline: none;
|
||
font-family: inherit;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.form-card .textarea {
|
||
height: auto;
|
||
min-height: 80px;
|
||
padding: 10px 14px;
|
||
line-height: 1.6;
|
||
resize: vertical;
|
||
}
|
||
.form-card .input:focus,
|
||
.form-card .select:focus,
|
||
.form-card .textarea:focus {
|
||
border-color: var(--heat-40);
|
||
box-shadow: inset 0 0 0 1px var(--heat-40);
|
||
}
|
||
.form-card .char-count {
|
||
display: block;
|
||
margin-top: 4px;
|
||
text-align: right;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-32);
|
||
letter-spacing: .02em;
|
||
}
|
||
|
||
/* ─── 商品主图区 · 左右两栏 (上传 + 示例) ─── */
|
||
.form-card .pf-upload-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
|
||
gap: 16px;
|
||
align-items: stretch;
|
||
}
|
||
.form-card .pf-upload-zone {
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-md);
|
||
padding: 28px 20px;
|
||
background: var(--background-lighter);
|
||
cursor: pointer;
|
||
text-align: center;
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
min-height: 180px;
|
||
}
|
||
.form-card .pf-upload-zone:hover { border-color: var(--heat); background: var(--heat-8); }
|
||
.form-card .pf-upload-zone .uz-ic {
|
||
width: 44px; height: 44px;
|
||
margin: 0 auto 10px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--heat-20);
|
||
border-radius: var(--r-md);
|
||
color: var(--heat);
|
||
display: grid; place-items: center;
|
||
}
|
||
.form-card .pf-upload-zone .uz-ic svg { width: 20px; height: 20px; }
|
||
.form-card .pf-upload-zone .uz-t { font-size: 14px; color: var(--accent-black); font-weight: 500; }
|
||
.form-card .pf-upload-zone .uz-t strong { color: var(--heat); font-weight: 600; }
|
||
.form-card .pf-upload-zone .uz-d {
|
||
margin-top: 8px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
}
|
||
.form-card .pf-example {
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 16px;
|
||
display: flex; flex-direction: column; gap: 10px;
|
||
}
|
||
.form-card .pf-example .ex-h {
|
||
font-size: 13px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.form-card .pf-example .ex-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
}
|
||
.form-card .pf-example .ex-grid .ex-thumb {
|
||
aspect-ratio: 1;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden;
|
||
position: relative;
|
||
display: grid; place-items: center;
|
||
color: var(--black-alpha-32);
|
||
}
|
||
.form-card .pf-example .ex-grid .ex-thumb svg { width: 22px; height: 22px; }
|
||
.form-card .pf-example .ex-grid .ex-thumb::after {
|
||
content: '';
|
||
position: absolute; inset: 0;
|
||
background:
|
||
repeating-linear-gradient(135deg, transparent 0 6px, rgba(0,0,0,.03) 6px 7px);
|
||
pointer-events: none;
|
||
}
|
||
.form-card .pf-example .ex-d {
|
||
font-size: 12px;
|
||
color: var(--black-alpha-56);
|
||
line-height: 1.5;
|
||
}
|
||
.form-card .pf-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
.form-card .pf-grid:empty { display: none; }
|
||
.form-card .pf-thumb {
|
||
aspect-ratio: 1;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
position: relative;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
}
|
||
.form-card .pf-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||
.form-card .pf-thumb .pf-x {
|
||
position: absolute; top: 4px; right: 4px;
|
||
width: 22px; height: 22px;
|
||
background: rgba(0,0,0,.7);
|
||
color: var(--accent-white);
|
||
border: 0; border-radius: 50%;
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
opacity: 0;
|
||
transition: opacity var(--t-base);
|
||
}
|
||
.form-card .pf-thumb:hover .pf-x { opacity: 1; }
|
||
.form-card .pf-thumb .pf-x svg { width: 11px; height: 11px; }
|
||
|
||
/* ─── bullet-list (核心卖点) · form view 复用原样式 ─── */
|
||
.form-card .bullet-list { list-style: none; padding: 0; margin: 0; }
|
||
.form-card .bullet-list .bl-item,
|
||
.form-card .bullet-list .bl-add {
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 8px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
margin-bottom: 6px;
|
||
font-size: 13.5px;
|
||
}
|
||
.form-card .bullet-list .bl-add { background: transparent; border-style: dashed; }
|
||
.form-card .bullet-list .num {
|
||
width: 22px; height: 22px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--heat);
|
||
font-weight: 700;
|
||
display: grid; place-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.form-card .bullet-list .bl-text { flex: 1; color: var(--accent-black); }
|
||
.form-card .bullet-list .bl-input {
|
||
flex: 1;
|
||
background: transparent; border: 0; outline: none;
|
||
font-size: 13.5px;
|
||
color: var(--accent-black);
|
||
font-family: inherit;
|
||
}
|
||
.form-card .bullet-list .bl-x {
|
||
width: 22px; height: 22px;
|
||
color: var(--black-alpha-48);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
border-radius: var(--r-sm);
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
.form-card .bullet-list .bl-x:hover { color: var(--accent-crimson); background: var(--crimson-bg); }
|
||
.form-card .bullet-list .bl-x svg { width: 11px; height: 11px; }
|
||
|
||
/* ─── 右栏 · 辅助 cards ─── */
|
||
.pc-aside { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
||
|
||
/* AI 入口 card */
|
||
.pc-ai-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 20px;
|
||
}
|
||
.pc-ai-card .pa-h h3 {
|
||
font-size: 14.5px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.pc-ai-card .pa-h p {
|
||
margin-top: 6px;
|
||
font-size: 12.5px;
|
||
color: var(--black-alpha-56);
|
||
line-height: 1.6;
|
||
}
|
||
.pc-ai-card .pa-entries { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
|
||
.pc-ai-card .pa-entry {
|
||
display: flex; align-items: center; gap: 12px;
|
||
padding: 10px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
}
|
||
.pc-ai-card .pa-entry:hover { border-color: var(--heat-40); background: var(--heat-8); }
|
||
.pc-ai-card .pa-entry .pe-thumb {
|
||
width: 56px; height: 56px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
flex-shrink: 0;
|
||
display: grid; place-items: center;
|
||
color: var(--heat);
|
||
}
|
||
.pc-ai-card .pa-entry .pe-thumb svg { width: 22px; height: 22px; }
|
||
.pc-ai-card .pa-entry .pe-info { flex: 1; min-width: 0; }
|
||
.pc-ai-card .pa-entry .pe-info .nm { font-size: 13px; font-weight: 600; color: var(--accent-black); }
|
||
.pc-ai-card .pa-entry .pe-info .dd { font-size: 11.5px; color: var(--black-alpha-56); margin-top: 3px; line-height: 1.5; }
|
||
.pc-ai-card .pa-entry .pe-cta {
|
||
font-size: 12.5px;
|
||
color: var(--heat);
|
||
background: var(--surface);
|
||
border: 1px solid var(--heat-40);
|
||
border-radius: var(--r-md);
|
||
padding: 5px 12px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
flex-shrink: 0;
|
||
transition: background var(--t-base);
|
||
}
|
||
.pc-ai-card .pa-entry .pe-cta:hover { background: var(--heat-12); }
|
||
|
||
/* 创建小贴士 card */
|
||
.pc-tips-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 20px;
|
||
}
|
||
.pc-tips-card .pt-h {
|
||
font-size: 14px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
margin-bottom: 12px;
|
||
}
|
||
.pc-tips-card .pt-item {
|
||
display: flex; gap: 10px;
|
||
padding: 8px 0;
|
||
border-bottom: 1px dashed var(--border-faint);
|
||
}
|
||
.pc-tips-card .pt-item:last-of-type { border-bottom: 0; }
|
||
.pc-tips-card .pt-item .pt-ic {
|
||
width: 20px; height: 20px;
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border-radius: var(--r-sm);
|
||
display: grid; place-items: center;
|
||
flex-shrink: 0;
|
||
margin-top: 1px;
|
||
}
|
||
.pc-tips-card .pt-item .pt-ic svg { width: 11px; height: 11px; }
|
||
.pc-tips-card .pt-item .pt-text { flex: 1; }
|
||
.pc-tips-card .pt-item .pt-text .t { font-size: 12.5px; font-weight: 600; color: var(--accent-black); }
|
||
.pc-tips-card .pt-item .pt-text .d { font-size: 11.5px; color: var(--black-alpha-56); margin-top: 3px; line-height: 1.5; }
|
||
.pc-tips-card .pt-guide {
|
||
margin-top: 10px;
|
||
display: block;
|
||
text-align: center;
|
||
padding: 8px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
font-size: 12.5px;
|
||
color: var(--black-alpha-72);
|
||
cursor: pointer;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-tips-card .pt-guide:hover { background: var(--heat-12); color: var(--heat); border-color: var(--heat-40); }
|
||
|
||
/* 商品创建后 流程图 */
|
||
.pc-after-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 20px;
|
||
}
|
||
.pc-after-card .pa-title {
|
||
font-size: 14px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
margin-bottom: 14px;
|
||
}
|
||
.pc-after-card .pa-flow {
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.pc-after-card .pa-node {
|
||
flex: 1;
|
||
text-align: center;
|
||
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
||
}
|
||
.pc-after-card .pa-node .ic {
|
||
width: 36px; height: 36px;
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border-radius: 50%;
|
||
display: grid; place-items: center;
|
||
}
|
||
.pc-after-card .pa-node .ic svg { width: 16px; height: 16px; }
|
||
.pc-after-card .pa-node .lbl {
|
||
font-size: 11px;
|
||
color: var(--black-alpha-72);
|
||
line-height: 1.4;
|
||
}
|
||
.pc-after-card .pa-arrow {
|
||
color: var(--black-alpha-32);
|
||
flex-shrink: 0;
|
||
}
|
||
.pc-after-card .pa-arrow svg { width: 14px; height: 14px; }
|
||
|
||
@media (max-width: 1100px) {
|
||
.pc-layout { grid-template-columns: 1fr; }
|
||
.form-card .pf-upload-row { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
/* ─── 新建商品 · drawer 形态 ─── */
|
||
.pc-drawer { width: 820px; max-width: 100vw; }
|
||
.pc-drawer .drawer-h h3 { font-size: 16px; font-weight: 600; }
|
||
.pc-drawer .drawer-b { padding: 24px 28px; }
|
||
/* drawer 内的 form-card 去掉外观, 由 drawer 自身提供容器 */
|
||
.pc-drawer .drawer-b .form-card {
|
||
background: transparent;
|
||
border: 0;
|
||
padding: 0;
|
||
border-radius: 0;
|
||
}
|
||
.pc-drawer .drawer-f {
|
||
padding: 14px 24px;
|
||
background: var(--surface);
|
||
align-items: center;
|
||
}
|
||
.pc-drawer .drawer-f .btn-guide {
|
||
margin-right: auto;
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
font-size: 13px;
|
||
color: var(--black-alpha-56);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
padding: 8px 10px;
|
||
border-radius: var(--r-md);
|
||
font-family: inherit;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-drawer .drawer-f .btn-guide:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
.pc-drawer .drawer-f .btn-guide svg { width: 14px; height: 14px; }
|
||
@media (max-width: 900px) {
|
||
.pc-drawer .drawer-b .pf-upload-row { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ============================================================
|
||
新建商品 · Form view (默认显示) · 参考图样式
|
||
· 切到 AI workflow 时 body 加 .workflow-mode, 这块被 .app 隐藏覆盖
|
||
============================================================ -->
|
||
<div id="page"></div>
|
||
|
||
<!-- 右弹窗 · 新建商品 -->
|
||
<div class="drawer-bg show" id="pc-drawer-bg"></div>
|
||
<aside class="drawer show pc-drawer" id="pc-drawer" role="dialog" aria-label="新建商品">
|
||
<div class="drawer-h">
|
||
<h3>新建商品</h3>
|
||
<button class="x" type="button" id="pc-drawer-close" aria-label="关闭">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="drawer-b">
|
||
<div class="form-card">
|
||
<div class="form-h">基础信息</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">商品名称<span class="req">*</span></label>
|
||
<input class="input" id="pf-name" placeholder="请输入商品名称(必填)" maxlength="100">
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div>
|
||
<label class="field-label">品类<span class="req">*</span></label>
|
||
<select class="select" id="pf-cat" data-cat-select>
|
||
<option>美妆个护</option>
|
||
<option>服饰内衣</option>
|
||
<option>食品饮料</option>
|
||
<option>家居家电</option>
|
||
<option>数码 3C</option>
|
||
<option>个护清洁</option>
|
||
<option>运动户外</option>
|
||
<option>母婴亲子</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="field-label">目标人群<span class="opt">(选填)</span></label>
|
||
<input class="input" id="pf-target" placeholder="例: 22-32 岁女性、敏感肌、办公室通勤">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">商品主图<span class="req">*</span></label>
|
||
<input type="file" id="pf-file" accept="image/*" multiple hidden>
|
||
<div class="pf-upload-row">
|
||
<div class="pf-upload-zone" id="pf-zone">
|
||
<div class="uz-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
|
||
</div>
|
||
<div class="uz-t">点击上传或<strong>拖拽图片</strong>到此处</div>
|
||
<div class="uz-d">// 支持 JPG、PNG 格式,建议尺寸 800×800 以上,大小不超过 10MB</div>
|
||
</div>
|
||
<div class="pf-example">
|
||
<div class="ex-h">示例图</div>
|
||
<div class="ex-grid">
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4h10l1 4v12H6V8l1-4z"/><path d="M9 4v3M15 4v3M9 11h6M9 14h6"/></svg></div>
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="5" width="12" height="15" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg></div>
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3h8l1 5v12H7V8l1-5z"/><circle cx="12" cy="13" r="2.5"/></svg></div>
|
||
</div>
|
||
<div class="ex-d">优质的商品图有助于生成更好的素材效果</div>
|
||
</div>
|
||
</div>
|
||
<div class="pf-grid" id="pf-grid"></div>
|
||
</div>
|
||
|
||
<div class="field" style="margin-bottom: 0;">
|
||
<label class="field-label">核心卖点<span class="req">*</span></label>
|
||
<ul class="bullet-list" id="pf-bullets" data-bl>
|
||
<li class="bl-add"><span class="num">+</span><input class="bl-input" placeholder="添加新卖点 · 回车确认"></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="drawer-f">
|
||
<button class="btn-guide" type="button" onclick="Shell.toast('使用指南', '点击查看完整填写指南')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 4M12 17h.01"/></svg>
|
||
使用指南
|
||
</button>
|
||
<button class="btn" type="button" id="pc-cancel-btn">取消</button>
|
||
<button class="btn btn-primary" type="button" id="pc-save-btn">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
|
||
创建商品
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
|
||
|
||
<script src="assets/shell.js?v=202605211643"></script>
|
||
<script>
|
||
// ============================================================
|
||
// 调用 Shell.render · 注入网站 sidebar (form mode 显示)
|
||
// ============================================================
|
||
Shell.render({
|
||
active: 'products',
|
||
crumbs: [{ label: '工作台', href: 'index.html' }, { label: '商品库', href: 'products.html' }, { label: '新建商品' }]
|
||
});
|
||
|
||
// ============================================================
|
||
// Form view · 多图上传 (与 workflow 独立)
|
||
// ============================================================
|
||
const PF_MAX = 5;
|
||
const pfFiles = []; // {id, dataUrl, name}
|
||
const pfFile = document.getElementById('pf-file');
|
||
const pfZone = document.getElementById('pf-zone');
|
||
const pfGrid = document.getElementById('pf-grid');
|
||
|
||
function pfUid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
||
function pfEsc(s) { return (s || '').replace(/[<>&"]/g, c => ({ '<':'<','>':'>','&':'&','"':'"' })[c]); }
|
||
|
||
function pfRender() {
|
||
pfGrid.innerHTML = pfFiles.map(u => `
|
||
<div class="pf-thumb" data-id="${u.id}">
|
||
<img src="${u.dataUrl}" alt="${pfEsc(u.name)}">
|
||
<button class="pf-x" type="button" title="删除">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
pfGrid.querySelectorAll('.pf-thumb .pf-x').forEach(b => {
|
||
b.onclick = e => {
|
||
e.stopPropagation();
|
||
const id = b.closest('.pf-thumb').dataset.id;
|
||
const i = pfFiles.findIndex(f => f.id === id);
|
||
if (i >= 0) pfFiles.splice(i, 1);
|
||
pfRender();
|
||
};
|
||
});
|
||
pfGrid.querySelectorAll('.pf-thumb img').forEach(img => {
|
||
img.onclick = () => Shell._openLightbox(img.src, img.alt);
|
||
});
|
||
}
|
||
|
||
function pfAdd(fileList) {
|
||
const remaining = PF_MAX - pfFiles.length;
|
||
if (remaining <= 0) { Shell.toast('已达上限', `${PF_MAX} / ${PF_MAX}`); return; }
|
||
const incoming = [...fileList].filter(f => f.type.startsWith('image/'));
|
||
const accepted = incoming.slice(0, remaining);
|
||
let done = 0;
|
||
accepted.forEach(f => {
|
||
const r = new FileReader();
|
||
r.onload = e => {
|
||
pfFiles.push({ id: pfUid(), dataUrl: e.target.result, name: f.name });
|
||
if (++done === accepted.length) {
|
||
pfRender();
|
||
Shell.toast('图片已添加', `共 ${pfFiles.length} 张`);
|
||
}
|
||
};
|
||
r.readAsDataURL(f);
|
||
});
|
||
}
|
||
|
||
pfZone.onclick = () => { if (pfFiles.length < PF_MAX) pfFile.click(); };
|
||
pfZone.addEventListener('dragover', e => { e.preventDefault(); pfZone.style.borderColor = 'var(--heat)'; });
|
||
pfZone.addEventListener('dragleave', () => { pfZone.style.borderColor = ''; });
|
||
pfZone.addEventListener('drop', e => {
|
||
e.preventDefault();
|
||
pfZone.style.borderColor = '';
|
||
if (e.dataTransfer?.files?.length) pfAdd(e.dataTransfer.files);
|
||
});
|
||
pfFile.onchange = e => { pfAdd(e.target.files); e.target.value = ''; };
|
||
|
||
// ============================================================
|
||
// Form view · bullet-list (核心卖点) 交互
|
||
// ============================================================
|
||
(function initBullets() {
|
||
const list = document.getElementById('pf-bullets');
|
||
if (!list) return;
|
||
const addInput = list.querySelector('.bl-add .bl-input');
|
||
const xSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
const renumber = () => {
|
||
[...list.querySelectorAll('.bl-item')].forEach((li, i) => {
|
||
li.querySelector('.num').textContent = i + 1;
|
||
});
|
||
};
|
||
const bindX = x => {
|
||
x.onclick = () => {
|
||
const li = x.closest('li');
|
||
li.style.transition = 'opacity .15s, transform .15s';
|
||
li.style.opacity = 0;
|
||
li.style.transform = 'translateX(-8px)';
|
||
setTimeout(() => { li.remove(); renumber(); }, 150);
|
||
};
|
||
};
|
||
const addBullet = text => {
|
||
const t = (text || '').trim();
|
||
if (!t) return;
|
||
const li = document.createElement('li');
|
||
li.className = 'bl-item';
|
||
li.innerHTML = `<span class="num">0</span><span class="bl-text">${pfEsc(t)}</span><span class="bl-x" title="删除">${xSvg}</span>`;
|
||
list.querySelector('.bl-add').before(li);
|
||
bindX(li.querySelector('.bl-x'));
|
||
renumber();
|
||
};
|
||
addInput?.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
addBullet(addInput.value);
|
||
addInput.value = '';
|
||
}
|
||
});
|
||
})();
|
||
|
||
// ============================================================
|
||
// 保存 + Mode 切换 · form ↔ workflow
|
||
// ============================================================
|
||
function pfValidate() {
|
||
const name = document.getElementById('pf-name').value.trim();
|
||
if (!name) { Shell.toast('请填写商品名称'); return false; }
|
||
if (pfFiles.length < 1) { Shell.toast('请至少上传 1 张商品图'); return false; }
|
||
return true;
|
||
}
|
||
|
||
function pfSave(redirect = true) {
|
||
if (!pfValidate()) return false;
|
||
Shell.toast('商品已创建', document.getElementById('pf-name').value);
|
||
if (redirect) setTimeout(() => location.href = 'products.html', 600);
|
||
return true;
|
||
}
|
||
|
||
// drawer 按钮: 保存 / 取消 / 关闭 / 点击遮罩
|
||
document.getElementById('pc-save-btn').onclick = () => pfSave(true);
|
||
const _back = () => location.href = 'products.html';
|
||
document.getElementById('pc-cancel-btn').onclick = _back;
|
||
document.getElementById('pc-drawer-close').onclick = _back;
|
||
document.getElementById('pc-drawer-bg').onclick = _back;
|
||
document.addEventListener('keydown', e => { if (e.key === 'Escape') _back(); });
|
||
|
||
// (AI 入口跳转 / pfSyncToWorkflow 已随 drawer 形态移除)
|
||
|
||
// 把 form view 的数据同步到 workflow (.wb) 的 state 和 DOM
|
||
function pfSyncToWorkflow() {
|
||
if (typeof state === 'undefined') return; // workflow JS 未初始化
|
||
// 商品名
|
||
const name = document.getElementById('pf-name').value.trim();
|
||
const aiName = document.getElementById('ai-name');
|
||
if (aiName) aiName.value = name;
|
||
const topbarName = document.getElementById('topbar-name');
|
||
if (topbarName) topbarName.value = name;
|
||
// 品类
|
||
const cat = document.getElementById('pf-cat').value;
|
||
const aiCat = document.getElementById('ai-cat');
|
||
if (aiCat) aiCat.value = cat;
|
||
// 目标人群
|
||
const target = document.getElementById('pf-target').value.trim();
|
||
const aiTarget = document.getElementById('ai-target');
|
||
if (aiTarget) aiTarget.value = target;
|
||
// 卖点
|
||
const aiBullets = document.getElementById('ai-bullets');
|
||
if (aiBullets) {
|
||
[...aiBullets.querySelectorAll('.bl-item')].forEach(li => li.remove());
|
||
const xSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
[...document.querySelectorAll('#pf-bullets .bl-item .bl-text')].forEach((el, i) => {
|
||
const t = el.textContent;
|
||
const li = document.createElement('li');
|
||
li.className = 'bl-item';
|
||
li.innerHTML = `<span class="num">${i + 1}</span><span class="bl-text">${pfEsc(t)}</span><span class="bl-x" title="删除">${xSvg}</span>`;
|
||
aiBullets.querySelector('.bl-add').before(li);
|
||
});
|
||
}
|
||
// 原图 → uploads
|
||
state.uploads.length = 0;
|
||
pfFiles.forEach(f => state.uploads.push({ id: f.id, dataUrl: f.dataUrl, name: f.name, type: 'image/' }));
|
||
if (typeof renderS1Grid === 'function') renderS1Grid();
|
||
if (typeof refreshSideAssets === 'function') refreshSideAssets();
|
||
if (typeof updateBottom === 'function') updateBottom();
|
||
}
|
||
|
||
// ============================================================
|
||
// STATE
|
||
// ============================================================
|
||
const state = {
|
||
mode: 'ai',
|
||
step: 1, // 1..4
|
||
sub: 1, // Step 3 内部 sub-tab (① 挑模特 → ② 生上身)
|
||
uploads: [], // Step 1 上传的原图
|
||
triViews: [], // Step 2 三视图版本历史 [{id, dataUrl?, createdAt, label}]
|
||
triCurrent: null, // 当前选中的三视图 id (= 加入商品的版本)
|
||
// Step 3
|
||
ownModels: [], // 商家上传的模特
|
||
selectedModels: new Set(), // 已选模特 id (用于触发生成)
|
||
outfits: {}, // { modelId: [{id, label}] } · 资产库
|
||
outfitRegenCount: {},
|
||
pickedOutfits: new Set(), // 已加入商品的 outfit id
|
||
// Step 4
|
||
platforms: {}, // 各平台头图 { platKey: { cards: [{id, label}], regenCount } } · 资产库
|
||
pickedPlatformCards: new Set(), // 已加入商品的 平台头图 card id
|
||
// 平台 checkbox (Step4 还未触发生成时的勾选)
|
||
selectedPlatforms: new Set(),
|
||
rightCollapsed: false,
|
||
};
|
||
|
||
const PLATFORM_META = {
|
||
amazon: { name: '亚马逊', spec: '1:1 白底', ratio: '1/1' },
|
||
xhs: { name: '小红书', spec: '3:4 生活感', ratio: '3/4' },
|
||
dy: { name: '抖音', spec: '9:16 视觉冲击', ratio: '9/16' },
|
||
tb: { name: '淘宝', spec: '4:5 营销', ratio: '4/5' },
|
||
jd: { name: '京东', spec: '1:1 商务', ratio: '1/1' },
|
||
pdd: { name: '拼多多', spec: '1:1 促销', ratio: '1/1' },
|
||
sph: { name: '视频号', spec: '9:16 引流', ratio: '9/16' },
|
||
ks: { name: '快手', spec: '9:16 老铁', ratio: '9/16' },
|
||
};
|
||
|
||
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
||
const esc = (s) => (s || '').replace(/[<>&"]/g, c => ({ '<':'<','>':'>','&':'&','"':'"' })[c]);
|
||
|
||
// ============================================================
|
||
// STEP NAVIGATION (5-step)
|
||
// ============================================================
|
||
function showStep(n) {
|
||
state.step = n;
|
||
document.querySelectorAll('.step-pane').forEach(p => p.classList.remove('active'));
|
||
document.querySelector(`[data-pane="${n}"]`)?.classList.add('active');
|
||
|
||
refreshSideSteps();
|
||
|
||
const strip = document.getElementById('subtab-strip');
|
||
if (strip) strip.style.display = n === 3 ? 'flex' : 'none';
|
||
|
||
updateBottom();
|
||
const canvas = document.querySelector('.wb-canvas');
|
||
if (canvas) canvas.scrollTop = 0;
|
||
|
||
// Step 4 进入时如果已勾选平台,渲染结果区
|
||
if (n === 4) renderPlatformResults();
|
||
}
|
||
|
||
// 已完成的 step 可点击回退
|
||
function canEnterStep(n) {
|
||
if (n === 1) return true;
|
||
if (n === 2) return state.uploads.length >= 1 && getName();
|
||
if (n === 3) return state.triViews.length >= 1; // 附加:三视图后可入
|
||
if (n === 4) return state.triViews.length >= 1; // 附加:三视图后可入
|
||
return false;
|
||
}
|
||
function getName() { return (document.getElementById('ai-name')?.value || '').trim(); }
|
||
|
||
// 真实"完成"状态(用于 sidebar done 视觉)· 附加 step 仅在用户实际生成了内容时才标 done
|
||
function isStepDone(sn) {
|
||
if (sn === 1) return state.uploads.length >= 1 && !!getName();
|
||
if (sn === 2) return state.triViews.length >= 1;
|
||
if (sn === 3) return countOutfitCards() > 0 || state.ownModels.length > 0;
|
||
if (sn === 4) return countPlatformCards() > 0;
|
||
return false;
|
||
}
|
||
function refreshSideSteps() {
|
||
document.querySelectorAll('#ai-steps .step-item').forEach(s => {
|
||
s.classList.remove('active', 'done', 'locked');
|
||
const sn = +s.dataset.step;
|
||
if (sn === state.step) {
|
||
s.classList.add('active');
|
||
} else if (isStepDone(sn)) {
|
||
s.classList.add('done');
|
||
} else if (!canEnterStep(sn)) {
|
||
s.classList.add('locked');
|
||
}
|
||
});
|
||
}
|
||
|
||
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');
|
||
});
|
||
|
||
const subtabMeta = document.getElementById('subtab-meta');
|
||
if (subtabMeta) {
|
||
subtabMeta.innerHTML = n === 1
|
||
? `// SUB-STEP 1 / 2 · 已选模特 <span class="accent">${selectedCount()}</span>`
|
||
: `// SUB-STEP 2 / 2 · ${state.selectedModels.size} 个模特 × 4 张`;
|
||
}
|
||
|
||
// 进入 SUB 2 时仅渲染(已生成的 outfits)· 不再自动触发生成
|
||
if (n === 2) renderOutfits();
|
||
|
||
updateBottom();
|
||
document.querySelector('.wb-canvas').scrollTop = 0;
|
||
}
|
||
window.showSub = showSub;
|
||
|
||
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>';
|
||
|
||
function updateBottom() {
|
||
// 更新各 step pane 底部成本提示 + topbar finish 状态
|
||
refreshPaneCosts();
|
||
refreshFinishCta();
|
||
}
|
||
|
||
function refreshPaneCosts() {
|
||
// Step 2 已生成: 切换文案为 "重新生成"
|
||
const p2lbl = document.getElementById('pane2-gen-label');
|
||
const p2cost = document.getElementById('pane2-cost');
|
||
if (p2lbl && p2cost) {
|
||
if (state.triViews.length > 0) {
|
||
p2lbl.textContent = '重新生成 (新增版本)';
|
||
p2cost.innerHTML = `// 已有 ${state.triViews.length} 个版本 · 重新生成 ~<span class="price">¥0.30</span>`;
|
||
} else {
|
||
p2lbl.textContent = '生成商品三视图';
|
||
p2cost.innerHTML = '// 生成三视图 · ~<span class="price">¥0.30</span> / 次';
|
||
}
|
||
}
|
||
// Step 3 SUB1 成本提示
|
||
const p3cost = document.getElementById('pane3-cost');
|
||
if (p3cost) {
|
||
const n = state.selectedModels.size;
|
||
p3cost.innerHTML = n > 0
|
||
? `// 已选 ${n} 个模特 · 预计 ${n * 4} 张 · ~<span class="price">¥${(n * 0.4).toFixed(2)}</span>`
|
||
: '// 未选模特 · 先勾选 1+ 个模特';
|
||
}
|
||
// Step 4 成本提示
|
||
const p4cost = document.getElementById('pane4-cost');
|
||
if (p4cost) {
|
||
const n = state.selectedPlatforms.size;
|
||
p4cost.innerHTML = n > 0
|
||
? `// 已选 ${n} 个平台 · 预计 ${n * 4} 张 · ~<span class="price">¥${(n * 0.5).toFixed(2)}</span>`
|
||
: '// 未选平台 · 先勾选 1+ 个平台';
|
||
}
|
||
}
|
||
|
||
function refreshFinishCta() {
|
||
// topbar 完成创建按钮
|
||
const tb = document.getElementById('topbar-finish');
|
||
if (!tb) return;
|
||
const ready = state.triViews.length >= 1;
|
||
tb.disabled = !ready;
|
||
tb.title = ready ? `加入商品库 (${countPickedAssets()} 张已选资产)` : '完成 Step 2 三视图后可创建';
|
||
}
|
||
|
||
function bindStepButtons() {
|
||
// Step 1 底部 · 下一步
|
||
document.getElementById('pane1-next')?.addEventListener('click', () => {
|
||
if (!getName()) { Shell.toast('请填写商品名称'); return; }
|
||
if (state.uploads.length < 1) { Shell.toast('请至少上传 1 张商品图'); return; }
|
||
showStep(2);
|
||
});
|
||
|
||
// Step 2 底部 · 生成 / 重新生成三视图
|
||
document.getElementById('pane2-gen')?.addEventListener('click', () => {
|
||
if (state.triViews.length === 0) autoGenTriView();
|
||
else triViewRegenerate();
|
||
});
|
||
|
||
// Step 3 SUB1 底部 · 生成上身图
|
||
document.getElementById('pane3-gen')?.addEventListener('click', () => {
|
||
const n = state.selectedModels.size;
|
||
if (n < 1) { Shell.toast('请先勾选至少 1 个模特'); return; }
|
||
generateOutfitsForSelected();
|
||
});
|
||
|
||
// Step 3 SUB2 底部 · 返回挑选
|
||
document.getElementById('pane3sub2-back')?.addEventListener('click', () => showSub(1));
|
||
|
||
// Step 4 底部 · 生成平台头图
|
||
document.getElementById('pane4-gen')?.addEventListener('click', () => {
|
||
const n = state.selectedPlatforms.size;
|
||
if (n < 1) { Shell.toast('请先勾选至少 1 个平台'); return; }
|
||
generatePlatformsForSelected();
|
||
});
|
||
|
||
// Topbar 完成创建
|
||
document.getElementById('topbar-finish')?.addEventListener('click', () => {
|
||
if (state.triViews.length < 1) { Shell.toast('需先完成商品三视图'); return; }
|
||
Shell.toast('商品已加入商品库', getName());
|
||
setTimeout(() => location.href = 'products.html', 800);
|
||
});
|
||
}
|
||
|
||
// 折叠右栏
|
||
window.toggleRightPanel = function() {
|
||
state.rightCollapsed = !state.rightCollapsed;
|
||
document.querySelector('.wb-controls')?.classList.toggle('collapsed', state.rightCollapsed);
|
||
};
|
||
|
||
// 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);
|
||
});
|
||
|
||
// ============================================================
|
||
// STEP 3 · 模特卡片 · 勾选 + 点卡查看详情弹窗
|
||
// ============================================================
|
||
// 初始化:把 HTML 中预选的卡片同步到 state.selectedModels
|
||
document.querySelectorAll('#model-grid .model-card.selected').forEach(c => {
|
||
state.selectedModels.add(c.dataset.id);
|
||
});
|
||
|
||
function selectedCount() { return state.selectedModels.size; }
|
||
|
||
function refreshCounts() {
|
||
const n = selectedCount();
|
||
const setText = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||
setText('sel-count', n);
|
||
setText('meta-count', n);
|
||
setText('sel-count-x4', n * 4);
|
||
refreshKindNums();
|
||
if (state.step === 3) updateBottom();
|
||
}
|
||
|
||
function refreshKindNums() {
|
||
const all = document.querySelectorAll('#model-grid .model-card').length;
|
||
const lib = document.querySelectorAll('#model-grid .model-card[data-kind="lib"]').length;
|
||
const mine = document.querySelectorAll('#model-grid .model-card[data-kind="mine"]').length;
|
||
const setText = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||
setText('kt-num-all', all);
|
||
setText('kt-num-lib', lib);
|
||
setText('kt-num-mine', mine);
|
||
}
|
||
|
||
const CHECK_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-6"/></svg>';
|
||
|
||
function toggleModelSelection(card) {
|
||
const id = card.dataset.id;
|
||
const wasSelected = card.classList.contains('selected');
|
||
card.classList.toggle('selected');
|
||
if (wasSelected) {
|
||
state.selectedModels.delete(id);
|
||
// 已生成的 outfits 保留在资产库 · 仅取消勾选标记
|
||
(state.outfits[id] || []).forEach(c => state.pickedOutfits.delete(c.id));
|
||
} else {
|
||
state.selectedModels.add(id);
|
||
}
|
||
refreshCounts();
|
||
refreshSideAssets();
|
||
}
|
||
|
||
function bindModelCard(card) {
|
||
// 注入勾选 checkbox 图标(若未注入)
|
||
if (!card.querySelector('.mc-check')) {
|
||
const chk = document.createElement('span');
|
||
chk.className = 'mc-check';
|
||
chk.title = '点击勾选/取消';
|
||
chk.innerHTML = CHECK_SVG;
|
||
chk.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
toggleModelSelection(card);
|
||
});
|
||
card.appendChild(chk);
|
||
}
|
||
// 整卡点击 → 打开详情弹窗
|
||
card.onclick = () => openModelDetail(card);
|
||
}
|
||
document.querySelectorAll('#model-grid .model-card').forEach(bindModelCard);
|
||
|
||
// ============================================================
|
||
// 模特详情弹窗 · 大图 + 标签 + 选中按钮
|
||
// ============================================================
|
||
let mdCurrentCard = null;
|
||
const mdModal = document.getElementById('md-modal');
|
||
|
||
window.openModelDetail = function(card) {
|
||
if (!card) return;
|
||
mdCurrentCard = card;
|
||
const id = card.dataset.id;
|
||
const kind = card.dataset.kind || 'lib';
|
||
const name = card.dataset.name;
|
||
const base = card.dataset.base || '';
|
||
const role = card.dataset.role || '';
|
||
const tags = (card.dataset.tags || '').split(',').filter(Boolean);
|
||
|
||
document.getElementById('md-name').textContent = name;
|
||
const kindEl = document.getElementById('md-kind');
|
||
kindEl.textContent = kind === 'mine' ? '我的 · 入资产库' : '平台模特库';
|
||
kindEl.classList.toggle('mine', kind === 'mine');
|
||
|
||
// 三视图(我的模特: 使用上传图;库模特:用 placeholder)
|
||
const views = document.getElementById('md-views');
|
||
if (kind === 'mine') {
|
||
const own = state.ownModels.find(m => m.id === id);
|
||
if (own?.sourceImg) {
|
||
views.innerHTML = `
|
||
<div><img src="${own.sourceImg}" alt="正面"></div>
|
||
<div class="placeholder"><span class="ph-frame">侧面</span></div>
|
||
<div class="placeholder"><span class="ph-frame">背面</span></div>
|
||
`;
|
||
} else {
|
||
views.innerHTML = `<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>`;
|
||
}
|
||
} else {
|
||
views.innerHTML = `
|
||
<div class="placeholder"><span class="ph-frame">${esc(name)}<br>正面</span></div>
|
||
<div class="placeholder"><span class="ph-frame">${esc(name)}<br>侧面</span></div>
|
||
<div class="placeholder"><span class="ph-frame">${esc(name)}<br>背面</span></div>
|
||
`;
|
||
}
|
||
|
||
// tags
|
||
const baseTags = base ? base.split(' · ').filter(Boolean) : [];
|
||
const allTags = baseTags.concat([role]).concat(tags).filter(Boolean);
|
||
document.getElementById('md-tags').innerHTML = allTags.map(t => `<span class="tag">${esc(t)}</span>`).join('');
|
||
|
||
// meta rows
|
||
const ageStr = baseTags.length > 1 ? baseTags[1] : '';
|
||
document.getElementById('md-age').textContent = ageStr ? `${ageStr} 岁 · 可微调 ±3` : '—';
|
||
document.getElementById('md-outfit').textContent = kind === 'mine' ? '商家上传立绘(原图)' : '白 T + 白短裤(默认立绘)';
|
||
document.getElementById('md-body-info').textContent = base.includes('双人') ? '双人组合' : (base.startsWith('男') ? '普通男 · 175cm / 65kg' : '普通女 · 165cm / 50kg');
|
||
document.getElementById('md-used').textContent = kind === 'mine' ? '商家上传 · 仅本商品' : '4 个项目 · 平均评分 4.5';
|
||
|
||
updateMdSelectButton(card.classList.contains('selected'));
|
||
document.getElementById('md-select-btn').onclick = () => {
|
||
toggleModelSelection(card);
|
||
updateMdSelectButton(card.classList.contains('selected'));
|
||
};
|
||
|
||
mdModal.classList.add('open');
|
||
};
|
||
|
||
window.closeModelDetail = function() {
|
||
mdModal.classList.remove('open');
|
||
mdCurrentCard = null;
|
||
};
|
||
|
||
mdModal?.addEventListener('click', e => { if (e.target === mdModal) closeModelDetail(); });
|
||
|
||
function updateMdSelectButton(isSelected) {
|
||
const lbl = document.getElementById('md-select-label');
|
||
const info = document.getElementById('md-foot-info');
|
||
const btn = document.getElementById('md-select-btn');
|
||
if (isSelected) {
|
||
lbl.textContent = '已选中 · 点击取消';
|
||
info.textContent = '// 已选中,下一步将生成 4 张上身图';
|
||
info.classList.add('is-selected');
|
||
btn.classList.remove('btn-primary');
|
||
} else {
|
||
lbl.textContent = '选中此模特';
|
||
info.textContent = '// 未选中';
|
||
info.classList.remove('is-selected');
|
||
btn.classList.add('btn-primary');
|
||
}
|
||
}
|
||
|
||
window.removeModel = function(id) {
|
||
const card = document.querySelector(`#model-grid .model-card[data-id="${id}"]`);
|
||
if (card) card.classList.remove('selected');
|
||
state.selectedModels.delete(id);
|
||
(state.outfits[id] || []).forEach(c => state.pickedOutfits.delete(c.id));
|
||
refreshCounts();
|
||
refreshSideAssets();
|
||
};
|
||
|
||
// ============================================================
|
||
// 来源切换 · 全部 / 模特库 / 我的
|
||
// ============================================================
|
||
document.querySelectorAll('#kind-toggle .kt-btn').forEach(btn => {
|
||
btn.onclick = () => {
|
||
document.querySelectorAll('#kind-toggle .kt-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
const kind = btn.dataset.kind;
|
||
document.querySelectorAll('#model-grid .model-card').forEach(c => {
|
||
const k = c.dataset.kind;
|
||
c.style.display = (kind === 'all' || kind === k) ? '' : 'none';
|
||
});
|
||
// "+ 上传新模特" 卡片在所有视图下显示
|
||
const addCard = document.getElementById('add-model-card');
|
||
if (addCard) addCard.style.display = '';
|
||
};
|
||
});
|
||
|
||
// ============================================================
|
||
// 核心卖点 · bullet-list 交互
|
||
// ============================================================
|
||
document.querySelectorAll('[data-bl]').forEach(list => {
|
||
const addInput = list.querySelector('.bl-add .bl-input');
|
||
const renumber = () => {
|
||
[...list.querySelectorAll('.bl-item')].forEach((li, i) => {
|
||
li.querySelector('.num').textContent = i + 1;
|
||
});
|
||
};
|
||
const xSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
const addBullet = (text) => {
|
||
const t = (text || '').trim();
|
||
if (!t) return;
|
||
const li = document.createElement('li');
|
||
li.className = 'bl-item';
|
||
li.innerHTML = `<span class="num">0</span><span class="bl-text">${t.replace(/[<>&]/g, c => ({ '<':'<','>':'>','&':'&' })[c])}</span><span class="bl-x" title="删除">${xSvg}</span>`;
|
||
list.querySelector('.bl-add').before(li);
|
||
bindX(li.querySelector('.bl-x'));
|
||
renumber();
|
||
};
|
||
const bindX = (x) => {
|
||
x.addEventListener('click', () => {
|
||
const li = x.closest('li');
|
||
li.style.transition = 'opacity .15s, transform .15s';
|
||
li.style.opacity = 0;
|
||
li.style.transform = 'translateX(-8px)';
|
||
setTimeout(() => { li.remove(); renumber(); }, 150);
|
||
});
|
||
};
|
||
list.querySelectorAll('.bl-x').forEach(bindX);
|
||
addInput?.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
addBullet(addInput.value);
|
||
addInput.value = '';
|
||
}
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// Step 1 · 多图上传 (data URLs · 不入资产库,仅商品详情)
|
||
// ============================================================
|
||
const MAX_UPLOADS = 5;
|
||
const s1File = document.getElementById('s1-file');
|
||
const s1Zone = document.getElementById('s1-zone');
|
||
const s1ZoneText = document.getElementById('s1-zone-text');
|
||
const s1Grid = document.getElementById('s1-grid');
|
||
|
||
function renderS1Grid() {
|
||
if (!s1Grid) return;
|
||
const xSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
s1Grid.innerHTML = state.uploads.map(u => `
|
||
<div class="up-thumb" data-id="${u.id}">
|
||
<img src="${u.dataUrl}" alt="${esc(u.name)}">
|
||
<button class="slot-x" type="button" title="删除">${xSvg}</button>
|
||
</div>
|
||
`).join('');
|
||
s1Grid.querySelectorAll('.up-thumb').forEach(t => {
|
||
const id = t.dataset.id;
|
||
t.querySelector('.slot-x').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const i = state.uploads.findIndex(u => u.id === id);
|
||
if (i >= 0) {
|
||
const rm = state.uploads.splice(i, 1)[0];
|
||
Shell.toast('已删除', rm.name);
|
||
renderS1Grid(); syncS1Zone(); renderAssetPreview(); updateBottom();
|
||
}
|
||
});
|
||
t.addEventListener('click', () => {
|
||
const u = state.uploads.find(x => x.id === id);
|
||
if (u) Shell._openLightbox(u.dataUrl, u.name);
|
||
});
|
||
});
|
||
}
|
||
function syncS1Zone() {
|
||
if (!s1Zone || !s1ZoneText) return;
|
||
const full = state.uploads.length >= MAX_UPLOADS;
|
||
s1Zone.classList.toggle('full', full);
|
||
s1ZoneText.textContent = full ? `已达上限 (${MAX_UPLOADS} / ${MAX_UPLOADS})` : '点击或拖拽上传图片';
|
||
}
|
||
function addS1Files(fileList) {
|
||
const remaining = MAX_UPLOADS - state.uploads.length;
|
||
if (remaining <= 0) { Shell.toast('已达上限', `${MAX_UPLOADS} / ${MAX_UPLOADS}`); return; }
|
||
const incoming = [...fileList].filter(f => f.type.startsWith('image/'));
|
||
const accepted = incoming.slice(0, remaining);
|
||
const overflow = incoming.length - accepted.length;
|
||
let done = 0;
|
||
accepted.forEach(f => {
|
||
const reader = new FileReader();
|
||
reader.onload = e => {
|
||
state.uploads.push({ id: uid(), dataUrl: e.target.result, name: f.name, type: f.type });
|
||
done++;
|
||
if (done === accepted.length) {
|
||
renderS1Grid(); syncS1Zone(); renderAssetPreview(); updateBottom();
|
||
const msg = overflow > 0 ? `+ ${done} 张 · 超出 ${overflow} 张忽略` : `+ ${done} 张 · 共 ${state.uploads.length}`;
|
||
Shell.toast('图片已添加 (商品详情)', msg);
|
||
}
|
||
};
|
||
reader.readAsDataURL(f);
|
||
});
|
||
}
|
||
if (s1File) s1File.addEventListener('change', e => { addS1Files(e.target.files); e.target.value = ''; });
|
||
if (s1Zone) {
|
||
s1Zone.addEventListener('click', () => { if (state.uploads.length < MAX_UPLOADS) s1File.click(); });
|
||
s1Zone.addEventListener('dragover', e => { e.preventDefault(); if (state.uploads.length < MAX_UPLOADS) s1Zone.style.borderColor = 'var(--heat)'; });
|
||
s1Zone.addEventListener('dragleave', () => { s1Zone.style.borderColor = ''; });
|
||
s1Zone.addEventListener('drop', e => { e.preventDefault(); s1Zone.style.borderColor = ''; if (e.dataTransfer?.files?.length) addS1Files(e.dataTransfer.files); });
|
||
}
|
||
|
||
// 商品名 → topbar 同步
|
||
document.getElementById('ai-name')?.addEventListener('input', e => {
|
||
const tb = document.getElementById('topbar-name');
|
||
if (tb) tb.value = e.target.value;
|
||
});
|
||
|
||
// ============================================================
|
||
// 右栏 · 商品预览 (只显示已加入商品的资产)
|
||
// ============================================================
|
||
function countOutfitCards() {
|
||
return Object.values(state.outfits).reduce((s, arr) => s + arr.length, 0);
|
||
}
|
||
function countPlatformCards() {
|
||
return Object.values(state.platforms).reduce((s, p) => s + p.cards.length, 0);
|
||
}
|
||
function countPickedAssets() {
|
||
let n = 0;
|
||
if (state.triCurrent) n++;
|
||
n += state.pickedOutfits.size;
|
||
n += state.pickedPlatformCards.size;
|
||
return n;
|
||
}
|
||
// 兼容旧调用
|
||
function countTotalAssets() { return countPickedAssets(); }
|
||
|
||
function renderAssetPreview() {
|
||
const name = getName() || '未命名商品';
|
||
const cat = document.getElementById('ai-cat')?.value || '';
|
||
const total = countPickedAssets();
|
||
|
||
document.getElementById('rap-name').textContent = name;
|
||
document.getElementById('rap-meta').textContent = `// ${cat || '未填写品类'} · 加入商品 ${total} 张 · 原图 ${state.uploads.length} 张`;
|
||
|
||
// 原图 (始终显示,商品详情数据)
|
||
const gOrig = document.getElementById('rap-grid-orig');
|
||
document.getElementById('rap-c-orig').textContent = state.uploads.length;
|
||
document.querySelector('[data-group="orig"]')?.classList.toggle('empty', state.uploads.length === 0);
|
||
if (state.uploads.length) {
|
||
gOrig.innerHTML = state.uploads.map(u => `<div class="rap-item" data-src="${u.dataUrl}" data-name="${esc(u.name)}"><img src="${u.dataUrl}" alt=""></div>`).join('');
|
||
} else {
|
||
gOrig.innerHTML = '<div class="rap-empty mono">// 还未上传</div>';
|
||
}
|
||
|
||
// 三视图 · 只显示当前加入商品的版本
|
||
const gTri = document.getElementById('rap-grid-tri');
|
||
const curTri = state.triViews.find(v => v.id === state.triCurrent);
|
||
document.getElementById('rap-c-tri').textContent = curTri ? 1 : 0;
|
||
document.querySelector('[data-group="tri"]')?.classList.toggle('empty', !curTri);
|
||
if (curTri) {
|
||
gTri.innerHTML = `<div class="rap-item"><div class="placeholder"><span class="ph-frame">${esc(curTri.label)}</span></div><span class="rap-mtag">${esc(curTri.label)}</span></div>`;
|
||
} else {
|
||
gTri.innerHTML = '<div class="rap-empty mono">// 还未生成三视图</div>';
|
||
}
|
||
|
||
// 上身图 · 只显示已加入商品的
|
||
const gOut = document.getElementById('rap-grid-outfit');
|
||
const pickedOutfitsArr = [];
|
||
Object.entries(state.outfits).forEach(([modelId, cards]) => {
|
||
const m = getModelById(modelId);
|
||
cards.forEach(c => {
|
||
if (state.pickedOutfits.has(c.id)) {
|
||
pickedOutfitsArr.push({ modelName: m?.name || modelId, label: c.label });
|
||
}
|
||
});
|
||
});
|
||
document.getElementById('rap-c-outfit').textContent = pickedOutfitsArr.length;
|
||
document.querySelector('[data-group="outfit"]')?.classList.toggle('empty', pickedOutfitsArr.length === 0);
|
||
if (pickedOutfitsArr.length) {
|
||
gOut.innerHTML = pickedOutfitsArr.map(p => `<div class="rap-item"><div class="placeholder"><span class="ph-frame">${esc(p.modelName)}</span></div><span class="rap-mtag">${esc(p.label)}</span></div>`).join('');
|
||
} else {
|
||
gOut.innerHTML = '<div class="rap-empty mono">// 还未加入上身图</div>';
|
||
}
|
||
|
||
// 我的模卡 · 默认入资产库,但显不显示由用户决定 - 此处用 ownModels 数显示在商品详情下
|
||
const gOwn = document.getElementById('rap-grid-own');
|
||
document.getElementById('rap-c-own').textContent = state.ownModels.length;
|
||
document.querySelector('[data-group="own"]')?.classList.toggle('empty', state.ownModels.length === 0);
|
||
if (state.ownModels.length) {
|
||
gOwn.innerHTML = state.ownModels.map(m => `<div class="rap-item" data-src="${m.sourceImg}" data-name="${esc(m.name)}"><img src="${m.sourceImg}" alt=""><span class="rap-mtag">${esc(m.name)}</span></div>`).join('');
|
||
} else {
|
||
gOwn.innerHTML = '<div class="rap-empty mono">// 未上传我的模特</div>';
|
||
}
|
||
|
||
// 平台头图 · 只显示已加入商品的
|
||
const gPlat = document.getElementById('rap-grid-plat');
|
||
const pickedPlatArr = [];
|
||
Object.entries(state.platforms).forEach(([pk, p]) => {
|
||
p.cards.forEach((c, i) => {
|
||
if (state.pickedPlatformCards.has(c.id)) {
|
||
pickedPlatArr.push({ platName: PLATFORM_META[pk].name, idx: i + 1 });
|
||
}
|
||
});
|
||
});
|
||
document.getElementById('rap-c-plat').textContent = pickedPlatArr.length;
|
||
document.querySelector('[data-group="plat"]')?.classList.toggle('empty', pickedPlatArr.length === 0);
|
||
if (pickedPlatArr.length) {
|
||
gPlat.innerHTML = pickedPlatArr.map(p => `<div class="rap-item"><div class="placeholder"><span class="ph-frame">${esc(p.platName)}<br>${p.idx}</span></div><span class="rap-mtag">${esc(p.platName)}</span></div>`).join('');
|
||
} else {
|
||
gPlat.innerHTML = '<div class="rap-empty mono">// 还未加入平台头图</div>';
|
||
}
|
||
|
||
// 原图 / 我的模卡 点击 lightbox
|
||
document.querySelectorAll('#right-asset-preview .rap-item[data-src]').forEach(it => {
|
||
it.onclick = () => Shell._openLightbox(it.dataset.src, it.dataset.name);
|
||
});
|
||
|
||
refreshFinishCta();
|
||
refreshSideSteps();
|
||
}
|
||
// 兼容旧名
|
||
function refreshSideAssets() { renderAssetPreview(); }
|
||
|
||
// ============================================================
|
||
// Step 2 · 商品三视图 · 按钮触发生成 + 版本历史
|
||
// ============================================================
|
||
function autoGenTriView() {
|
||
if (state.triViews.length > 0) return triViewRegenerate();
|
||
const btn = document.getElementById('pane2-gen');
|
||
if (btn) btn.disabled = true;
|
||
Shell.toast('正在生成三视图', '~12s · ¥0.30');
|
||
setTimeout(() => {
|
||
const v = { id: uid(), label: `v${state.triViews.length + 1}`, createdAt: new Date().toLocaleTimeString().slice(0, 5) };
|
||
state.triViews.push(v);
|
||
state.triCurrent = v.id; // 默认选中最新 = 加入商品
|
||
// 切换显示
|
||
document.getElementById('triview-stage-empty').style.display = 'none';
|
||
document.getElementById('triview-stage').style.display = '';
|
||
document.getElementById('triview-versions').style.display = '';
|
||
if (btn) btn.disabled = false;
|
||
renderTriView();
|
||
refreshSideAssets();
|
||
updateBottom();
|
||
Shell.toast('三视图已生成 · 当前版本已加入商品', v.label);
|
||
}, 1200);
|
||
}
|
||
function triViewRegenerate() {
|
||
const btn = document.getElementById('pane2-gen');
|
||
if (btn) btn.disabled = true;
|
||
Shell.toast('重新生成中', '~¥0.30 · 新增版本');
|
||
setTimeout(() => {
|
||
const v = { id: uid(), label: `v${state.triViews.length + 1}`, createdAt: new Date().toLocaleTimeString().slice(0, 5) };
|
||
state.triViews.push(v);
|
||
state.triCurrent = v.id;
|
||
if (btn) btn.disabled = false;
|
||
renderTriView();
|
||
refreshSideAssets();
|
||
updateBottom();
|
||
Shell.toast('新版本已生成 · 已切换为加入商品的版本', v.label);
|
||
}, 1000);
|
||
}
|
||
function renderTriView() {
|
||
const display = document.getElementById('triview-display');
|
||
const vtag = document.getElementById('triview-vtag');
|
||
const list = document.getElementById('triview-list');
|
||
if (!display || !list) return;
|
||
|
||
const cur = state.triViews.find(v => v.id === state.triCurrent) || state.triViews[state.triViews.length - 1];
|
||
if (cur) {
|
||
const ph = display.querySelector('.ph-frame');
|
||
if (ph) ph.innerHTML = `[ 正面 ] [ 侧面 ] [ 背面 ]<br>三视角合一 · ${cur.label} · ${cur.createdAt}`;
|
||
if (vtag) vtag.textContent = `当前 · ${cur.label} · ${cur.createdAt} (已加入商品)`;
|
||
}
|
||
|
||
if (state.triViews.length === 0) {
|
||
list.innerHTML = '<div class="tv-empty">// 暂无版本</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = state.triViews.map(v => `
|
||
<div class="tv-item ${v.id === state.triCurrent ? 'current' : ''}" data-id="${v.id}" title="${v.id === state.triCurrent ? '当前加入商品的版本' : '点击切换为加入商品的版本'}">
|
||
<div class="placeholder"><span class="ph-frame">${v.label}</span></div>
|
||
<span class="tv-vtag">${v.label} · ${v.createdAt}${v.id === state.triCurrent ? ' · ✓' : ''}</span>
|
||
</div>
|
||
`).join('');
|
||
list.querySelectorAll('.tv-item').forEach(it => {
|
||
it.onclick = () => {
|
||
state.triCurrent = it.dataset.id;
|
||
renderTriView();
|
||
refreshSideAssets();
|
||
Shell.toast('已切换加入商品的三视图版本');
|
||
};
|
||
});
|
||
}
|
||
|
||
// ============================================================
|
||
// Step 3 · 上身图生成 (SUB 2)
|
||
// 模特库模卡: 不入资产 · 上身图入资产
|
||
// 我的模卡: 入资产 · 上身图入资产
|
||
// ============================================================
|
||
const OUTFIT_LABELS = ['持物半身', '使用中', '镜前自拍', '全身展示'];
|
||
|
||
function getModelById(id) {
|
||
// own
|
||
const own = state.ownModels.find(m => m.id === id);
|
||
if (own) return { id: own.id, name: own.name, role: own.role, kind: 'mine' };
|
||
// lib (from DOM)
|
||
const card = document.querySelector(`#model-grid .model-card[data-id="${id}"]`);
|
||
if (card) return { id, name: card.dataset.name, role: card.dataset.role || '', kind: card.dataset.kind || 'lib' };
|
||
return null;
|
||
}
|
||
|
||
// 按钮触发 · 仅为还没生成过 outfits 的已选模特生成
|
||
function generateOutfitsForSelected() {
|
||
const targets = Array.from(state.selectedModels).filter(id => !state.outfits[id]);
|
||
if (targets.length === 0) {
|
||
Shell.toast('已选模特都已生成 · 切换到上身图查看');
|
||
showSub(2);
|
||
return;
|
||
}
|
||
Shell.toast(`正在生成 ${targets.length * 4} 张上身图`, `~¥${(targets.length * 0.4).toFixed(2)} · ${targets.length} 模特`);
|
||
setTimeout(() => {
|
||
targets.forEach(id => {
|
||
state.outfits[id] = OUTFIT_LABELS.map(label => ({ id: uid(), label }));
|
||
});
|
||
showSub(2);
|
||
renderOutfits();
|
||
refreshSideAssets();
|
||
updateBottom();
|
||
Shell.toast('上身图已生成 · 勾选喜欢的加入商品库');
|
||
}, 1500);
|
||
}
|
||
|
||
const PICK_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-6"/></svg>';
|
||
|
||
function renderOutfits() {
|
||
const wrap = document.getElementById('outfits-wrap');
|
||
if (!wrap) return;
|
||
const modelsWithOutfits = Array.from(state.selectedModels).filter(id => state.outfits[id]);
|
||
if (modelsWithOutfits.length === 0) {
|
||
wrap.innerHTML = `
|
||
<div class="outfit-empty-step">
|
||
<div class="ee-t">还未生成上身图</div>
|
||
<div class="ee-d">// 回到 SUB1 勾选模特 · 点底部「生成上身图」按钮</div>
|
||
<div class="ee-btn"><button class="btn" onclick="showSub(1)">← 返回挑选模特</button></div>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
wrap.innerHTML = modelsWithOutfits.map(modelId => {
|
||
const m = getModelById(modelId);
|
||
if (!m) return '';
|
||
const cards = state.outfits[modelId] || [];
|
||
const regenN = state.outfitRegenCount[modelId] || 0;
|
||
const pickedInBlock = cards.filter(c => state.pickedOutfits.has(c.id)).length;
|
||
return `
|
||
<div class="outfit-block ${m.kind === 'mine' ? 'kind-mine' : ''}" data-model="${modelId}">
|
||
<div class="ob-h">
|
||
<span class="ob-mname">
|
||
${esc(m.name)}
|
||
<span class="ob-mkind">${m.kind === 'mine' ? '我的' : '模特库'}</span>
|
||
</span>
|
||
<span class="ob-meta">// 4 张 · 已加入商品 ${pickedInBlock}${regenN > 0 ? ` · 已重生 ${regenN} 轮` : ''}</span>
|
||
<button class="ob-regen-all" data-act="regen-all-outfit" data-model="${modelId}">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 4v4h-4"/></svg>
|
||
整组重生
|
||
</button>
|
||
</div>
|
||
<div class="outfit-grid">
|
||
${cards.map(c => `
|
||
<div class="outfit-card ${state.pickedOutfits.has(c.id) ? 'selected' : ''}" data-cid="${c.id}" data-model="${modelId}">
|
||
<div class="placeholder"><span class="ph-frame">${esc(m.name)}<br>${esc(c.label)}</span></div>
|
||
<span class="oc-tag">${esc(c.label)}</span>
|
||
<div class="oc-actions">
|
||
<button class="oc-btn" data-act="regen-one" data-model="${modelId}" data-cid="${c.id}" title="单张重生">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 4v4h-4"/></svg>
|
||
</button>
|
||
<button class="oc-btn del" data-act="del-outfit" data-model="${modelId}" data-cid="${c.id}" title="删除">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||
</button>
|
||
</div>
|
||
<button class="pick-chip ${state.pickedOutfits.has(c.id) ? 'picked' : ''}" data-act="pick-outfit" data-cid="${c.id}">
|
||
${PICK_SVG}${state.pickedOutfits.has(c.id) ? '已加入商品' : '加入商品'}
|
||
</button>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
wrap.querySelectorAll('[data-act="regen-all-outfit"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); regenAllOutfitsFor(b.dataset.model); };
|
||
});
|
||
wrap.querySelectorAll('[data-act="regen-one"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); regenOneOutfit(b.dataset.model, b.dataset.cid); };
|
||
});
|
||
wrap.querySelectorAll('[data-act="del-outfit"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); delOutfitCard(b.dataset.model, b.dataset.cid); };
|
||
});
|
||
wrap.querySelectorAll('[data-act="pick-outfit"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); togglePickOutfit(b.dataset.cid); };
|
||
});
|
||
}
|
||
|
||
function togglePickOutfit(cardId) {
|
||
if (state.pickedOutfits.has(cardId)) state.pickedOutfits.delete(cardId);
|
||
else state.pickedOutfits.add(cardId);
|
||
renderOutfits();
|
||
refreshSideAssets();
|
||
}
|
||
|
||
function regenAllOutfitsFor(modelId) {
|
||
// 老 cards 的 picked 状态保留(资产库仍存),新 cards 替换中央展示
|
||
state.outfitRegenCount[modelId] = (state.outfitRegenCount[modelId] || 0) + 1;
|
||
const v = state.outfitRegenCount[modelId];
|
||
// 移除旧 picked 标记(因为中央只展示新版本,旧版仅存档)
|
||
(state.outfits[modelId] || []).forEach(c => state.pickedOutfits.delete(c.id));
|
||
state.outfits[modelId] = OUTFIT_LABELS.map(label => ({ id: uid(), label: `${label} v${v + 1}` }));
|
||
renderOutfits(); refreshSideAssets();
|
||
const m = getModelById(modelId);
|
||
Shell.toast(`${m?.name || '模特'} 整组重生`, '~¥0.40 · 4 张');
|
||
}
|
||
function regenOneOutfit(modelId, cardId) {
|
||
const arr = state.outfits[modelId];
|
||
if (!arr) return;
|
||
const i = arr.findIndex(c => c.id === cardId);
|
||
if (i < 0) return;
|
||
state.pickedOutfits.delete(cardId);
|
||
const old = arr[i];
|
||
arr[i] = { id: uid(), label: `${old.label.split(' v')[0]} ★` };
|
||
renderOutfits(); refreshSideAssets();
|
||
Shell.toast('单张已重生', '~¥0.10');
|
||
}
|
||
function delOutfitCard(modelId, cardId) {
|
||
const arr = state.outfits[modelId];
|
||
if (!arr) return;
|
||
state.pickedOutfits.delete(cardId);
|
||
state.outfits[modelId] = arr.filter(c => c.id !== cardId);
|
||
renderOutfits(); refreshSideAssets();
|
||
Shell.toast('已删除上身图');
|
||
}
|
||
|
||
// ============================================================
|
||
// Step 3 · 上传新模特 · 弹窗
|
||
// ============================================================
|
||
let nmSourceImg = null;
|
||
const nmModal = document.getElementById('nm-modal');
|
||
const nmFile = document.getElementById('nm-file');
|
||
const nmZone = document.getElementById('nm-up-zone');
|
||
const nmSubmit = document.getElementById('nm-submit');
|
||
|
||
window.openNewModelModal = function() {
|
||
nmModal.classList.add('open');
|
||
};
|
||
window.closeNewModelModal = function() {
|
||
nmModal.classList.remove('open');
|
||
};
|
||
function resetNewModelForm() {
|
||
nmSourceImg = null;
|
||
document.getElementById('nm-name').value = '';
|
||
document.getElementById('nm-role').value = '';
|
||
document.getElementById('nm-gender').value = '女';
|
||
document.getElementById('nm-age').value = '22-28';
|
||
document.querySelectorAll('#nm-tags .nm-tag-chip').forEach(c => c.classList.remove('active'));
|
||
nmZone.classList.remove('has-img');
|
||
nmZone.innerHTML = `
|
||
<span class="nm-uz-ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg></span>
|
||
<span class="nm-uz-t">点击上传参考图</span>
|
||
<span class="nm-uz-d">建议正面半身照 · JPG / PNG / WEBP</span>
|
||
<button class="nm-uz-replace" type="button">替换</button>
|
||
`;
|
||
}
|
||
|
||
document.getElementById('add-model-card')?.addEventListener('click', () => {
|
||
resetNewModelForm();
|
||
openNewModelModal();
|
||
});
|
||
nmModal?.addEventListener('click', e => { if (e.target === nmModal) closeNewModelModal(); });
|
||
|
||
nmZone?.addEventListener('click', () => nmFile.click());
|
||
nmFile?.addEventListener('change', e => {
|
||
const f = e.target.files?.[0];
|
||
if (!f || !f.type.startsWith('image/')) return;
|
||
const reader = new FileReader();
|
||
reader.onload = ev => {
|
||
nmSourceImg = ev.target.result;
|
||
nmZone.classList.add('has-img');
|
||
nmZone.innerHTML = `<img src="${nmSourceImg}" alt=""><button class="nm-uz-replace" type="button">替换</button>`;
|
||
};
|
||
reader.readAsDataURL(f);
|
||
e.target.value = '';
|
||
});
|
||
|
||
document.querySelectorAll('#nm-tags .nm-tag-chip').forEach(chip => {
|
||
chip.onclick = e => { e.preventDefault(); chip.classList.toggle('active'); };
|
||
});
|
||
|
||
nmSubmit?.addEventListener('click', () => {
|
||
const name = document.getElementById('nm-name').value.trim();
|
||
if (!nmSourceImg) { Shell.toast('请上传参考图'); return; }
|
||
if (!name) { Shell.toast('请填写模特名称'); return; }
|
||
const gender = document.getElementById('nm-gender').value;
|
||
const age = document.getElementById('nm-age').value;
|
||
const role = document.getElementById('nm-role').value.trim() || '我的模特';
|
||
const tags = [...document.querySelectorAll('#nm-tags .nm-tag-chip.active')].map(c => c.dataset.tag);
|
||
|
||
closeNewModelModal();
|
||
Shell.toast('AI 生成模卡中', '~6s · ¥0.30');
|
||
|
||
setTimeout(() => {
|
||
const id = 'mine-' + uid();
|
||
const model = {
|
||
id, kind: 'mine', name, base: `${gender} · ${age}`, role,
|
||
tags: tags.join(','), sourceImg: nmSourceImg,
|
||
createdAt: new Date().toLocaleTimeString().slice(0, 5),
|
||
};
|
||
state.ownModels.push(model);
|
||
// 注入卡片 + 自动选中
|
||
injectOwnModelCard(model);
|
||
state.selectedModels.add(id);
|
||
refreshCounts();
|
||
refreshSideAssets();
|
||
Shell.toast('模卡已生成并入资产库', `${name} · 已自动选中`);
|
||
}, 900);
|
||
});
|
||
|
||
function injectOwnModelCard(m) {
|
||
const grid = document.getElementById('model-grid');
|
||
if (!grid) return;
|
||
const div = document.createElement('div');
|
||
div.className = 'model-card kind-mine selected';
|
||
div.dataset.kind = 'mine';
|
||
div.dataset.id = m.id;
|
||
div.dataset.name = m.name;
|
||
div.dataset.base = m.base;
|
||
div.dataset.role = m.role;
|
||
div.dataset.tags = m.tags;
|
||
div.innerHTML = `
|
||
<span class="source-tag">[ 我的 ]</span>
|
||
<div class="placeholder"><img src="${m.sourceImg}" style="width:100%;height:100%;object-fit:cover;"></div>
|
||
<div class="lbl-bottom"><div class="nm">${esc(m.name)}</div><div class="tags">${esc(m.base)} · ${esc(m.role)}</div></div>
|
||
`;
|
||
// 插入到 "+ 上传新模特" 之后
|
||
const addCard = document.getElementById('add-model-card');
|
||
if (addCard && addCard.nextSibling) grid.insertBefore(div, addCard.nextSibling);
|
||
else grid.appendChild(div);
|
||
bindModelCard(div);
|
||
|
||
// 若当前为"全部"或"我的"视图则显示,否则隐藏
|
||
const activeKind = document.querySelector('#kind-toggle .kt-btn.active')?.dataset.kind || 'all';
|
||
if (activeKind === 'lib') div.style.display = 'none';
|
||
}
|
||
|
||
// ============================================================
|
||
// Step 4 · 平台头图
|
||
// ============================================================
|
||
function renderPlatformResults() {
|
||
const wrap = document.getElementById('platform-results');
|
||
if (!wrap) return;
|
||
const keys = Object.keys(state.platforms);
|
||
if (keys.length === 0) {
|
||
wrap.innerHTML = '<div class="platform-empty mono">// 勾选上方平台,点击下方「生成平台头图」按钮开始</div>';
|
||
return;
|
||
}
|
||
wrap.innerHTML = keys.map(pk => {
|
||
const p = state.platforms[pk];
|
||
const meta = PLATFORM_META[pk];
|
||
return `
|
||
<div class="platform-block" data-plat="${pk}">
|
||
<div class="pb-h">
|
||
<span class="pb-name">${meta.name}</span>
|
||
<span class="pb-spec">${meta.spec}</span>
|
||
<button class="pb-regen" data-act="regen-all" data-plat="${pk}">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 4v4h-4"/></svg>
|
||
整组重生
|
||
</button>
|
||
</div>
|
||
<div class="pb-grid">
|
||
${p.cards.map((c, i) => `
|
||
<div class="pb-card ${state.pickedPlatformCards.has(c.id) ? 'selected' : ''}" data-cid="${c.id}" data-plat="${pk}">
|
||
<div class="placeholder"><span class="ph-frame">${meta.name}<br>候选 ${i+1}</span></div>
|
||
<button class="pb-x" data-act="del" data-plat="${pk}" data-cid="${c.id}">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||
</button>
|
||
<span class="pb-vtag">${c.label || `候选${i+1}`}</span>
|
||
<button class="pick-chip ${state.pickedPlatformCards.has(c.id) ? 'picked' : ''}" data-act="pick-plat" data-cid="${c.id}">
|
||
${PICK_SVG}${state.pickedPlatformCards.has(c.id) ? '已加入' : '加入商品'}
|
||
</button>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
wrap.querySelectorAll('[data-act="regen-all"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); regenPlatform(b.dataset.plat); };
|
||
});
|
||
wrap.querySelectorAll('[data-act="del"]').forEach(b => {
|
||
b.onclick = e => {
|
||
e.stopPropagation();
|
||
const pk = b.dataset.plat, cid = b.dataset.cid;
|
||
state.pickedPlatformCards.delete(cid);
|
||
state.platforms[pk].cards = state.platforms[pk].cards.filter(c => c.id !== cid);
|
||
if (state.platforms[pk].cards.length === 0) delete state.platforms[pk];
|
||
renderPlatformResults(); refreshSideAssets();
|
||
Shell.toast('已删除候选');
|
||
};
|
||
});
|
||
wrap.querySelectorAll('[data-act="pick-plat"]').forEach(b => {
|
||
b.onclick = e => { e.stopPropagation(); togglePickPlatform(b.dataset.cid); };
|
||
});
|
||
}
|
||
|
||
function togglePickPlatform(cardId) {
|
||
if (state.pickedPlatformCards.has(cardId)) state.pickedPlatformCards.delete(cardId);
|
||
else state.pickedPlatformCards.add(cardId);
|
||
renderPlatformResults();
|
||
refreshSideAssets();
|
||
}
|
||
|
||
function genPlatform(pk) {
|
||
if (!state.platforms[pk]) state.platforms[pk] = { cards: [], regenCount: 0 };
|
||
state.platforms[pk].cards = Array.from({ length: 4 }, (_, i) => ({ id: uid(), label: `候选${i+1}` }));
|
||
}
|
||
function regenPlatform(pk) {
|
||
(state.platforms[pk]?.cards || []).forEach(c => state.pickedPlatformCards.delete(c.id));
|
||
state.platforms[pk].cards = Array.from({ length: 4 }, (_, i) => ({ id: uid(), label: `候选${i+1} v${++state.platforms[pk].regenCount + 1}` }));
|
||
renderPlatformResults(); refreshSideAssets();
|
||
Shell.toast(`${PLATFORM_META[pk].name} 整组重生`, '~¥0.50');
|
||
}
|
||
|
||
// 按钮触发 · 仅为还没生成过 platforms 的已勾选平台生成
|
||
function generatePlatformsForSelected() {
|
||
const targets = Array.from(state.selectedPlatforms).filter(pk => !state.platforms[pk]);
|
||
if (targets.length === 0) {
|
||
Shell.toast('已勾选平台都已生成 · 可勾选「加入商品」');
|
||
return;
|
||
}
|
||
Shell.toast(`正在生成 ${targets.length * 4} 张平台头图`, `~¥${(targets.length * 0.5).toFixed(2)} · ${targets.length} 平台`);
|
||
setTimeout(() => {
|
||
targets.forEach(pk => genPlatform(pk));
|
||
renderPlatformResults();
|
||
refreshSideAssets();
|
||
updateBottom();
|
||
Shell.toast('平台头图已生成 · 勾选喜欢的加入商品库');
|
||
}, 1500);
|
||
}
|
||
|
||
// 平台 checkbox 变化 → 仅记录勾选状态,不再自动生成
|
||
document.querySelectorAll('.plat-card input[type="checkbox"]').forEach(cb => {
|
||
cb.onchange = () => {
|
||
const pk = cb.dataset.plat;
|
||
if (cb.checked) {
|
||
state.selectedPlatforms.add(pk);
|
||
} else {
|
||
state.selectedPlatforms.delete(pk);
|
||
// 取消勾选时,如该平台已生成,保留资产但提示
|
||
if (state.platforms[pk]) {
|
||
state.platforms[pk].cards.forEach(c => state.pickedPlatformCards.delete(c.id));
|
||
delete state.platforms[pk];
|
||
renderPlatformResults();
|
||
refreshSideAssets();
|
||
}
|
||
}
|
||
updateBottom();
|
||
};
|
||
});
|
||
|
||
// 商品名 / 品类 输入时同步右栏资产预览
|
||
document.getElementById('ai-name')?.addEventListener('input', renderAssetPreview);
|
||
document.getElementById('ai-cat')?.addEventListener('change', renderAssetPreview);
|
||
|
||
// ============================================================
|
||
// INIT
|
||
// ============================================================
|
||
bindStepButtons();
|
||
refreshKindNums();
|
||
showStep(1);
|
||
refreshSideAssets();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|