AirShelf/电商AI平台/platform-cover.html
UI 设计 868ba69ea4
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
fix(cache): 给 shell.js/restraint.css 加 ?v=时间戳 强制绕过浏览器旧缓存
2026-05-21 16:44:10 +08:00

1361 lines
54 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?v=202605211643">
<style>
.app { height: 100vh; overflow: hidden; }
main { display: flex; flex-direction: column; min-height: 0; }
#page-content { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 24px 28px 0; }
.page-head { flex-shrink: 0; margin-bottom: 16px; }
.pc-layout {
flex: 1; min-height: 0;
display: grid;
grid-template-columns: 220px 360px 1fr;
gap: 20px;
padding-bottom: 24px;
}
@media (max-width: 1280px) {
.pc-layout { grid-template-columns: 200px 340px 1fr; gap: 16px; }
}
/* ─── 任务栏 (最左 · 历史任务列表) ─── */
.pc-tasks-panel {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: flex; flex-direction: column;
overflow: hidden;
}
.pc-tasks-h {
display: flex; align-items: center; gap: 6px;
padding: 14px 16px 10px;
border-bottom: 1px solid var(--border-faint);
font-size: 13px; font-weight: 600; color: var(--accent-black);
}
.pc-tasks-h .ct {
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .02em;
margin-left: auto;
}
.pc-tasks-h .new {
margin-left: 6px;
width: 24px; height: 24px;
display: grid; place-items: center;
background: var(--heat-12); color: var(--heat);
border: 0; border-radius: var(--r-sm);
cursor: pointer;
transition: background var(--t-base);
}
.pc-tasks-h .new:hover { background: var(--heat-20); }
.pc-tasks-h .new svg { width: 12px; height: 12px; }
.pc-tasks-list {
flex: 1; min-height: 0;
overflow-y: auto;
padding: 8px;
display: flex; flex-direction: column; gap: 6px;
}
.pc-tasks-empty {
padding: 24px 14px;
text-align: center;
font-size: 11.5px;
color: var(--black-alpha-48);
line-height: 1.55;
}
.pc-tasks-empty .mono {
font-family: var(--font-mono); font-size: 10.5px;
letter-spacing: .02em; display: block; margin-top: 4px;
}
.pc-task-card {
padding: 10px 12px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
cursor: pointer;
transition: background var(--t-base), border-color var(--t-base);
position: relative;
}
.pc-task-card:hover { background: var(--surface); border-color: var(--black-alpha-24); }
.pc-task-card.active { background: var(--heat-12); border-color: var(--heat-20); }
.pc-task-card .nm {
font-size: 12.5px; font-weight: 600; color: var(--accent-black);
line-height: 1.35;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
padding-right: 18px;
}
.pc-task-card.active .nm { color: var(--heat); }
.pc-task-card .meta {
margin-top: 4px;
font-family: var(--font-mono); font-size: 10px;
color: var(--black-alpha-48); letter-spacing: .02em;
display: flex; align-items: center; gap: 6px;
}
.pc-task-card .meta .pill-mini {
padding: 1px 6px; border-radius: var(--r-pill);
font-size: 9.5px; font-weight: 600; letter-spacing: .04em;
}
.pc-task-card .meta .pill-mini.gen { background: var(--heat-12); color: var(--heat); }
.pc-task-card .meta .pill-mini.ok { background: var(--forest-bg); color: var(--accent-forest); }
.pc-task-card .meta .pill-mini.err { background: var(--crimson-bg); color: var(--accent-crimson); }
.pc-task-card .x {
position: absolute; top: 6px; right: 6px;
width: 18px; height: 18px;
display: grid; place-items: center;
background: transparent; border: 0;
border-radius: var(--r-sm);
cursor: pointer;
color: var(--black-alpha-48);
opacity: 0;
transition: opacity var(--t-base), background var(--t-base), color var(--t-base);
}
.pc-task-card:hover .x { opacity: 1; }
.pc-task-card .x:hover { background: var(--black-alpha-8); color: var(--accent-crimson); }
.pc-task-card .x svg { width: 10px; height: 10px; }
/* 左栏 表单 */
.pc-form {
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
overflow-y: auto;
padding: 18px 20px;
display: flex; flex-direction: column;
}
.pc-step { margin-bottom: 22px; }
.pc-step:last-child { margin-bottom: 0; }
.pc-step-h { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.pc-step-h .num {
width: 22px; height: 22px; border-radius: 50%;
background: var(--heat-12); color: var(--heat);
font-family: var(--font-mono); font-size: 11px; font-weight: 700;
display: grid; place-items: center;
}
.pc-step-h .title { font-size: 14px; font-weight: 600; color: var(--accent-black); }
/* 商品选择 · 已选 chip 列表 */
.prod-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
.prod-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
transition: background var(--t-base), border-color var(--t-base);
}
.prod-row .thumb { width: 28px; height: 28px; border-radius: var(--r-sm); flex-shrink: 0; }
.prod-row .info { flex: 1; min-width: 0; }
.prod-row .nm {
font-size: 12.5px; color: var(--accent-black); font-weight: 500;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prod-row .meta {
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .02em;
margin-top: 2px;
}
.prod-row .x {
width: 22px; height: 22px;
display: grid; place-items: center;
background: transparent; border: 0;
border-radius: var(--r-sm);
cursor: pointer;
color: var(--black-alpha-48);
flex-shrink: 0;
transition: background var(--t-base), color var(--t-base);
}
.prod-row .x:hover { background: var(--black-alpha-8); color: var(--accent-crimson); }
.prod-row .x svg { width: 12px; height: 12px; }
.prod-empty {
padding: 14px 10px;
text-align: center;
background: var(--background-lighter);
border: 1px dashed var(--border-faint);
border-radius: var(--r-sm);
color: var(--black-alpha-48);
font-size: 12px;
margin-bottom: 6px;
}
.prod-empty .mono {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: .02em;
margin-top: 4px;
}
.prod-add {
display: flex; align-items: center; justify-content: center; gap: 6px;
padding: 8px 10px;
background: transparent;
border: 1px dashed var(--heat-40);
border-radius: var(--r-sm);
cursor: pointer;
font-size: 12.5px; color: var(--heat);
font-family: inherit;
transition: background var(--t-base);
width: 100%;
}
.prod-add:hover { background: var(--heat-12); }
.prod-add svg { width: 12px; height: 12px; }
/* 商品库全屏弹窗 */
.pl-modal-bg {
position: fixed; inset: 0;
background: var(--surface);
z-index: 998;
display: none;
}
.pl-modal-bg.show { display: flex; }
.pl-modal {
margin: 0; flex: 1;
background: var(--surface);
overflow: hidden;
display: flex; flex-direction: column;
}
.pl-modal-h {
display: flex; align-items: center; gap: 14px;
padding: 14px 28px;
border-bottom: 1px solid var(--border-faint);
flex-shrink: 0;
}
.pl-modal-h h2 { font-size: 16px; font-weight: 600; }
.pl-modal-h .ct {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .02em;
}
.pl-modal-h .actions { margin-left: auto; display: flex; gap: 10px; }
.pl-modal-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 200px 1fr; }
.pl-side {
border-right: 1px solid var(--border-faint);
padding: 18px 0;
overflow-y: auto;
}
.pl-side .pl-side-h {
padding: 0 20px 8px;
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .06em;
}
.pl-side .pl-side-item {
display: flex; align-items: center; gap: 8px;
padding: 9px 20px;
cursor: pointer;
color: var(--black-alpha-72);
font-size: 13px;
border-left: 3px solid transparent;
transition: background var(--t-base), color var(--t-base);
}
.pl-side .pl-side-item:hover { background: var(--black-alpha-4); }
.pl-side .pl-side-item.active {
background: var(--heat-12); color: var(--accent-black);
border-left-color: var(--heat); font-weight: 600;
}
.pl-side .pl-side-item .ct {
margin-left: auto;
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48);
}
.pl-main { overflow-y: auto; padding: 0; display: flex; flex-direction: column; }
.pl-toolbar {
padding: 14px 28px;
border-bottom: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 12px;
flex-shrink: 0;
}
.pl-toolbar .search { position: relative; flex: 1; max-width: 360px; }
.pl-toolbar .search input {
width: 100%; height: 32px;
padding: 0 10px 0 32px;
background: var(--background-lighter);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-sm);
font-size: 12.5px; font-family: inherit;
color: var(--accent-black); outline: none;
}
.pl-toolbar .search svg {
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
width: 14px; height: 14px; color: var(--black-alpha-48);
}
.pl-toolbar .btn-new {
height: 32px; padding: 0 14px;
display: inline-flex; align-items: center; gap: 6px;
background: var(--surface);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-sm);
color: var(--accent-black);
font-family: inherit; font-size: 12.5px;
cursor: pointer;
margin-left: auto;
transition: background var(--t-base), border-color var(--t-base);
}
.pl-toolbar .btn-new:hover { background: var(--background-lighter); border-color: var(--black-alpha-24); }
.pl-toolbar .btn-new svg { width: 13px; height: 13px; }
.pl-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 28px 28px; }
.pl-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 12px;
}
.pl-card {
position: relative;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 10px;
cursor: pointer;
display: flex; flex-direction: column; gap: 6px;
transition: background var(--t-base), border-color var(--t-base);
}
.pl-card:hover { background: var(--surface); }
.pl-card.selected { border-color: var(--heat); background: var(--heat-12); }
.pl-card .pl-thumb { aspect-ratio: 1; border-radius: var(--r-sm); }
.pl-card .pl-name { font-size: 12.5px; font-weight: 500; color: var(--accent-black); }
.pl-card .pl-meta {
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .02em;
}
.pl-card .pl-check {
position: absolute; top: 16px; right: 16px;
width: 22px; height: 22px;
background: rgba(255,255,255,.95);
border: 1.5px solid var(--black-alpha-24);
border-radius: 50%;
display: grid; place-items: center;
z-index: 2;
color: var(--accent-white);
}
.pl-card .pl-check svg { width: 11px; height: 11px; opacity: 0; }
.pl-card.selected .pl-check { background: var(--heat); border-color: var(--heat); }
.pl-card.selected .pl-check svg { opacity: 1; }
/* 卡片右上 actions: 编辑 + 删除 (hover 显示, check 左侧) */
.pl-card .pl-card-actions {
position: absolute;
top: 14px; right: 44px;
display: flex; gap: 4px;
z-index: 2;
opacity: 0;
transition: opacity var(--t-base);
}
.pl-card:hover .pl-card-actions { opacity: 1; }
.pl-card .pl-act {
width: 26px; height: 26px;
background: rgba(255,255,255,.95);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-sm);
display: grid; place-items: center;
cursor: pointer;
color: var(--black-alpha-72);
transition: color var(--t-base), border-color var(--t-base), background var(--t-base);
}
.pl-card .pl-act:hover { color: var(--heat); border-color: var(--heat); background: var(--surface); }
.pl-card .pl-act.danger:hover { color: var(--accent-crimson); border-color: var(--accent-crimson); }
.pl-card .pl-act svg { width: 12px; height: 12px; }
/* 编辑商品 drawer */
.pc-drawer { width: 720px; max-width: 100vw; z-index: 1101; }
.pc-drawer .drawer-b { padding: 24px 28px; }
#pc-drawer-bg.drawer-bg { z-index: 1100; }
.pc-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.pc-field-label { font-size: 13px; font-weight: 500; color: var(--accent-black); }
.pc-field-label .req { color: var(--accent-crimson); margin-left: 2px; }
.pc-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 18px; }
.pc-field-row > div { display: flex; flex-direction: column; gap: 6px; }
.pc-bullets {
list-style: none; padding: 0; margin: 0;
display: flex; flex-direction: column; gap: 6px;
}
.pc-bullets li {
display: flex; align-items: center; gap: 8px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
padding: 0 10px;
height: 36px;
}
.pc-bullets li.add { border-style: dashed; border-color: var(--heat-40); }
.pc-bullets li .num {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); width: 18px; text-align: center;
flex-shrink: 0;
}
.pc-bullets li.add .num { color: var(--heat); }
.pc-bullets li input {
flex: 1; border: 0; background: transparent; outline: none;
font-size: 13px; color: var(--accent-black);
font-family: inherit;
}
.pc-bullets li input::placeholder { color: var(--black-alpha-48); }
.pc-bullets li .rm {
width: 22px; height: 22px;
background: transparent; border: 0; border-radius: var(--r-sm);
color: var(--black-alpha-48); cursor: pointer;
display: grid; place-items: center;
}
.pc-bullets li .rm:hover { color: var(--accent-crimson); background: var(--black-alpha-4); }
.pc-bullets li .rm svg { width: 11px; height: 11px; }
.pl-modal-f {
padding: 14px 28px;
border-top: 1px solid var(--border-faint);
display: flex; justify-content: flex-end; align-items: center; gap: 10px;
flex-shrink: 0;
}
.pl-modal-f .summary {
margin-right: auto;
font-family: var(--font-mono); font-size: 12px;
color: var(--black-alpha-56); letter-spacing: .02em;
}
.pl-modal-f .summary b { color: var(--heat); font-weight: 700; }
/* 平台多选卡片 */
.platform-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px;
}
.platform-card {
position: relative;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 10px 6px;
cursor: pointer;
text-align: center;
transition: background var(--t-base), border-color var(--t-base);
}
.platform-card:hover { background: var(--surface); }
.platform-card.selected { border-color: var(--heat); background: var(--heat-12); }
.platform-card .p-logo {
width: 32px; height: 32px;
margin: 0 auto 4px;
border-radius: var(--r-md);
display: grid; place-items: center;
color: var(--accent-white);
font-family: var(--font-mono);
font-size: 11px; font-weight: 700;
}
.platform-card .p-name {
font-size: 11.5px;
color: var(--accent-black);
font-weight: 500;
}
.platform-card.selected .p-name { color: var(--heat); }
.platform-card .p-check {
position: absolute; top: 4px; right: 4px;
width: 16px; height: 16px;
border-radius: 50%;
background: transparent;
border: 1.5px solid var(--black-alpha-24);
}
.platform-card.selected .p-check {
background: var(--heat); border-color: var(--heat);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.5'%3E%3Cpolyline points='3 8 7 12 13 4'/%3E%3C/svg%3E");
background-position: center;
background-size: 10px 10px;
background-repeat: no-repeat;
border: 0;
}
/* 平台 logo 配色 */
.p-logo.dy { background: #000; }
.p-logo.tb { background: #ff6f00; }
.p-logo.tm { background: #ff0036; }
.p-logo.jd { background: #e1251b; }
.p-logo.pdd { background: #e02e24; }
.p-logo.xhs { background: #ff2741; }
.p-logo.ks { background: #ff4906; }
.p-logo.sph { background: #07c160; }
.p-logo.amz { background: #ff9900; }
.p-logo.al { background: #2c4af1; }
/* pill row */
.pill-row { display: flex; gap: 6px; }
.pill-row .opt {
flex: 1; height: 32px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
color: var(--black-alpha-72); font-size: 12.5px;
cursor: pointer; font-family: inherit;
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
}
.pill-row .opt:hover { color: var(--accent-black); }
.pill-row .opt.active { background: var(--heat-12); color: var(--heat); border-color: var(--heat-40); font-weight: 600; }
.pc-sub-h { font-size: 12px; color: var(--black-alpha-48); margin-bottom: 6px; font-family: var(--font-mono); letter-spacing: .02em; }
.pc-sub { margin-bottom: 12px; }
.pc-sub:last-child { margin-bottom: 0; }
/* CTA */
.pc-cta { margin-top: auto; padding-top: 14px; }
.pc-cta .btn-gen {
width: 100%; justify-content: center;
padding: 12px; font-size: 14px; font-weight: 600;
}
.pc-cta .btn-gen.disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }
.pc-cta-hint {
margin-top: 8px;
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .02em;
text-align: center; line-height: 1.5;
}
/* 右栏 预览 */
.pc-preview {
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
padding: 18px 22px;
display: flex; flex-direction: column;
overflow-y: auto;
}
.pc-pv-h {
display: flex; flex-direction: column; gap: 8px;
padding-bottom: 14px; margin-bottom: 16px;
border-bottom: 1px solid var(--border-faint);
}
.pc-pv-h .row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--black-alpha-72); }
.pc-pv-h .row .k { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
.pc-pv-h .row .v { color: var(--accent-black); font-weight: 500; }
.pv-platform-section { margin-bottom: 24px; }
.pv-platform-section:last-child { margin-bottom: 0; }
.pv-platform-section .ps-h {
display: flex; align-items: center; gap: 8px;
margin-bottom: 10px;
font-size: 13px; font-weight: 600; color: var(--accent-black);
}
.pv-platform-section .ps-h .ct {
margin-left: auto; font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .02em; font-weight: 400;
}
.pv-platform-section .ps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
}
.pv-platform-section .ps-grid .mp-result {
position: relative;
aspect-ratio: 1;
border-radius: var(--r-md);
overflow: hidden;
background: var(--surface);
border: 1px solid var(--border-faint);
display: flex; flex-direction: column;
}
.pv-platform-section .ps-grid .mp-result .mp-r-thumb { flex: 1; }
.pv-platform-section .ps-grid .mp-result .mp-r-act {
padding: 4px 6px;
background: rgba(255,255,255,.95);
}
.pv-platform-section .ps-grid .mp-result .add-lib {
width: 100%;
display: inline-flex; align-items: center; justify-content: center; gap: 4px;
background: var(--surface);
border: 1px solid var(--heat-40);
border-radius: var(--r-sm);
color: var(--heat);
height: 22px;
cursor: pointer; font-family: inherit;
font-size: 10.5px;
transition: background var(--t-base);
}
.pv-platform-section .ps-grid .mp-result .add-lib:hover { background: var(--heat-12); }
.pv-platform-section .ps-grid .mp-result .add-lib.added {
background: var(--accent-emerald-bg, #e6f4ec);
color: var(--accent-emerald, #1f8a51);
border-color: var(--accent-emerald-bd, #c4e3d1);
}
.pv-platform-section .ps-grid .mp-result .add-lib svg { width: 10px; height: 10px; }
.pc-pv-foot {
margin-top: 14px; padding-top: 12px;
border-top: 1px solid var(--border-faint);
font-family: var(--font-mono); font-size: 11.5px;
color: var(--black-alpha-56); letter-spacing: .02em;
line-height: 1.6;
}
.pc-pv-foot a { color: var(--heat); }
/* 空状态 */
.pc-pv-empty {
flex: 1;
display: grid; place-items: center;
color: var(--black-alpha-48);
font-size: 13px;
text-align: center;
}
.pc-pv-empty .mono {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); margin-top: 6px;
letter-spacing: .04em;
}
@media (max-width: 1100px) {
.pc-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div id="page">
<div class="page-head">
<div>
<h1>平台套图</h1>
<div class="sub"><span class="mono">// 选商品 + 选平台 → 生成 AI 套图</span> · 失败不扣费,采纳入库才计费</div>
</div>
<div class="actions">
<a class="btn" href="asset-factory.html">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
返回图片生成
</a>
</div>
</div>
<div class="pc-layout">
<!-- ===== 最左栏 · 任务历史 ===== -->
<div class="pc-tasks-panel" id="tasks-panel">
<div class="pc-tasks-h">
历史任务
<span class="ct" id="tasks-count">0</span>
<button class="new" type="button" id="tasks-new-btn" title="新建任务">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
</button>
</div>
<div class="pc-tasks-list" id="tasks-list"></div>
</div>
<!-- ===== 中栏 · 表单 ===== -->
<div class="pc-form">
<!-- ① 选择商品 (单选) -->
<div class="pc-step">
<div class="pc-step-h">
<span class="num">1</span>
<span class="title">选择商品</span>
</div>
<div class="prod-list" id="prod-list">
<!-- 已选商品 chip · JS 动态渲染 -->
</div>
<button class="prod-add" type="button" id="prod-add-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
去商品库添加
</button>
</div>
<!-- ② 选择平台 (单选, 10 个热门电商平台) -->
<div class="pc-step">
<div class="pc-step-h">
<span class="num">2</span>
<span class="title">选择平台</span>
</div>
<div class="platform-grid" id="platform-grid">
<div class="platform-card selected" data-id="dy" data-name="抖音电商">
<div class="p-check"></div>
<div class="p-logo dy"></div>
<div class="p-name">抖音电商</div>
</div>
<div class="platform-card" data-id="tb" data-name="淘宝">
<div class="p-check"></div>
<div class="p-logo tb"></div>
<div class="p-name">淘宝</div>
</div>
<div class="platform-card" data-id="tm" data-name="天猫">
<div class="p-check"></div>
<div class="p-logo tm"></div>
<div class="p-name">天猫</div>
</div>
<div class="platform-card" data-id="jd" data-name="京东">
<div class="p-check"></div>
<div class="p-logo jd"></div>
<div class="p-name">京东</div>
</div>
<div class="platform-card" data-id="pdd" data-name="拼多多">
<div class="p-check"></div>
<div class="p-logo pdd"></div>
<div class="p-name">拼多多</div>
</div>
<div class="platform-card" data-id="xhs" data-name="小红书">
<div class="p-check"></div>
<div class="p-logo xhs"></div>
<div class="p-name">小红书</div>
</div>
<div class="platform-card" data-id="ks" data-name="快手">
<div class="p-check"></div>
<div class="p-logo ks"></div>
<div class="p-name">快手</div>
</div>
<div class="platform-card" data-id="sph" data-name="视频号">
<div class="p-check"></div>
<div class="p-logo sph"></div>
<div class="p-name">视频号</div>
</div>
<div class="platform-card" data-id="amz" data-name="亚马逊">
<div class="p-check"></div>
<div class="p-logo amz">a</div>
<div class="p-name">亚马逊</div>
</div>
<div class="platform-card" data-id="al" data-name="1688">
<div class="p-check"></div>
<div class="p-logo al"></div>
<div class="p-name">1688</div>
</div>
</div>
</div>
<!-- ③ 生成设置 -->
<div class="pc-step">
<div class="pc-step-h">
<span class="num">3</span>
<span class="title">生成设置</span>
</div>
<div class="pc-sub">
<div class="pc-sub-h">// 生成数量</div>
<div class="pill-row" data-key="count">
<button type="button" class="opt active" data-val="4">4 张</button>
<button type="button" class="opt" data-val="8">8 张</button>
<button type="button" class="opt" data-val="12">12 张</button>
</div>
</div>
</div>
<!-- 底部 立即生成 -->
<div class="pc-cta">
<button class="btn btn-primary btn-gen" id="pc-go-btn" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3l14 9-14 9V3z"/></svg>
立即生成 (预估 <span id="cost-total">¥4.00</span>)
</button>
<div class="pc-cta-hint">// 满意后点 [入资产库] 才扣费 · 失败不扣</div>
</div>
</div>
<!-- ===== 右栏 · 预览 ===== -->
<div class="pc-preview" id="pc-preview">
<div class="pc-pv-h">
<div class="row"><span class="k">已选商品</span><span class="v" id="pv-prod">补水保湿精华液</span></div>
<div class="row"><span class="k">已选平台</span><span class="v" id="pv-platforms">抖音电商 / 淘宝</span></div>
<div class="row"><span class="k">生成</span><span class="v" id="pv-count">4</span></div>
</div>
<div id="pv-results">
<!-- 默认占位 -->
</div>
<div class="pc-pv-foot">
// 生成结果默认不入资产库,满意后点 [入资产库] 才扣费并保存
<br>// 任务进度可在 <a href="asset-factory.html">任务中心 →</a> 查看
</div>
</div>
</div>
</div>
<!-- ===== 编辑商品 drawer (在商品库内点编辑触发,prefilled) ===== -->
<div class="drawer-bg" id="pc-drawer-bg"></div>
<aside class="drawer pc-drawer" id="pc-drawer" role="dialog" aria-label="编辑商品" aria-hidden="true">
<div class="drawer-h">
<h3 id="pc-drawer-title">编辑商品</h3>
<button class="x" type="button" id="pc-drawer-close" aria-label="关闭">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
</button>
</div>
<div class="drawer-b">
<div class="pc-field">
<label class="pc-field-label">商品名称<span class="req">*</span></label>
<input class="input" id="pcf-name" placeholder="请输入商品名称(必填)" maxlength="100">
</div>
<div class="pc-field-row">
<div>
<label class="pc-field-label">品类<span class="req">*</span></label>
<select class="select" id="pcf-cat">
<option>美妆个护</option>
<option>服饰内衣</option>
<option>食品饮料</option>
<option>家居家电</option>
<option>数码 3C</option>
<option>个护清洁</option>
<option>运动户外</option>
<option>母婴亲子</option>
</select>
</div>
<div>
<label class="pc-field-label">目标人群<span style="color:var(--black-alpha-48);margin-left:2px">(选填)</span></label>
<input class="input" id="pcf-target" placeholder="例: 22-32 岁女性、敏感肌、办公室通勤">
</div>
</div>
<div class="pc-field">
<label class="pc-field-label">核心卖点<span class="req">*</span></label>
<ul class="pc-bullets" id="pcf-bullets">
<li class="add"><span class="num">+</span><input id="pcf-add-input" placeholder="添加新卖点 · 回车确认"></li>
</ul>
</div>
</div>
<div class="drawer-f">
<button class="btn" type="button" id="pc-cancel-btn">取消</button>
<button class="btn btn-primary" type="button" id="pc-save-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
保存修改
</button>
</div>
</aside>
<!-- ===== 商品库 全屏(无遮罩自适应,多选) ===== -->
<div class="pl-modal-bg" id="pl-modal-bg">
<div class="pl-modal">
<div class="pl-modal-h">
<h2>商品库</h2>
<span class="ct" id="pl-total-ct">// 共 7 个商品</span>
<div class="actions">
<button class="x" type="button" id="pl-close-btn" aria-label="关闭" style="width:32px;height:32px;display:grid;place-items:center;background:transparent;border:0;border-radius:var(--r-sm);cursor:pointer;color:var(--black-alpha-56)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
</button>
</div>
</div>
<div class="pl-modal-body">
<aside class="pl-side">
<div class="pl-side-h">分类</div>
<div class="pl-side-item active" data-cat="">全部 <span class="ct">7</span></div>
<div class="pl-side-item" data-cat="美妆个护">美妆个护 <span class="ct">2</span></div>
<div class="pl-side-item" data-cat="数码 3C">数码 3C <span class="ct">1</span></div>
<div class="pl-side-item" data-cat="食品饮料">食品饮料 <span class="ct">2</span></div>
<div class="pl-side-item" data-cat="家居家电">家居家电 <span class="ct">1</span></div>
<div class="pl-side-item" data-cat="运动户外">运动户外 <span class="ct">1</span></div>
</aside>
<div class="pl-main">
<div class="pl-toolbar">
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="pl-search-input" placeholder="搜索商品名">
</div>
<button class="btn-new" type="button" id="pl-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
</div>
<div class="pl-scroll">
<div class="pl-grid" id="pl-grid"></div>
</div>
</div>
</div>
<div class="pl-modal-f">
<div class="summary">// 已选 <b id="pl-sel-ct">0</b> 个商品</div>
<button class="btn" type="button" id="pl-cancel-btn">取消</button>
<button class="btn btn-primary" type="button" id="pl-confirm-btn">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l5 5L20 7"/></svg>
确认选择
</button>
</div>
</div>
</div>
<script src="assets/shell.js?v=202605211643"></script>
<script src="assets/new-product-drawer.js?v=202605211643"></script>
<script>
Shell.render({
active: 'asset-factory',
crumbs: [
{ label: '工作台', href: 'index.html' },
{ label: '图片生成', href: 'asset-factory.html' },
{ label: '平台套图' }
]
});
// ─── 商品库数据 (mock,与 products.html 7 个商品对齐) ───
const PRODUCTS = [
{ id: 'p1', name: '透真玻尿酸补水面膜', cat: '美妆个护', meta: '熬夜党 · 124 素材' },
{ id: 'p2', name: '南卡 Lite Pro 蓝牙耳机', cat: '数码 3C', meta: '通勤 · 96 素材' },
{ id: 'p3', name: '滋啦速食牛肉面 6 桶装', cat: '食品饮料', meta: '加班 · 96 素材' },
{ id: 'p4', name: '透真清透物理防晒霜', cat: '美妆个护', meta: 'SPF50 · 76 素材' },
{ id: 'p5', name: '三顿半同款冻干咖啡粉', cat: '食品饮料', meta: '提神 · 68 素材' },
{ id: 'p6', name: '小熊 4L 可视空气炸锅', cat: '家居家电', meta: '小户型 · 54 素材' },
{ id: 'p7', name: '露露同款裸感瑜伽裤', cat: '运动户外', meta: '健身房 · 42 素材' },
];
// State (单选)
const state = {
selectedProd: 'p1', // string | null
selectedPlatform: 'dy', // string | null
count: 4,
};
const UNIT_PRICE = 0.50;
// ─── 已选商品 渲染 (单选) ───
function renderSelectedProds() {
const list = document.getElementById('prod-list');
const id = state.selectedProd;
const p = id ? PRODUCTS.find(x => x.id === id) : null;
if (!p) {
list.innerHTML = '<div class="prod-empty">未选择商品<div class="mono">// 点击下方按钮去商品库选</div></div>';
document.getElementById('pv-prod').textContent = '未选择';
} else {
list.innerHTML = `
<div class="prod-row" data-id="${p.id}">
<div class="placeholder thumb"></div>
<div class="info"><div class="nm">${p.name}</div><div class="meta">${p.cat}</div></div>
<button class="x" type="button" data-rm="${p.id}" aria-label="移除">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
</button>
</div>
`;
list.querySelector('button.x[data-rm]').addEventListener('click', () => {
state.selectedProd = null;
renderSelectedProds();
});
document.getElementById('pv-prod').textContent = p.name;
}
updateCost();
renderPreviewSections();
}
// ─── 商品库全屏弹窗 (单选) ───
let _plDraft = null;
let _plCatFilter = '';
let _plQuery = '';
function renderProdLib() {
const grid = document.getElementById('pl-grid');
let list = PRODUCTS;
if (_plCatFilter) list = list.filter(p => p.cat === _plCatFilter);
if (_plQuery) list = list.filter(p => p.name.includes(_plQuery));
grid.innerHTML = list.map(p => `
<div class="pl-card${_plDraft === p.id ? ' selected' : ''}" data-id="${p.id}">
<div class="pl-card-actions">
<button class="pl-act" type="button" data-edit="${p.id}" title="编辑商品" aria-label="编辑">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
</button>
<button class="pl-act danger" type="button" data-del="${p.id}" title="删除商品" aria-label="删除">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>
</button>
</div>
<div class="pl-check"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></div>
<div class="placeholder pl-thumb"><span class="ph-frame">${p.name}</span></div>
<div class="pl-name">${p.name}</div>
<div class="pl-meta">${p.cat} · ${p.meta}</div>
</div>
`).join('');
grid.querySelectorAll('.pl-card').forEach(card => {
card.addEventListener('click', e => {
if (e.target.closest('[data-edit]') || e.target.closest('[data-del]')) return;
const id = card.dataset.id;
// 单选: 选中当前,取消其他
_plDraft = (_plDraft === id) ? null : id;
grid.querySelectorAll('.pl-card').forEach(c => c.classList.toggle('selected', c.dataset.id === _plDraft));
document.getElementById('pl-sel-ct').textContent = _plDraft ? 1 : 0;
});
});
grid.querySelectorAll('[data-edit]').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
openEditProductDrawer(btn.dataset.edit);
});
});
grid.querySelectorAll('[data-del]').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const id = btn.dataset.del;
const p = PRODUCTS.find(x => x.id === id);
if (!p) return;
if (!confirm('确认删除「' + p.name + '」?\n该操作不可撤销,商品下生成的素材记录也会一并清理。')) return;
const idx = PRODUCTS.findIndex(x => x.id === id);
if (idx >= 0) PRODUCTS.splice(idx, 1);
if (_plDraft === id) _plDraft = null;
if (state.selectedProd === id) state.selectedProd = null;
renderProdLib();
renderSelectedProds();
Shell.toast('已删除', p.name);
});
});
document.getElementById('pl-sel-ct').textContent = _plDraft ? 1 : 0;
}
// ─── 编辑商品 drawer (在商品库内 prefill 数据) ───
const PRODUCT_EXTRA = {
p1: { target: '熬夜党 · 25-35 岁女性 · 敏感肌', bullets: ['72h 长效补水', '官方授权正品', '通勤补妆神器'] },
p2: { target: '通勤党 · 18-30 岁 · 大学生 / 白领', bullets: ['主动降噪 35dB', '蓝牙 5.4 双设备', '32h 续航'] },
p3: { target: '加班党 · 独居青年 · 一人食场景', bullets: ['一杯水即可', '原切牛肉块充足', '6 桶大箱装'] },
p4: { target: '通勤防晒 · 油皮 / 敏感肌', bullets: ['SPF50+ PA++++', '物理防晒不刺激', '清透不假白'] },
p5: { target: '咖啡入门 · 早八党 · 加班族', bullets: ['冷热水即溶', '原产地豆精选', '24 颗精装'] },
p6: { target: '小户型 · 健康饮食 · 新手厨房', bullets: ['4L 大容量', '可视玻璃观察', '一键预设'] },
p7: { target: '健身房 · 通勤穿搭 · 18-32 岁女性', bullets: ['裸感面料', '高弹收腹', '亲肤透气'] },
};
let _editingProdId = null;
function openEditProductDrawer(id) {
const p = PRODUCTS.find(x => x.id === id);
if (!p) return;
_editingProdId = id;
document.getElementById('pc-drawer-title').textContent = '编辑商品 · ' + p.name;
document.getElementById('pcf-name').value = p.name;
document.getElementById('pcf-cat').value = p.cat;
const extra = PRODUCT_EXTRA[id] || { target: '', bullets: [] };
document.getElementById('pcf-target').value = extra.target || '';
const ul = document.getElementById('pcf-bullets');
ul.querySelectorAll('li:not(.add)').forEach(li => li.remove());
const addLi = ul.querySelector('.add');
(extra.bullets || []).forEach((b, i) => {
const li = document.createElement('li');
li.innerHTML = `
<span class="num">${i + 1}</span>
<input value="${b.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
});
document.getElementById('pcf-add-input').value = '';
document.getElementById('pc-drawer-bg').classList.add('show');
document.getElementById('pc-drawer').classList.add('show');
document.getElementById('pc-drawer').setAttribute('aria-hidden', 'false');
}
function renumberBullets() {
const ul = document.getElementById('pcf-bullets');
[...ul.querySelectorAll('li:not(.add) .num')].forEach((s, i) => { s.textContent = i + 1; });
}
function closeEditProductDrawer() {
document.getElementById('pc-drawer-bg').classList.remove('show');
document.getElementById('pc-drawer').classList.remove('show');
document.getElementById('pc-drawer').setAttribute('aria-hidden', 'true');
_editingProdId = null;
}
document.getElementById('pcf-add-input').addEventListener('keydown', e => {
if (e.key !== 'Enter') return;
const v = e.target.value.trim();
if (!v) return;
const ul = document.getElementById('pcf-bullets');
const addLi = ul.querySelector('.add');
const li = document.createElement('li');
li.innerHTML = `
<span class="num">0</span>
<input value="${v.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
e.target.value = '';
renumberBullets();
});
document.getElementById('pc-drawer-close').addEventListener('click', closeEditProductDrawer);
document.getElementById('pc-cancel-btn').addEventListener('click', closeEditProductDrawer);
document.getElementById('pc-drawer-bg').addEventListener('click', closeEditProductDrawer);
document.getElementById('pc-save-btn').addEventListener('click', () => {
if (!_editingProdId) return;
const newName = document.getElementById('pcf-name').value.trim();
const newCat = document.getElementById('pcf-cat').value;
const newTarget = document.getElementById('pcf-target').value.trim();
if (!newName) { Shell.toast('请填写商品名称'); return; }
const p = PRODUCTS.find(x => x.id === _editingProdId);
if (p) { p.name = newName; p.cat = newCat; }
const bullets = [...document.querySelectorAll('#pcf-bullets li:not(.add) input')].map(i => i.value.trim()).filter(Boolean);
PRODUCT_EXTRA[_editingProdId] = { target: newTarget, bullets };
Shell.toast('已保存', newName);
closeEditProductDrawer();
renderProdLib();
renderSelectedProds();
});
document.getElementById('prod-add-btn').addEventListener('click', () => {
_plDraft = state.selectedProd;
_plCatFilter = '';
_plQuery = '';
document.getElementById('pl-search-input').value = '';
document.querySelectorAll('.pl-side-item').forEach(x => x.classList.toggle('active', x.dataset.cat === ''));
renderProdLib();
document.getElementById('pl-modal-bg').classList.add('show');
});
document.getElementById('pl-close-btn').addEventListener('click', () => {
document.getElementById('pl-modal-bg').classList.remove('show');
});
document.getElementById('pl-cancel-btn').addEventListener('click', () => {
document.getElementById('pl-modal-bg').classList.remove('show');
});
document.getElementById('pl-confirm-btn').addEventListener('click', () => {
if (!_plDraft) { Shell.toast('请先选择商品', '只能选 1 个'); return; }
state.selectedProd = _plDraft;
document.getElementById('pl-modal-bg').classList.remove('show');
renderSelectedProds();
});
document.getElementById('pl-new-btn').addEventListener('click', () => {
if (!window.NewProductDrawer) { Shell.toast('Drawer 未加载'); return; }
window.NewProductDrawer.open({
onSave: function (p) {
const product = {
id: p.id,
name: p.name,
cat: p.cat,
meta: (p.target ? p.target.split(/[ ,、、]+/)[0] : '新建') + ' · ' + p.imgs + ' 张图',
};
PRODUCTS.unshift(product);
// 单选: 新建商品直接选中(覆盖原选)
_plDraft = product.id;
state.selectedProd = product.id;
_plCatFilter = '';
_plQuery = '';
const searchInput = document.getElementById('pl-search-input');
if (searchInput) searchInput.value = '';
renderProdLib();
renderSelectedProds();
Shell.toast('已加入商品库', '+ ' + product.name);
}
});
});
document.querySelectorAll('.pl-side-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.pl-side-item').forEach(x => x.classList.remove('active'));
item.classList.add('active');
_plCatFilter = item.dataset.cat;
renderProdLib();
});
});
document.getElementById('pl-search-input').addEventListener('input', e => {
_plQuery = e.target.value.trim();
renderProdLib();
});
// 平台单选
function updatePlatforms() {
const id = state.selectedPlatform;
const c = id ? document.querySelector('.platform-card[data-id="' + id + '"]') : null;
document.getElementById('pv-platforms').textContent = c ? c.dataset.name : '未选择';
updateCost();
renderPreviewSections();
}
function updateCost() {
const hasProd = !!state.selectedProd;
const hasPlat = !!state.selectedPlatform;
const total = (hasProd && hasPlat ? 1 : 0) * state.count * UNIT_PRICE;
document.getElementById('cost-total').textContent = '¥' + total.toFixed(2);
const btn = document.getElementById('pc-go-btn');
if (!hasProd || !hasPlat) btn.classList.add('disabled');
else btn.classList.remove('disabled');
document.getElementById('pv-count').textContent = state.count + ' 张';
}
document.querySelectorAll('.platform-card').forEach(card => {
card.addEventListener('click', () => {
const id = card.dataset.id;
state.selectedPlatform = (state.selectedPlatform === id) ? null : id;
document.querySelectorAll('.platform-card').forEach(c =>
c.classList.toggle('selected', c.dataset.id === state.selectedPlatform)
);
updatePlatforms();
});
});
// 数量
document.querySelectorAll('.pill-row').forEach(row => {
row.addEventListener('click', e => {
const btn = e.target.closest('.opt');
if (!btn) return;
row.querySelectorAll('.opt').forEach(o => o.classList.remove('active'));
btn.classList.add('active');
state.count = +btn.dataset.val;
updateCost();
renderPreviewSections();
});
});
// 渲染右侧预览 (单平台)
function renderPreviewSections() {
const container = document.getElementById('pv-results');
const id = state.selectedPlatform;
const c = id ? document.querySelector('.platform-card[data-id="' + id + '"]') : null;
if (!c) {
container.innerHTML = '<div class="pc-pv-empty">请先选择平台<div class="mono">// PICK A PLATFORM</div></div>';
return;
}
container.innerHTML = `
<div class="pv-platform-section">
<div class="ps-h">
<span>${c.dataset.name} 套图预览</span>
<span class="ct">${state.count} 张</span>
</div>
<div class="ps-grid">
${Array(state.count).fill(0).map(() => `
<div class="mp-result">
<div class="placeholder mp-r-thumb"><span class="ph-frame">${c.dataset.name}</span></div>
<div class="mp-r-act">
<button class="add-lib" type="button" data-cost="${UNIT_PRICE}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 5v14M5 12h14"/></svg>
入资产库 ¥${UNIT_PRICE.toFixed(2)}
</button>
</div>
</div>
`).join('')}
</div>
</div>
`;
container.querySelectorAll('.add-lib').forEach(b => {
b.addEventListener('click', e => {
e.stopPropagation();
if (b.classList.contains('added')) return;
b.classList.add('added');
b.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l5 5L20 7"/></svg> 已入库';
Shell.toast('已入库', '已扣 ¥' + (+b.dataset.cost).toFixed(2));
});
});
}
// 立即生成
document.getElementById('pc-go-btn').addEventListener('click', () => {
if (!state.selectedPlatform || !state.selectedProd) return;
Shell.toast('已提交任务', '可在 [任务中心] 查看进度');
renderPreviewSections();
});
// URL ?product=商品名 → 替换默认选中(从 products.html 跳过来时携带)
(function applyUrlProduct() {
const q = new URLSearchParams(location.search);
const productName = q.get('product');
if (!productName) return;
let p = PRODUCTS.find(x => x.name === productName);
if (!p) {
p = { id: 'np-' + Date.now().toString(36), name: productName, cat: '美妆个护', meta: '新建 · 待补充' };
PRODUCTS.unshift(p);
}
state.selectedProd = p.id;
})();
// 初始
renderSelectedProds();
updatePlatforms();
/* ============================================================
任务历史 (localStorage) · 仅在「立即生成」时创建任务
============================================================ */
(function () {
'use strict';
const TASK_TYPE = 'platform';
const KEY = 'fs-image-tasks-' + TASK_TYPE;
let tasks = [];
let currentId = null;
const PLAT_NAME = {};
document.querySelectorAll('#platform-grid .platform-card').forEach(c => {
PLAT_NAME[c.dataset.id] = c.dataset.name || c.dataset.id;
});
function load() {
try { return JSON.parse(localStorage.getItem(KEY) || '[]'); } catch (e) { return []; }
}
function save(arr) {
try { localStorage.setItem(KEY, JSON.stringify(arr)); } catch (e) {}
}
function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
function buildSnapshot() {
return {
selectedProd: state.selectedProd,
selectedPlatform: state.selectedPlatform,
count: state.count,
};
}
function autoName(snap) {
const prod = snap.selectedProd ? PRODUCTS.find(p => p.id === snap.selectedProd) : null;
const left = prod ? (prod.name.length > 10 ? prod.name.slice(0, 10) + '…' : prod.name) : '未选商品';
const right = snap.selectedPlatform ? (PLAT_NAME[snap.selectedPlatform] || snap.selectedPlatform) : '未选平台';
return `${left} × ${right}`;
}
function timeNow() {
const d = new Date();
return ('0'+(d.getMonth()+1)).slice(-2) + '.' + ('0'+d.getDate()).slice(-2) + ' ' + ('0'+d.getHours()).slice(-2) + ':' + ('0'+d.getMinutes()).slice(-2);
}
function renderTasksList() {
const root = document.getElementById('tasks-list');
document.getElementById('tasks-count').textContent = tasks.length;
if (!tasks.length) {
root.innerHTML = `<div class="pc-tasks-empty">还没有历史任务<span class="mono">// 调整商品/平台/参数后生成</span></div>`;
return;
}
const STATUS_LABEL = { gen: '生成中', ok: '已完成', err: '失败' };
const sorted = [...tasks].sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
root.innerHTML = sorted.map(t => `
<div class="pc-task-card${t.id === currentId ? ' active' : ''}" data-id="${t.id}">
<div class="nm">${escapeHtml(t.name)}</div>
<div class="meta">
<span class="pill-mini ${t.status}">${STATUS_LABEL[t.status] || t.status}</span>
<span>${escapeHtml(t.time || '')}</span>
</div>
<button class="x" type="button" data-rm="${t.id}" title="删除">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
</button>
</div>
`).join('');
root.querySelectorAll('.pc-task-card').forEach(card => {
card.addEventListener('click', e => {
if (e.target.closest('button.x')) return;
loadTaskIntoForm(card.dataset.id);
});
});
root.querySelectorAll('button.x[data-rm]').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const id = btn.dataset.rm;
const idx = tasks.findIndex(t => t.id === id);
if (idx < 0) return;
const name = tasks[idx].name;
tasks.splice(idx, 1);
save(tasks);
if (currentId === id) currentId = null;
renderTasksList();
Shell.toast('已删除任务', name);
});
});
}
function loadTaskIntoForm(id) {
const t = tasks.find(x => x.id === id);
if (!t) return;
state.selectedProd = t.snap.selectedProd || null;
state.selectedPlatform = t.snap.selectedPlatform || null;
state.count = t.snap.count;
document.querySelectorAll('.pill-row[data-key="count"] .opt').forEach(b => b.classList.toggle('active', +b.dataset.val === state.count));
document.querySelectorAll('#platform-grid .platform-card').forEach(c => c.classList.toggle('selected', c.dataset.id === state.selectedPlatform));
renderSelectedProds();
updatePlatforms();
currentId = id;
renderTasksList();
}
function newBlankTask() {
state.selectedProd = null;
state.selectedPlatform = null;
state.count = 4;
document.querySelectorAll('.pill-row[data-key="count"] .opt').forEach(b => b.classList.toggle('active', +b.dataset.val === 4));
document.querySelectorAll('#platform-grid .platform-card').forEach(c => c.classList.remove('selected'));
renderSelectedProds();
updatePlatforms();
currentId = null;
renderTasksList();
}
// 「立即生成」 → 才创建任务
document.getElementById('pc-go-btn').addEventListener('click', () => {
if (!state.selectedPlatform || !state.selectedProd) return;
const snap = buildSnapshot();
const name = autoName(snap);
const time = timeNow();
if (currentId) {
const t = tasks.find(x => x.id === currentId);
if (t) { t.snap = snap; t.name = name; t.status = 'gen'; t.time = time; }
} else {
currentId = 'task-' + Date.now();
tasks.push({ id: currentId, type: TASK_TYPE, name, snap, status: 'gen', time, createdAt: Date.now() });
}
save(tasks);
renderTasksList();
});
document.getElementById('tasks-new-btn').addEventListener('click', newBlankTask);
tasks = load();
renderTasksList();
})();
</script>
</body>
</html>