1086 lines
45 KiB
HTML
1086 lines
45 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>商品工作台 · Airshelf</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/restraint.css?v=2026052607">
|
||
<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="Shell.toast('打开编辑面板','跳转至 product-detail.html · 行内编辑商品信息');setTimeout(()=>location.href='product-detail.html',600);">
|
||
<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="document.getElementById('__ov-photo-input').click()" title="添加"><input type="file" id="__ov-photo-input" accept="image/*" multiple hidden onchange="if(this.files.length)Shell.toast('已上传 '+this.files.length+' 张','补充图已加入商品图册')">
|
||
<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">// 单张 16:9 · 正 / 侧 / 背 合一</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/icons.js?v=2026052608"></script>
|
||
<script src="assets/shell.js?v=2026052607"></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 icon = (name, opts) => window.IconKit ? window.IconKit.svg(name, opts) : '';
|
||
|
||
const MODELS = [
|
||
{ id: 'm1', name: '林夕', meta: '女 · 25 · 都市白领', icon: 'users' },
|
||
{ id: 'm2', name: '小苏', meta: '女 · 23 · 文艺学生', icon: 'users' },
|
||
{ id: 'm3', name: '阿楠', meta: '女 · 28 · 同事型', icon: 'users' },
|
||
{ id: 'm4', name: '小七', meta: '女 · 20 · 学生', icon: 'users' },
|
||
{ id: 'm5', name: '王姐', meta: '女 · 38 · 居家', icon: 'users' },
|
||
{ id: 'm6', name: '阿杰', meta: '男 · 30 · 都市', icon: 'users' },
|
||
{ id: 'm7', name: '阿强', meta: '男 · 26 · 健身', icon: 'users' },
|
||
{ id: 'm8', name: '+ 自定义', meta: '上传自有模特照', icon: 'userPlus', custom: true },
|
||
];
|
||
|
||
const PLATFORMS = [
|
||
{ id: 'p1', name: '淘宝 / 天猫', meta: '1:1 · 800×800', icon: 'image' },
|
||
{ id: 'p2', name: '抖店', meta: '3:4 · 750×1000', icon: 'image' },
|
||
{ id: 'p3', name: '拼多多', meta: '1:1 · 800×800', icon: 'image' },
|
||
{ id: 'p4', name: '京东', meta: '1:1 · 800×800', icon: 'image' },
|
||
{ id: 'p5', name: '小红书', meta: '3:4 · 600×800', icon: 'image' },
|
||
{ id: 'p6', name: '1688', meta: '1:1 · 750×750', icon: 'image' },
|
||
];
|
||
|
||
// 已生成资产 (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();(window.Shell&&Shell._openLightbox?Shell._openLightbox('','${a.id} · 预览'):Shell.toast('打开预览','${a.id}'))">
|
||
<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">${icon(m.icon, { size: 18 })}</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">${icon(p.icon, { size: 18 })}</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>
|