AirShelf/v1/mockups/mockup-B.html
iye f420af2069
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
chore: 全量推送 · 累积页面改动 + Next.js 工程骨架 + v1/ 归档 + 文档
页面 (电商AI平台/)
- account / team / settings / index / products / projects: 累积迭代
- restraint.css: 设计 token 补充
- login.html / register.html: 新增登录注册页
- _ARCHIVE.md: 归档说明

Next.js 工程骨架
- app/ + components/: 新一代 SPA 雏形 (page / layout / sidebar / topbar / GridBg / Icon)
- package.json / package-lock.json / next.config.mjs / tsconfig.json / postcss.config.mjs / next-env.d.ts

历史归档 / 文档
- v1/: 原 V1 静态稿镜像 (含 mockup-A/B/C)
- PRD.md / deployment-guide.md / _check.html
- ui参考/ / screenshots/

杂项
- .gitignore 调整
- 删除根 README.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:16:46 +08:00

441 lines
20 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>方案 B · 单页折叠流 · 新建商品(AI 生成)</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../assets/restraint.css">
<style>
body { background: var(--bg); }
.demo-bar { padding: 16px 24px; background: var(--card); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 16px; font-family: 'Inter', sans-serif; }
.demo-bar .ti { font-weight: 700; font-size: 15px; }
.demo-bar .desc { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--ink-3); }
/* Drawer wrapper - simulating right-side drawer */
.drawer-shell {
background: var(--card);
border-left: 1px solid var(--border);
width: 920px; max-width: 100vw;
margin-left: auto;
min-height: calc(100vh - 60px);
display: flex; flex-direction: column;
position: relative;
}
.drawer-shell::before, .drawer-shell::after { content: '+'; position: absolute; color: var(--ink-3); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
.drawer-shell::before { top: -7px; left: -7px; }
.drawer-shell::after { bottom: -7px; right: -7px; }
.dr-head {
padding: 18px 28px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 14px;
}
.dr-head h2 { font-size: 17px; font-weight: 700; }
.dr-head .mode-pill {
font-family: 'JetBrains Mono', monospace; font-size: 10.5px;
color: var(--orange); background: var(--orange-tint); border: 1px solid var(--orange-soft);
padding: 2px 8px; letter-spacing: .04em;
}
.dr-head .x { margin-left: auto; width: 28px; height: 28px; display: grid; place-items: center; cursor: pointer; color: var(--ink-2); }
.dr-head .x:hover { background: var(--bg-soft); color: var(--ink); }
/* Mode toggle at top */
.mode-toggle {
padding: 14px 28px;
border-bottom: 1px solid var(--border);
background: var(--bg-soft);
}
.mode-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.mode-card {
padding: 14px 16px;
background: var(--card);
border: 1px solid var(--border);
cursor: pointer;
display: flex; align-items: center; gap: 12px;
}
.mode-card:hover { background: var(--bg); border-color: var(--ink-3); }
.mode-card.active { border-color: var(--orange); background: var(--orange-tint); }
.mode-card .ic-md { width: 36px; height: 36px; background: var(--card); border: 1px solid var(--border); display: grid; place-items: center; color: var(--orange); flex-shrink: 0; }
.mode-card.active .ic-md { background: var(--orange); color: #FFF; border-color: var(--orange); }
.mode-card .t { font-size: 13px; font-weight: 600; }
.mode-card .d { font-size: 11px; color: var(--ink-3); margin-top: 2px; font-family: 'JetBrains Mono', monospace; letter-spacing: .02em; }
/* Top progress bar */
.progress-strip {
padding: 16px 28px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 14px;
}
.progress-strip .lbl { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: .04em; min-width: 70px; }
.progress-strip .bar { flex: 1; height: 4px; background: var(--bg-soft); }
.progress-strip .bar > span { display: block; height: 100%; background: var(--orange); transition: width .3s; }
.progress-strip .pct { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700; color: var(--orange); }
/* Body - vertical accordion sections */
.dr-body { flex: 1; overflow-y: auto; }
.acc-section {
border-bottom: 1px solid var(--border);
}
.acc-section:last-child { border-bottom: 0; }
.acc-head {
padding: 18px 28px;
display: flex; align-items: center; gap: 14px;
cursor: pointer;
user-select: none;
position: relative;
}
.acc-head:hover { background: var(--bg-soft); }
.acc-section.locked .acc-head { cursor: not-allowed; opacity: .55; }
.acc-section.locked .acc-head:hover { background: transparent; }
.acc-num {
width: 26px; height: 26px;
border: 1px solid var(--border); background: var(--card);
color: var(--ink-3);
font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700;
display: grid; place-items: center;
flex-shrink: 0;
}
.acc-section.done .acc-num { background: var(--ink); color: #FFF; border-color: var(--ink); }
.acc-section.active .acc-num { background: var(--orange); color: #FFF; border-color: var(--orange); }
.acc-meta { flex: 1; }
.acc-meta h3 { font-size: 14px; font-weight: 600; }
.acc-meta .sm { font-size: 11.5px; color: var(--ink-3); margin-top: 3px; font-family: 'JetBrains Mono', monospace; letter-spacing: .02em; }
.acc-section.done .acc-meta .sm { color: var(--ink-2); font-family: 'Inter', sans-serif; letter-spacing: 0; }
.acc-summary {
display: none;
align-items: center; gap: 8px;
padding: 6px 12px;
background: var(--green-bg); color: var(--green); border: 1px solid var(--green-bd);
font-size: 11.5px; font-weight: 600;
}
.acc-section.done .acc-summary { display: inline-flex; }
.acc-section.done .acc-toggle { display: none; }
.acc-toggle {
padding: 4px 10px;
background: var(--bg-soft); border: 1px solid var(--border);
font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-2);
cursor: pointer;
letter-spacing: .04em;
}
.acc-section.active .acc-toggle { display: none; }
.acc-section.locked .acc-toggle { display: none; }
.acc-body {
padding: 0 28px 28px;
display: none;
}
.acc-section.active .acc-body { display: block; animation: fade .2s ease; }
@keyframes fade { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.acc-section.done .acc-body-summary {
display: block;
padding: 0 28px 16px 68px;
}
.acc-section:not(.done) .acc-body-summary { display: none; }
.acc-body-summary { display: none; }
.preview-row { display: flex; gap: 8px; align-items: center; }
.preview-row .placeholder { width: 40px; height: 40px; }
/* Step contents */
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.upload-mini {
aspect-ratio: 4/5;
background: var(--bg-soft);
border: 1.5px dashed var(--border);
display: grid; place-items: center;
text-align: center;
cursor: pointer;
color: var(--ink-2);
}
.upload-mini.has-file { border-style: solid; padding: 0; background: var(--bg-soft); border-color: var(--orange); }
.upload-mini.has-file .placeholder { width: 100%; height: 100%; }
.upload-mini .ic-cam { width: 36px; height: 36px; border: 1px solid currentColor; display: grid; place-items: center; margin: 0 auto 10px; }
.s1-row { display: grid; grid-template-columns: 200px 1fr; gap: 24px; }
.gen-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.gen-card { aspect-ratio: 1; background: var(--card); border: 1px solid var(--border); cursor: pointer; position: relative; overflow: hidden; }
.gen-card:hover { border-color: var(--ink-3); }
.gen-card .placeholder { width: 100%; height: 100%; }
.gen-card.selected { border: 2px solid var(--orange); }
.gen-card.selected::after {
content: '✓'; position: absolute; top: 6px; right: 6px;
width: 22px; height: 22px; background: var(--orange); color: #FFF;
display: grid; place-items: center; font-weight: 700; font-size: 12px;
}
.gen-card .badge {
position: absolute; bottom: 6px; left: 6px;
font-family: 'JetBrains Mono', monospace; font-size: 10px;
background: rgba(255,255,255,.95); color: var(--ink-2);
border: 1px solid var(--border);
padding: 1px 5px; letter-spacing: .04em;
}
.regen-line { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.regen-line .info { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: .04em; }
.model-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin-bottom: 18px; }
.model-card { aspect-ratio: 3/4; background: var(--card); border: 1px solid var(--border); cursor: pointer; position: relative; }
.model-card:hover { border-color: var(--ink-3); }
.model-card.selected { border: 2px solid var(--orange); }
.model-card.selected::after { content: '✓'; position: absolute; top: 5px; right: 5px; width: 18px; height: 18px; background: var(--orange); color: #FFF; display: grid; place-items: center; font-weight: 700; font-size: 10px; }
.model-card .lbl-bottom { position: absolute; bottom: 0; left: 0; right: 0; background: rgba(255,255,255,.95); padding: 3px 6px; font-size: 10px; color: var(--ink-2); font-family: 'JetBrains Mono', monospace; letter-spacing: .02em; text-align: center; }
.model-card .placeholder { width: 100%; height: 100%; }
.sub-h { display: flex; align-items: center; justify-content: space-between; margin: 14px 0 10px; }
.sub-h h4 { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.sub-h .info { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--ink-3); letter-spacing: .04em; }
.bullet-list { list-style: none; padding: 0; }
.bullet-list li { display: flex; gap: 8px; align-items: center; padding: 8px 10px; background: var(--bg-soft); border: 1px solid var(--border); margin-bottom: 6px; font-size: 13px; }
.bullet-list .num { width: 18px; height: 18px; background: var(--card); border: 1px solid var(--border); font-size: 11px; color: var(--ink-2); display: grid; place-items: center; flex-shrink: 0; font-family: 'JetBrains Mono', monospace; }
/* Footer */
.dr-foot {
padding: 14px 28px;
border-top: 1px solid var(--border);
background: var(--bg-soft);
display: flex; align-items: center; justify-content: space-between;
position: sticky; bottom: 0;
}
.dr-foot .info { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: .04em; }
</style>
</head>
<body>
<div class="demo-bar">
<a href="index.html" class="btn btn-ghost btn-sm">← 返回方案对比</a>
<span class="ti">方案 B · 单页折叠流(Accordion Flow)</span>
<span class="desc">// 全流程在一页,选过的折叠摘要,可随时回去改 · 适合反复对比</span>
</div>
<div class="drawer-shell">
<header class="dr-head">
<h2>新建商品</h2>
<span class="mode-pill">[ AI 生成模式 ]</span>
<div class="x" onclick="alert('演示原型')"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
</header>
<!-- Mode select at top -->
<div class="mode-toggle">
<div class="mode-grid">
<div class="mode-card">
<div class="ic-md"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg></div>
<div><div class="t">我有商品图</div><div class="d">// upload-only · 直接上传图片</div></div>
</div>
<div class="mode-card active">
<div class="ic-md"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M9.663 17h4.673M12 3v1M3.34 7l.7.7M20.66 7l-.7.7M2 12h1M21 12h1M12 19a7 7 0 100-14 7 7 0 000 14z"/></svg></div>
<div><div class="t">AI 帮我生成图</div><div class="d">// 上传 1 张原图,AI 出 4 选 1 + 模特上身</div></div>
</div>
</div>
</div>
<div class="progress-strip">
<span class="lbl">// PROGRESS</span>
<div class="bar"><span style="width:50%" id="bar"></span></div>
<span class="pct" id="pct">2 / 4</span>
</div>
<div class="dr-body">
<!-- ============= 1 · 商品信息 (DONE - collapsed) ============= -->
<section class="acc-section done" data-step="1">
<div class="acc-head">
<div class="acc-num"></div>
<div class="acc-meta">
<h3>商品信息 + 上传原图</h3>
<div class="sm">透真玻尿酸补水面膜 · 美妆个护 · ¥39.9 · 已上传 1 张原图</div>
</div>
<span class="acc-summary">✓ 已完成</span>
<button class="acc-toggle">编辑</button>
</div>
<div class="acc-body-summary">
<div class="preview-row">
<div class="placeholder"><span class="ph-frame">原图</span></div>
<div style="font-size:12px; color:var(--ink-3); font-family:'JetBrains Mono',monospace;">// 已记录 · 点击右侧"编辑"修改</div>
</div>
</div>
</section>
<!-- ============= 2 · 生成头图 (ACTIVE) ============= -->
<section class="acc-section active" data-step="2">
<div class="acc-head">
<div class="acc-num">2</div>
<div class="acc-meta">
<h3>生成头图 · 选 1 张</h3>
<div class="sm">// AI 已生成 4 张候选 · 点击你最满意的那张</div>
</div>
</div>
<div class="acc-body">
<div class="gen-grid" id="head-grid">
<div class="gen-card selected" data-id="h1"><div class="placeholder"><span class="ph-frame">候选 A · 白底简约</span></div><span class="badge">A</span></div>
<div class="gen-card" data-id="h2"><div class="placeholder"><span class="ph-frame">候选 B · 木纹背景</span></div><span class="badge">B</span></div>
<div class="gen-card" data-id="h3"><div class="placeholder"><span class="ph-frame">候选 C · 浅米石材</span></div><span class="badge">C</span></div>
<div class="gen-card" data-id="h4"><div class="placeholder"><span class="ph-frame">候选 D · 暖光氛围</span></div><span class="badge">D</span></div>
</div>
<div class="regen-line">
<span class="info">// ~¥0.20 / 4 张 · 不满意不扣费</span>
<div class="hstack">
<button class="btn btn-sm" onclick="regen('head')">↻ 全部重新生成</button>
<button class="btn btn-primary btn-sm" onclick="confirmStep(2)">确认这张 →</button>
</div>
</div>
</div>
</section>
<!-- ============= 3 · 模特上身 (LOCKED until step 2 done) ============= -->
<section class="acc-section locked" data-step="3">
<div class="acc-head">
<div class="acc-num">3</div>
<div class="acc-meta">
<h3>选模特 · 生成上身图</h3>
<div class="sm">// 完成上一步后开启</div>
</div>
</div>
<div class="acc-body">
<div class="sub-h">
<h4>① 选一个 AI 模特</h4>
<span class="info">// 12 个模板 · 1 选</span>
</div>
<div class="model-row">
<div class="model-card selected"><div class="placeholder"><span class="ph-frame">林夕 · 25</span></div><div class="lbl-bottom">林夕</div></div>
<div class="model-card"><div class="placeholder"><span class="ph-frame">阿楠</span></div><div class="lbl-bottom">阿楠</div></div>
<div class="model-card"><div class="placeholder"><span class="ph-frame">小七</span></div><div class="lbl-bottom">小七</div></div>
<div class="model-card"><div class="placeholder"><span class="ph-frame">王姐</span></div><div class="lbl-bottom">王姐</div></div>
<div class="model-card"><div class="placeholder"><span class="ph-frame">阿杰</span></div><div class="lbl-bottom">阿杰</div></div>
<div class="model-card"><div class="placeholder"><span class="ph-frame">+ 更多</span></div><div class="lbl-bottom">浏览全部</div></div>
</div>
<div class="sub-h">
<h4>② 选你要的上身图(可多选)</h4>
<span class="info">// 已选 2 / 4</span>
</div>
<div class="gen-grid" id="wear-grid">
<div class="gen-card selected" data-id="w1"><div class="placeholder"><span class="ph-frame">上身 A · 持物半身</span></div><span class="badge">A</span></div>
<div class="gen-card" data-id="w2"><div class="placeholder"><span class="ph-frame">上身 B · 敷面膜中</span></div><span class="badge">B</span></div>
<div class="gen-card selected" data-id="w3"><div class="placeholder"><span class="ph-frame">上身 C · 镜前自拍</span></div><span class="badge">C</span></div>
<div class="gen-card" data-id="w4"><div class="placeholder"><span class="ph-frame">上身 D · 床边特写</span></div><span class="badge">D</span></div>
</div>
<div class="regen-line">
<span class="info">// ~¥0.40 / 4 张</span>
<div class="hstack">
<button class="btn btn-sm" onclick="regen('wear')">↻ 重新生成上身图</button>
<button class="btn btn-primary btn-sm" onclick="confirmStep(3)">下一步 →</button>
</div>
</div>
</div>
</section>
<!-- ============= 4 · 完成 ============= -->
<section class="acc-section locked" data-step="4">
<div class="acc-head">
<div class="acc-num">4</div>
<div class="acc-meta">
<h3>预览 · 创建</h3>
<div class="sm">// 完成上一步后开启</div>
</div>
</div>
<div class="acc-body">
<p style="font-size:13px; color:var(--ink-2); margin-bottom:14px;">检查信息无误后,点击下方「创建商品」。</p>
<div class="preview-row" style="gap:12px;">
<div class="placeholder" style="width:120px; height:150px;"><span class="ph-frame">头图 A</span></div>
<div class="placeholder" style="width:120px; height:150px;"><span class="ph-frame">上身 A</span></div>
<div class="placeholder" style="width:120px; height:150px;"><span class="ph-frame">上身 C</span></div>
</div>
<button class="btn btn-primary btn-lg" style="margin-top:18px; width:100%;" onclick="alert('✓ 商品已创建!')">✓ 创建商品</button>
</div>
</section>
</div>
<footer class="dr-foot">
<span class="info" id="foot">// STEP 2 / 4 · 选头图</span>
<div class="hstack">
<button class="btn">取消</button>
</div>
</footer>
</div>
<script>
const TOTAL = 4;
let activeStep = 2;
function refreshUI() {
const sections = document.querySelectorAll('.acc-section');
sections.forEach((s, i) => {
const n = i + 1;
s.classList.remove('active', 'locked');
if (n < activeStep) s.classList.add('done');
else s.classList.remove('done');
if (n === activeStep) s.classList.add('active');
else if (n > activeStep) s.classList.add('locked');
});
const pct = Math.round(((activeStep - 1) / TOTAL) * 100);
document.getElementById('bar').style.width = pct + '%';
document.getElementById('pct').textContent = `${activeStep - 1} / ${TOTAL}`;
document.getElementById('foot').textContent = `// STEP ${activeStep} / ${TOTAL}`;
}
function confirmStep(n) {
activeStep = n + 1;
refreshUI();
const next = document.querySelector(`[data-step="${activeStep}"]`);
if (next) next.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// Click "编辑" on done sections
document.querySelectorAll('.acc-section.done .acc-toggle').forEach(b => {
b.onclick = (e) => {
e.stopPropagation();
const sec = b.closest('.acc-section');
const n = +sec.dataset.step;
activeStep = n;
refreshUI();
sec.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
});
// Click on done section header to expand
document.querySelectorAll('.acc-section.done .acc-head').forEach(h => {
h.onclick = () => {
const sec = h.closest('.acc-section');
const n = +sec.dataset.step;
activeStep = n;
refreshUI();
};
});
// single select for head
document.querySelectorAll('#head-grid .gen-card').forEach(c => {
c.onclick = () => {
document.querySelectorAll('#head-grid .gen-card').forEach(x => x.classList.remove('selected'));
c.classList.add('selected');
};
});
// single select for model
document.querySelectorAll('.model-card').forEach(c => {
c.onclick = () => {
document.querySelectorAll('.model-card').forEach(x => x.classList.remove('selected'));
c.classList.add('selected');
};
});
// multi select for wear
document.querySelectorAll('#wear-grid .gen-card').forEach(c => {
c.onclick = () => c.classList.toggle('selected');
});
function regen(t) {
const el = document.querySelector(t === 'head' ? '#head-grid' : '#wear-grid');
el.style.opacity = .35;
setTimeout(() => { el.style.opacity = 1; }, 700);
}
refreshUI();
</script>
</body>
</html>