AirShelf/v2/product-studio.html
UI 设计 e293aa43be
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
feat(v2): 添加 V2.1 设计稿目录 · 团队/设置页 · pipeline 多项 mock 优化
2026-05-21 16:18:28 +08:00

1083 lines
44 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>商品工作台 · 流·Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/restraint.css">
<style>
/* ─── Onboarding tip(首次进入) ─── */
.onboard-tip {
background: var(--heat-8);
border: 1px solid var(--heat-40);
border-radius: var(--r-md);
padding: 16px 20px;
margin-bottom: 24px;
display: flex; align-items: center; gap: 14px;
position: relative;
animation: slideIn .4s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.onboard-tip .ic {
width: 36px; height: 36px;
background: var(--heat); color: #fff;
border-radius: var(--r-md);
display: grid; place-items: center; flex-shrink: 0;
}
.onboard-tip .ic svg { width: 18px; height: 18px; }
.onboard-tip .body { flex: 1; }
.onboard-tip .t { font-size: 14px; font-weight: 600; color: var(--accent-black); }
.onboard-tip .d { font-size: 12.5px; color: var(--black-alpha-72); margin-top: 4px; line-height: 1.55; }
.onboard-tip .d strong { color: var(--heat); font-weight: 600; }
.onboard-tip .acts { display: flex; gap: 8px; align-items: center; }
.onboard-tip .dismiss {
background: transparent; border: 0;
color: var(--black-alpha-56);
font-size: 12.5px; cursor: pointer;
padding: 6px 12px;
border-radius: var(--r-md);
transition: background var(--t-base), color var(--t-base);
}
.onboard-tip .dismiss:hover { background: var(--black-alpha-4); color: var(--accent-black); }
/* ─── 商品概览卡(信息 + 原图 mini-grid 合并) ─── */
.overview-card {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 22px 24px;
margin-bottom: 28px;
display: grid;
grid-template-columns: 1fr 320px;
gap: 28px;
align-items: start;
}
.ov-info-head {
display: flex; align-items: flex-start; gap: 14px;
margin-bottom: 12px;
}
.ov-info-head .ic {
width: 36px; height: 36px;
background: var(--heat-12); color: var(--heat);
border-radius: var(--r-md);
display: grid; place-items: center;
flex-shrink: 0;
}
.ov-info-head .ic svg { width: 17px; height: 17px; }
.ov-info-head .name { font-size: 16px; font-weight: 600; color: var(--accent-black); line-height: 1.3; }
.ov-info-head .meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .04em; margin-top: 4px; }
.ov-info-head .edit {
margin-left: auto;
display: inline-flex; align-items: center; gap: 6px;
height: 30px; padding: 0 12px;
background: transparent; border: 1px solid var(--border-faint); border-radius: var(--r-md);
color: var(--black-alpha-56); font-size: 12.5px; cursor: pointer;
transition: all var(--t-base);
}
.ov-info-head .edit:hover { color: var(--heat); border-color: var(--heat-40); background: var(--heat-8); }
.ov-info-head .edit svg { width: 12px; height: 12px; }
.ov-tags { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.ov-tags .t {
font-size: 11.5px; padding: 3px 10px;
background: var(--background-lighter);
color: var(--black-alpha-56);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
}
.ov-sell {
margin-top: 14px; padding-top: 14px;
border-top: 1px dashed var(--border-faint);
font-size: 12.5px; color: var(--black-alpha-72);
line-height: 1.65;
}
.ov-sell .lbl {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .04em;
margin-right: 6px;
}
/* 右侧:原图 mini-grid */
.ov-photos {
border-left: 1px dashed var(--border-faint);
padding-left: 28px;
}
.ov-photos-h {
display: flex; align-items: baseline; justify-content: space-between;
margin-bottom: 10px;
}
.ov-photos-h .t {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .04em;
}
.ov-photos-h .n { color: var(--heat); font-weight: 600; }
.ov-photos-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
.ov-photo {
aspect-ratio: 1;
border-radius: var(--r-sm);
border: 1px solid var(--border-faint);
background: var(--background-lighter);
background-size: cover; background-position: center;
position: relative;
cursor: pointer;
transition: all var(--t-base);
}
.ov-photo:hover { border-color: var(--heat-40); transform: scale(1.04); z-index: 1; }
.ov-photo .pmain {
position: absolute; top: 3px; left: 3px;
font-family: var(--font-mono); font-size: 8.5px; font-weight: 600;
padding: 1px 4px; background: var(--heat); color: #fff;
border-radius: 3px; letter-spacing: .04em;
}
.ov-photo.add {
border-style: dashed; cursor: pointer;
display: grid; place-items: center;
color: var(--black-alpha-32);
transition: all var(--t-base);
}
.ov-photo.add:hover { border-color: var(--heat); color: var(--heat); background: var(--heat-8); }
.ov-photo.add svg { width: 14px; height: 14px; }
/* ─── 响应式 ─── */
@media (max-width: 1100px) {
.overview-card { grid-template-columns: 1fr; }
.ov-photos { border-left: 0; padding-left: 0; padding-top: 18px; border-top: 1px dashed var(--border-faint); }
}
/* ─── 区块标题 ─── */
.section-title {
display: flex; align-items: baseline; gap: 12px;
margin-bottom: 16px;
}
.section-title h2 {
font-size: 16px; font-weight: 600; color: var(--accent-black);
letter-spacing: -0.01em;
}
.section-title .sub {
font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48);
letter-spacing: .04em;
}
/* ─── AI 工具箱(横排 4 列) ─── */
.toolbox {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 36px;
}
.tool-card {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 18px;
display: flex;
flex-direction: column;
gap: 12px;
cursor: pointer;
transition: all var(--t-base);
position: relative;
min-height: 132px;
}
.tool-card:hover { border-color: var(--heat-40); background: var(--heat-4); transform: translateY(-1px); box-shadow: 0 4px 16px rgba(0,0,0,.04); }
.tool-card.featured { border-color: var(--heat-40); }
.tool-card.featured::after {
content: '推荐 ★';
position: absolute;
top: 14px; right: 14px;
font-family: var(--font-mono); font-size: 10px; font-weight: 600;
color: #fff; background: var(--heat);
padding: 3px 8px; border-radius: var(--r-sm);
letter-spacing: .04em;
}
.tool-card .ic-box {
width: 40px; height: 40px;
background: var(--heat-12); color: var(--heat);
border-radius: var(--r-md);
display: grid; place-items: center;
flex-shrink: 0;
}
.tool-card .ic-box svg { width: 18px; height: 18px; }
.tool-card .info { flex: 1; }
.tool-card .info .t { font-size: 14px; font-weight: 600; color: var(--accent-black); line-height: 1.3; }
.tool-card .info .d { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-56); margin-top: 6px; letter-spacing: .02em; line-height: 1.45; }
.tool-card .foot {
display: flex; align-items: center; justify-content: space-between;
padding-top: 10px;
border-top: 1px dashed var(--border-faint);
margin-top: auto;
}
.tool-card .foot .cost {
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .02em;
}
.tool-card .foot .cost b { color: var(--heat); font-weight: 600; }
.tool-card .foot .arrow {
width: 24px; height: 24px; border-radius: var(--r-sm);
background: var(--background-lighter); color: var(--black-alpha-56);
display: grid; place-items: center;
transition: all var(--t-fast);
}
.tool-card:hover .foot .arrow { background: var(--heat); color: #fff; transform: translateX(2px); }
.tool-card .foot .arrow svg { width: 11px; height: 11px; }
.tool-card.coming-soon { opacity: .7; cursor: default; }
.tool-card.coming-soon:hover { border-color: var(--border-faint); background: var(--surface); transform: none; box-shadow: none; }
.tool-card.coming-soon .foot .arrow { display: none; }
.tool-card.coming-soon .foot .cost { font-style: italic; }
@media (max-width: 1100px) {
.toolbox { grid-template-columns: repeat(2, 1fr); }
}
/* ─── 生成资产 grid ─── */
.asset-section {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 22px 24px;
}
.asset-tabs {
display: flex; gap: 4px;
border-bottom: 1px solid var(--border-faint);
margin-bottom: 18px;
}
.asset-tab {
padding: 0 14px; height: 36px;
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 500;
color: var(--black-alpha-56);
cursor: pointer; border: 0; background: transparent;
position: relative;
transition: color var(--t-base);
}
.asset-tab .count {
font-family: var(--font-mono); font-size: 10.5px;
padding: 1px 6px; background: var(--background-lighter);
color: var(--black-alpha-48); border-radius: var(--r-pill);
letter-spacing: .04em;
}
.asset-tab:hover { color: var(--accent-black); }
.asset-tab.active { color: var(--accent-black); }
.asset-tab.active::after {
content: ''; position: absolute;
left: 0; right: 0; bottom: -1px; height: 2px;
background: var(--heat);
}
.asset-tab.active .count { color: var(--heat); background: var(--heat-12); }
.asset-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
.asset-it {
position: relative;
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
overflow: hidden;
background: var(--background-lighter);
cursor: pointer;
transition: all var(--t-base);
}
.asset-it:hover { border-color: var(--heat-40); transform: translateY(-1px); box-shadow: 0 4px 16px rgba(0,0,0,.04); }
.asset-it .a-thumb {
aspect-ratio: 1;
background-size: cover; background-position: center;
position: relative;
}
.asset-it .a-thumb.skeleton {
background: linear-gradient(110deg, var(--background-lighter) 8%, var(--black-alpha-4) 18%, var(--background-lighter) 33%);
background-size: 200% 100%;
animation: shimmer 1.4s linear infinite;
display: grid; place-items: center;
color: var(--black-alpha-48); font-family: var(--font-mono); font-size: 11px;
letter-spacing: .04em;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.asset-it .a-tag {
position: absolute; top: 8px; left: 8px;
font-family: var(--font-mono); font-size: 10px;
padding: 2px 7px;
background: rgba(255,255,255,.92); color: var(--black-alpha-72);
border-radius: var(--r-sm); letter-spacing: .04em;
}
.asset-it .a-body {
padding: 10px 12px;
border-top: 1px solid var(--border-faint);
background: var(--surface);
}
.asset-it .a-name { font-size: 12.5px; font-weight: 500; color: var(--accent-black); }
.asset-it .a-meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); margin-top: 2px; letter-spacing: .02em; }
.asset-it .a-actions {
position: absolute; top: 8px; right: 8px;
display: flex; gap: 4px;
opacity: 0; transition: opacity var(--t-base);
}
.asset-it:hover .a-actions { opacity: 1; }
.asset-it .a-actions button {
width: 26px; height: 26px;
background: rgba(255,255,255,.94); border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
display: grid; place-items: center;
color: var(--black-alpha-72); cursor: pointer;
padding: 0;
transition: all var(--t-fast);
}
.asset-it .a-actions button:hover { background: var(--heat); color: #fff; border-color: var(--heat); }
.asset-it .a-actions button.danger:hover { background: var(--accent-crimson); border-color: var(--accent-crimson); }
.asset-it .a-actions svg { width: 12px; height: 12px; }
.asset-empty {
grid-column: 1 / -1;
text-align: center; padding: 40px 0;
color: var(--black-alpha-48); font-size: 13px;
}
.asset-empty p { font-family: var(--font-mono); font-size: 11.5px; margin-top: 6px; }
/* ─── 抽屉(模特库 / 平台库) ─── */
.picker-bg {
position: fixed; inset: 0;
background: rgba(21,20,15,.42);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 1000;
display: none; align-items: center; justify-content: center;
opacity: 0; transition: opacity .2s;
}
.picker-bg.show { display: flex; opacity: 1; }
.picker {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
max-width: 720px; width: 92%;
max-height: calc(100vh - 80px);
display: flex; flex-direction: column;
position: relative;
transform: scale(.96);
transition: transform .25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.picker-bg.show .picker { transform: scale(1); }
.picker::before, .picker::after,
.picker .corner-tr, .picker .corner-bl {
content: ''; position: absolute;
width: 14px; height: 14px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
background-size: contain; pointer-events: none;
}
.picker::before { top: -7px; left: -7px; }
.picker::after { bottom: -7px; right: -7px; }
.picker .corner-tr { top: -7px; right: -7px; }
.picker .corner-bl { bottom: -7px; left: -7px; }
.picker-h {
padding: 22px 28px 18px;
border-bottom: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 14px;
flex-shrink: 0;
}
.picker-h .ic {
width: 36px; height: 36px;
background: var(--heat-12); color: var(--heat);
border-radius: var(--r-md);
display: grid; place-items: center;
}
.picker-h .ic svg { width: 17px; height: 17px; }
.picker-h .ti { flex: 1; }
.picker-h .ti .t { font-size: 16px; font-weight: 600; color: var(--accent-black); }
.picker-h .ti .d { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); margin-top: 4px; letter-spacing: .04em; }
.picker-h .x {
width: 32px; height: 32px; border-radius: var(--r-md);
display: grid; place-items: center;
color: var(--black-alpha-56); cursor: pointer;
background: transparent; border: 0;
transition: all var(--t-base);
}
.picker-h .x:hover { background: var(--black-alpha-4); color: var(--accent-crimson); }
.picker-h .x svg { width: 14px; height: 14px; }
.picker-b { padding: 22px 28px; flex: 1; overflow-y: auto; min-height: 0; }
.picker-f {
padding: 16px 24px;
border-top: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 10px;
background: var(--background-lighter);
flex-shrink: 0;
}
.picker-f .meta { flex: 1; font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
.picker-f .meta .accent { color: var(--heat); font-weight: 600; }
/* 模特 / 平台 选项卡片 */
.opt-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
.opt-card {
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 10px;
cursor: pointer;
transition: all var(--t-base);
background: var(--surface);
}
.opt-card:hover { border-color: var(--heat-40); background: var(--heat-4); }
.opt-card.selected { border-color: var(--heat); background: var(--heat-8); }
.opt-card .opt-thumb {
aspect-ratio: 1;
background: var(--background-lighter);
border-radius: var(--r-sm);
display: grid; place-items: center;
color: var(--black-alpha-48);
font-family: var(--font-mono); font-size: 11px;
margin-bottom: 8px;
overflow: hidden;
}
.opt-card.selected .opt-thumb { background: var(--heat-12); color: var(--heat); }
.opt-card .opt-name { font-size: 12.5px; font-weight: 500; color: var(--accent-black); }
.opt-card .opt-meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); margin-top: 2px; letter-spacing: .02em; }
.opt-card.selected .opt-name { color: var(--heat); }
/* 配置参数 */
.opt-config {
margin-top: 18px; padding-top: 18px;
border-top: 1px dashed var(--border-faint);
}
.opt-config-row {
display: flex; align-items: center; gap: 16px;
margin-bottom: 12px;
font-size: 13px;
}
.opt-config-row > label { width: 90px; color: var(--black-alpha-72); }
.opt-config-row .val { color: var(--accent-black); }
.opt-config-row .pillset { display: flex; gap: 6px; }
.opt-config-row .pillset .p {
padding: 5px 12px;
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
background: var(--surface);
font-size: 12px; color: var(--black-alpha-56);
cursor: pointer;
transition: all var(--t-base);
}
.opt-config-row .pillset .p:hover { color: var(--accent-black); border-color: var(--black-alpha-24); }
.opt-config-row .pillset .p.on { background: var(--heat-12); color: var(--heat); border-color: var(--heat-40); }
</style>
</head>
<body>
<div id="page">
<div class="page-head">
<div>
<h1>透真补水面膜</h1>
<div class="sub"><span class="mono">// 商品工作台 · 已生成 <span id="total-assets" style="color:var(--heat);font-weight:600">11</span> 张资产 · 创建于 5/19</span></div>
</div>
<div class="actions">
<a class="btn" href="products.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
返回商品库
</a>
<button class="btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2zM17 21v-8H7v8M7 3v5h8"/></svg>
保存
</button>
<a class="btn btn-primary" href="projects-new.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="18" height="16"/><path d="M7 4v16M16 4v16M3 9h18M3 15h18"/></svg>
创建视频项目
</a>
</div>
</div>
<!-- ============ Onboarding(首次进入显示) ============ -->
<div class="onboard-tip" id="onboard-tip" hidden>
<div class="ic">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
</div>
<div class="body">
<div class="t">商品已建档 · 推荐先生成白底三视图</div>
<div class="d">AI 以「主图」为基准生成正/侧/背 3 张白底图,Seedance 视频里商品的<strong>清晰度和稳定性 +60%</strong>。约 18 秒、¥1.6 · 失败不扣费。</div>
</div>
<div class="acts">
<button class="dismiss" onclick="dismissOnboard()">稍后再说</button>
<button class="btn btn-primary" onclick="dismissOnboard();openWhiteBg();">
<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="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
现在生成
</button>
</div>
</div>
<!-- ============ 商品概览卡(信息 + 原图合并) ============ -->
<div class="overview-card">
<div>
<div class="ov-info-head">
<div class="ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m3 7 9-4 9 4-9 4-9-4z"/><path d="m3 12 9 4 9-4M3 17l9 4 9-4"/></svg>
</div>
<div style="flex:1;min-width:0;">
<div class="name">透真玻尿酸补水面膜</div>
<div class="meta">[ 美妆个护 ] · ¥39.9 · 22-32 岁女性</div>
</div>
<button class="edit" onclick="alert('行内编辑商品信息 · demo')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
</div>
<div class="ov-tags">
<span class="t">熬夜党</span>
<span class="t">敏感肌</span>
<span class="t">补水</span>
<span class="t">玻尿酸</span>
<span class="t">通勤</span>
</div>
<div class="ov-sell">
<span class="lbl">// 卖点</span>
玻尿酸双效保湿 · 4 小时持久水润 · 敏感肌可用 · 通勤补水 · 平价代替
</div>
</div>
<div class="ov-photos">
<div class="ov-photos-h">
<span class="t">原图册 · <span class="n">3</span> / 5</span>
<span class="t" style="color:var(--black-alpha-32);">JPG / PNG · ≤ 5MB</span>
</div>
<div class="ov-photos-grid">
<div class="ov-photo" style="background-image: linear-gradient(135deg,#fde2c6 0%, #ffd0a8 100%);" title="主图">
<span class="pmain">MAIN</span>
</div>
<div class="ov-photo" style="background-image: linear-gradient(135deg,#dceafe 0%, #c3e0fe 100%);" title="包装"></div>
<div class="ov-photo" style="background-image: linear-gradient(135deg,#e7e3d5 0%, #d6d1c0 100%);" title="质地"></div>
<div class="ov-photo add" onclick="alert('上传新图 · demo')" title="添加">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
</div>
</div>
</div>
</div>
<!-- ============ AI 工具箱(横排 4 张卡) ============ -->
<div class="section-title">
<h2>AI 工具箱</h2>
<span class="sub">// 按需调用 · 生成结果自动入资产库 · 失败不扣费</span>
</div>
<div class="toolbox">
<div class="tool-card featured" onclick="openWhiteBg()">
<div class="ic-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg>
</div>
<div class="info">
<div class="t">白底三视图</div>
<div class="d">正/侧/背 · Seedance 效果 +60%</div>
</div>
<div class="foot">
<span class="cost">~18 秒 · <b>¥1.6</b></span>
<span class="arrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></span>
</div>
</div>
<div class="tool-card" onclick="openModelPicker()">
<div class="ic-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v2"/></svg>
</div>
<div class="info">
<div class="t">AI 模特上身图</div>
<div class="d">8 模特库可选 · 每次 4 张</div>
</div>
<div class="foot">
<span class="cost">~35 秒 · <b>¥3.2</b></span>
<span class="arrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></span>
</div>
</div>
<div class="tool-card" onclick="openPlatformPicker()">
<div class="ic-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
</div>
<div class="info">
<div class="t">平台套图</div>
<div class="d">6 平台规格 · 每平台 4 张</div>
</div>
<div class="foot">
<span class="cost">~28 秒 · <b>¥2.4</b></span>
<span class="arrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></span>
</div>
</div>
<div class="tool-card coming-soon">
<div class="ic-box" style="background:var(--background-lighter);color:var(--black-alpha-48);">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 3"/></svg>
</div>
<div class="info">
<div class="t" style="color:var(--black-alpha-72);">更多工具</div>
<div class="d">卖点海报 / 详情页插画</div>
</div>
<div class="foot">
<span class="cost">即将上线</span>
</div>
</div>
</div>
<!-- ============ 已生成资产 ============ -->
<div class="asset-section">
<div class="section-title">
<h2>已生成资产</h2>
<span class="sub">// 自动入「资产库 / 跨项目共享 / 商品图」</span>
</div>
<div class="asset-tabs" id="asset-tabs">
<button class="asset-tab active" data-tab="all">全部 <span class="count" id="c-all">11</span></button>
<button class="asset-tab" data-tab="white">白底三视图 <span class="count" id="c-white">3</span></button>
<button class="asset-tab" data-tab="model">模特上身 <span class="count" id="c-model">4</span></button>
<button class="asset-tab" data-tab="platform">平台套图 <span class="count" id="c-platform">4</span></button>
</div>
<div class="asset-grid" id="asset-grid"></div>
</div>
</div>
<!-- ============ 模特库选择抽屉 ============ -->
<div class="picker-bg" id="model-picker-bg" onclick="if(event.target===this)closePicker('model')">
<div class="picker">
<span class="corner-tr"></span><span class="corner-bl"></span>
<div class="picker-h">
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v2"/></svg></div>
<div class="ti"><div class="t">选择模特</div><div class="d">// 系统已有 8 位模特 · 每次生成 4 张上身图</div></div>
<button class="x" onclick="closePicker('model')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 6L6 18M6 6l12 12"/></svg></button>
</div>
<div class="picker-b">
<div class="opt-grid" id="model-grid"></div>
<div class="opt-config">
<div class="opt-config-row">
<label>场景偏好</label>
<div class="pillset" data-key="scene">
<span class="p on" data-v="室内自拍">室内自拍</span>
<span class="p" data-v="梳妆台">梳妆台</span>
<span class="p" data-v="户外清晨">户外清晨</span>
<span class="p" data-v="纯色背景">纯色背景</span>
</div>
</div>
<div class="opt-config-row">
<label>构图</label>
<div class="pillset" data-key="frame">
<span class="p on" data-v="半身">半身</span>
<span class="p" data-v="特写">特写</span>
<span class="p" data-v="全身">全身</span>
</div>
</div>
<div class="opt-config-row">
<label>数量</label>
<span class="val">4 张 · 不满意可原地重跑</span>
</div>
</div>
</div>
<div class="picker-f">
<span class="meta">// 预计耗时 35 秒 · <span class="accent">¥3.2</span> · 失败不扣费</span>
<button class="btn" onclick="closePicker('model')">取消</button>
<button class="btn btn-primary" id="model-go-btn" disabled>
<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="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
开始生成
</button>
</div>
</div>
</div>
<!-- ============ 平台选择抽屉 ============ -->
<div class="picker-bg" id="platform-picker-bg" onclick="if(event.target===this)closePicker('platform')">
<div class="picker">
<span class="corner-tr"></span><span class="corner-bl"></span>
<div class="picker-h">
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg></div>
<div class="ti"><div class="t">选择平台</div><div class="d">// 按平台调性 + 比例生成 · 每平台 4 张</div></div>
<button class="x" onclick="closePicker('platform')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 6L6 18M6 6l12 12"/></svg></button>
</div>
<div class="picker-b">
<div class="opt-grid" id="platform-grid"></div>
<div class="opt-config">
<div class="opt-config-row">
<label>类型</label>
<div class="pillset" data-key="ptype">
<span class="p on" data-v="主图">主图(1:1)</span>
<span class="p" data-v="详情页头图">详情页头图(750×1000)</span>
<span class="p" data-v="活动横幅">活动横幅(750×260)</span>
</div>
</div>
<div class="opt-config-row">
<label>风格</label>
<div class="pillset" data-key="pstyle">
<span class="p on" data-v="干净电商">干净电商</span>
<span class="p" data-v="种草风">种草风</span>
<span class="p" data-v="节日热闹">节日热闹</span>
</div>
</div>
<div class="opt-config-row">
<label>数量</label>
<span class="val">4 张 · 不满意可原地重跑</span>
</div>
</div>
</div>
<div class="picker-f">
<span class="meta">// 预计耗时 28 秒 · <span class="accent">¥2.4</span> · 失败不扣费</span>
<button class="btn" onclick="closePicker('platform')">取消</button>
<button class="btn btn-primary" id="platform-go-btn" disabled>
<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="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
开始生成
</button>
</div>
</div>
</div>
<!-- ============ 白底三视图确认弹窗 ============ -->
<div class="picker-bg" id="white-picker-bg" onclick="if(event.target===this)closePicker('white')">
<div class="picker" style="max-width: 460px;">
<span class="corner-tr"></span><span class="corner-bl"></span>
<div class="picker-h">
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg></div>
<div class="ti"><div class="t">生成白底三视图</div><div class="d">// 一次出 3 张 · 正面 / 侧面 / 背面</div></div>
<button class="x" onclick="closePicker('white')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 6L6 18M6 6l12 12"/></svg></button>
</div>
<div class="picker-b">
<div style="font-size:13px;color:var(--black-alpha-72);line-height:1.7;margin-bottom:14px;">
AI 会以「主图」为基准,自动去白底 + 重打光 + 推算另外两个视角。商品形态尽量保持稳定。
</div>
<div class="opt-config" style="margin-top:0;padding-top:0;border:0;">
<div class="opt-config-row">
<label>视角</label>
<div class="pillset" data-key="angles">
<span class="p on" data-v="正">正面</span>
<span class="p on" data-v="侧">侧面</span>
<span class="p on" data-v="背">背面</span>
</div>
</div>
<div class="opt-config-row">
<label>背景</label>
<div class="pillset" data-key="bg">
<span class="p on" data-v="纯白">纯白</span>
<span class="p" data-v="浅灰">浅灰</span>
<span class="p" data-v="渐变">柔和渐变</span>
</div>
</div>
</div>
</div>
<div class="picker-f">
<span class="meta">// 预计 18 秒 · <span class="accent">¥1.6</span></span>
<button class="btn" onclick="closePicker('white')">取消</button>
<button class="btn btn-primary" id="white-go-btn">
<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="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
开始生成
</button>
</div>
</div>
</div>
<script src="assets/shell.js"></script>
<script>
Shell.render({
active: 'products',
crumbs: [
{ label: '工作台', href: 'index.html' },
{ label: '商品库', href: 'products.html' },
{ label: '透真补水面膜' }
]
});
// 修复:让 picker 弹窗脱离 main 的 stacking,提到 body 末尾
['model-picker-bg', 'platform-picker-bg', 'white-picker-bg'].forEach(id => {
const el = document.getElementById(id);
if (el && el.parentElement !== document.body) document.body.appendChild(el);
});
// Onboarding: ?from=new&onboard=1 时显示
const _qs = new URLSearchParams(location.search);
if (_qs.get('onboard') === '1' || _qs.get('from') === 'new') {
const tip = document.getElementById('onboard-tip');
if (tip) tip.hidden = false;
}
function dismissOnboard() {
const tip = document.getElementById('onboard-tip');
if (tip) tip.hidden = true;
}
// ============= 假数据 =============
const MODELS = [
{ id: 'm1', name: '林夕', meta: '女 · 25 · 都市白领', emoji: '◐' },
{ id: 'm2', name: '小苏', meta: '女 · 23 · 文艺学生', emoji: '◑' },
{ id: 'm3', name: '阿楠', meta: '女 · 28 · 同事型', emoji: '◓' },
{ id: 'm4', name: '小七', meta: '女 · 20 · 学生', emoji: '◒' },
{ id: 'm5', name: '王姐', meta: '女 · 38 · 居家', emoji: '◍' },
{ id: 'm6', name: '阿杰', meta: '男 · 30 · 都市', emoji: '◐' },
{ id: 'm7', name: '阿强', meta: '男 · 26 · 健身', emoji: '◑' },
{ id: 'm8', name: '+ 自定义', meta: '上传自有模特照', emoji: '+', custom: true },
];
const PLATFORMS = [
{ id: 'p1', name: '淘宝 / 天猫', meta: '1:1 · 800×800', emoji: '🅣' },
{ id: 'p2', name: '抖店', meta: '3:4 · 750×1000', emoji: '🅓' },
{ id: 'p3', name: '拼多多', meta: '1:1 · 800×800', emoji: '🅟' },
{ id: 'p4', name: '京东', meta: '1:1 · 800×800', emoji: '🅙' },
{ id: 'p5', name: '小红书', meta: '3:4 · 600×800', emoji: '🅡' },
{ id: 'p6', name: '1688', meta: '1:1 · 750×750', emoji: '🅑' },
];
// 已生成资产 (mock)
let assets = [
{ id: 'a1', kind: 'white', name: '白底 · 正面', meta: '512×512 · 5/19 14:23', color: '#f4f4f4' },
{ id: 'a2', kind: 'white', name: '白底 · 侧面', meta: '512×512 · 5/19 14:23', color: '#f0f0f0' },
{ id: 'a3', kind: 'white', name: '白底 · 背面', meta: '512×512 · 5/19 14:23', color: '#eeeeee' },
{ id: 'a4', kind: 'model', name: '林夕 · 上身 01', meta: '小美 · 1024×1024 · 5/19', color: 'linear-gradient(135deg,#ffe0b2,#ffccbc)' },
{ id: 'a5', kind: 'model', name: '林夕 · 上身 02', meta: '小美 · 1024×1024 · 5/19', color: 'linear-gradient(135deg,#fde2c6,#ffbcaa)' },
{ id: 'a6', kind: 'model', name: '林夕 · 上身 03', meta: '小美 · 1024×1024 · 5/19', color: 'linear-gradient(135deg,#fbcfe8,#fce7f3)' },
{ id: 'a7', kind: 'model', name: '林夕 · 上身 04', meta: '小美 · 1024×1024 · 5/19', color: 'linear-gradient(135deg,#f3e8ff,#fae8ff)' },
{ id: 'a8', kind: 'platform', name: '淘宝 · 主图 01', meta: '800×800 · 5/19', color: 'linear-gradient(135deg,#fff1f0,#ffd6cc)' },
{ id: 'a9', kind: 'platform', name: '淘宝 · 主图 02', meta: '800×800 · 5/19', color: 'linear-gradient(135deg,#fef3c7,#fde68a)' },
{ id: 'a10', kind: 'platform', name: '淘宝 · 主图 03', meta: '800×800 · 5/19', color: 'linear-gradient(135deg,#dcfce7,#bbf7d0)' },
{ id: 'a11', kind: 'platform', name: '淘宝 · 主图 04', meta: '800×800 · 5/19', color: 'linear-gradient(135deg,#dbeafe,#bfdbfe)' },
];
let currentTab = 'all';
let pickerState = {
model: { selected: '', scene: '室内自拍', frame: '半身' },
platform: { selected: '', ptype: '主图', pstyle: '干净电商' },
white: { angles: ['正','侧','背'], bg: '纯白' },
};
const $ = id => document.getElementById(id);
// ============= 渲染:已生成资产 grid =============
function renderAssets() {
const grid = $('asset-grid');
const filtered = currentTab === 'all' ? assets : assets.filter(a => a.kind === currentTab);
if (filtered.length === 0) {
grid.innerHTML = `<div class="asset-empty">还没有这类资产<p>// 用上方工具箱生成</p></div>`;
return;
}
grid.innerHTML = filtered.map(a => {
const tagText = { white: '白底', model: '模特', platform: '平台' }[a.kind];
const isGradient = (a.color || '').startsWith('linear');
const bg = isGradient ? a.color : a.color;
return `
<div class="asset-it" data-id="${a.id}">
<div class="a-thumb${a.skeleton ? ' skeleton' : ''}" style="${a.skeleton ? '' : (isGradient ? `background:${bg}` : `background-color:${bg}`)}">
${a.skeleton ? '生成中…' : `<span class="a-tag">${tagText}</span>`}
</div>
${a.skeleton ? '' : `
<div class="a-actions">
<button title="预览" onclick="event.stopPropagation();alert('预览 · demo')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
</button>
<button title="重跑" onclick="event.stopPropagation();regen('${a.id}')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5"/></svg>
</button>
<button class="danger" title="删除" onclick="event.stopPropagation();delAsset('${a.id}')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
</button>
</div>
<div class="a-body">
<div class="a-name">${a.name}</div>
<div class="a-meta">${a.meta}</div>
</div>`}
</div>
`;
}).join('');
}
function updateCounts() {
$('c-all').textContent = assets.filter(a => !a.skeleton).length;
$('c-white').textContent = assets.filter(a => a.kind === 'white' && !a.skeleton).length;
$('c-model').textContent = assets.filter(a => a.kind === 'model' && !a.skeleton).length;
$('c-platform').textContent = assets.filter(a => a.kind === 'platform' && !a.skeleton).length;
$('total-assets').textContent = assets.filter(a => !a.skeleton).length;
}
// Tab 切换
document.querySelectorAll('#asset-tabs .asset-tab').forEach(t => {
t.addEventListener('click', () => {
document.querySelectorAll('#asset-tabs .asset-tab').forEach(x => x.classList.remove('active'));
t.classList.add('active');
currentTab = t.dataset.tab;
renderAssets();
});
});
// ============= 抽屉:打开/关闭 =============
function openPicker(which) {
$(`${which}-picker-bg`).classList.add('show');
}
function closePicker(which) {
$(`${which}-picker-bg`).classList.remove('show');
}
function openWhiteBg() {
openPicker('white');
}
function openModelPicker() {
// 填充模特库
$('model-grid').innerHTML = MODELS.map(m => `
<div class="opt-card${m.custom ? ' add' : ''}" data-id="${m.id}" onclick="selectOption('model', '${m.id}', this)">
<div class="opt-thumb">${m.emoji}</div>
<div class="opt-name">${m.name}</div>
<div class="opt-meta">${m.meta}</div>
</div>
`).join('');
$('model-go-btn').disabled = true;
pickerState.model.selected = '';
openPicker('model');
}
function openPlatformPicker() {
$('platform-grid').innerHTML = PLATFORMS.map(p => `
<div class="opt-card" data-id="${p.id}" onclick="selectOption('platform', '${p.id}', this)">
<div class="opt-thumb">${p.emoji}</div>
<div class="opt-name">${p.name}</div>
<div class="opt-meta">${p.meta}</div>
</div>
`).join('');
$('platform-go-btn').disabled = true;
pickerState.platform.selected = '';
openPicker('platform');
}
function selectOption(which, id, el) {
el.parentElement.querySelectorAll('.opt-card').forEach(x => x.classList.remove('selected'));
el.classList.add('selected');
pickerState[which].selected = id;
$(`${which}-go-btn`).disabled = false;
}
// pillset 切换
document.querySelectorAll('.pillset').forEach(set => {
set.addEventListener('click', e => {
const p = e.target.closest('.p');
if (!p) return;
const key = set.dataset.key;
// 白底 angles 是多选,其他单选
if (key === 'angles') {
p.classList.toggle('on');
} else {
set.querySelectorAll('.p').forEach(x => x.classList.remove('on'));
p.classList.add('on');
}
});
});
// ============= 开始生成 =============
function startGen(kind, makeCards) {
// 关闭抽屉
closePicker(kind === 'white' ? 'white' : kind);
// 切到对应 tab
const tabKey = kind === 'white' ? 'white' : kind;
document.querySelectorAll('#asset-tabs .asset-tab').forEach(x =>
x.classList.toggle('active', x.dataset.tab === tabKey));
currentTab = tabKey;
// 插入 skeleton 卡到 assets 开头
const skeletonItems = makeCards.map((_, i) => ({
id: `sk-${Date.now()}-${i}`,
kind, skeleton: true,
name: '生成中', meta: 'pending',
color: '#f0f0f0',
}));
assets = [...skeletonItems, ...assets];
renderAssets();
// 1.5s 后替换为真实卡
setTimeout(() => {
const newCards = makeCards.map((c, i) => ({
id: `g-${Date.now()}-${i}`,
kind,
name: c.name,
meta: c.meta,
color: c.color,
}));
// 删除 skeleton
assets = assets.filter(a => !skeletonItems.find(s => s.id === a.id));
// 插入真实卡
assets = [...newCards, ...assets];
renderAssets();
updateCounts();
Shell.toast(`已生成 ${newCards.length}`, '入资产库');
}, 1500);
}
// 白底生成
$('white-go-btn').addEventListener('click', () => {
const angles = [...document.querySelectorAll('#white-picker-bg .pillset[data-key="angles"] .p.on')].map(x => x.dataset.v);
if (!angles.length) { alert('至少选择一个视角'); return; }
startGen('white', angles.map((a, i) => ({
name: `白底 · ${a}`,
meta: `512×512 · 刚刚生成`,
color: i === 0 ? '#fafafa' : i === 1 ? '#f5f5f5' : '#f0f0f0',
})));
});
// 模特生成
$('model-go-btn').addEventListener('click', () => {
if (!pickerState.model.selected) return;
const model = MODELS.find(m => m.id === pickerState.model.selected);
if (!model) return;
const palette = [
'linear-gradient(135deg,#fcd5ce,#f8edeb)',
'linear-gradient(135deg,#cfe1b9,#e9edc9)',
'linear-gradient(135deg,#bee3f8,#c3dafe)',
'linear-gradient(135deg,#fde2e4,#fad2e1)',
];
startGen('model', palette.map((color, i) => ({
name: `${model.name} · 上身 0${i+1}`,
meta: `${model.name} · 1024×1024 · 刚刚`,
color,
})));
});
// 平台生成
$('platform-go-btn').addEventListener('click', () => {
if (!pickerState.platform.selected) return;
const plat = PLATFORMS.find(p => p.id === pickerState.platform.selected);
if (!plat) return;
const palette = [
'linear-gradient(135deg,#fff7ed,#ffedd5)',
'linear-gradient(135deg,#fef3c7,#fde68a)',
'linear-gradient(135deg,#ecfccb,#d9f99d)',
'linear-gradient(135deg,#cffafe,#a5f3fc)',
];
startGen('platform', palette.map((color, i) => ({
name: `${plat.name} · 0${i+1}`,
meta: `${plat.name} · ${plat.meta.split('·')[1]||'800×800'} · 刚刚`,
color,
})));
});
// 重跑 & 删除
function regen(id) {
const idx = assets.findIndex(a => a.id === id);
if (idx < 0) return;
const old = assets[idx];
assets[idx] = { ...old, skeleton: true, name: '重跑中', meta: 'pending' };
renderAssets();
setTimeout(() => {
assets[idx] = { ...old, id: `g-${Date.now()}`, meta: `${old.meta.split('·')[0]}· 刚刚重跑` };
renderAssets();
Shell.toast('已重新生成', old.name);
}, 1200);
}
function delAsset(id) {
const a = assets.find(x => x.id === id);
assets = assets.filter(x => x.id !== id);
renderAssets();
updateCounts();
Shell.toast('已删除', a?.name || '');
}
// ESC 关
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
document.querySelectorAll('.picker-bg.show').forEach(p => p.classList.remove('show'));
}
});
// 初始化
renderAssets();
updateCounts();
</script>
</body>
</html>