All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 7s
2311 lines
93 KiB
HTML
2311 lines
93 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>
|
||
.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: 0; }
|
||
|
||
.pc-layout {
|
||
flex: 1; min-height: 0;
|
||
display: grid;
|
||
grid-template-columns: 260px 1fr;
|
||
}
|
||
@media (max-width: 1280px) {
|
||
.pc-layout { grid-template-columns: 240px 1fr; }
|
||
}
|
||
|
||
/* ─── 主区: flat 双区 · 用 border 分隔 ─── */
|
||
.pc-main {
|
||
display: flex; flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
/* 主区头部 · toolbar 风格 (跟 image-optimize 一致) */
|
||
.pc-main-h {
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 12px 28px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
background: var(--surface);
|
||
}
|
||
.pc-main-h .cur-title {
|
||
display: flex; align-items: baseline; gap: 8px;
|
||
min-width: 0; max-width: 50%;
|
||
}
|
||
.pc-main-h .cur-title .crumb {
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .04em;
|
||
flex-shrink: 0;
|
||
}
|
||
.pc-main-h .cur-title .nm {
|
||
font-size: 15px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
letter-spacing: -.005em;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.pc-main-h .cur-title .nm.placeholder {
|
||
font-weight: 400; font-size: 13px;
|
||
color: var(--black-alpha-48);
|
||
}
|
||
.pc-main-h .spacer { flex: 1; }
|
||
.pc-main-h .search-btn {
|
||
width: 32px; height: 32px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-72);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
transition: border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-main-h .search-btn:hover { border-color: var(--heat-20); color: var(--heat); }
|
||
.pc-main-h .search-btn svg { width: 14px; height: 14px; }
|
||
.pc-main-h .tb-chip {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
height: 32px; padding: 0 10px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-size: 12.5px; color: var(--black-alpha-72);
|
||
font-family: inherit; cursor: pointer;
|
||
transition: border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-main-h .tb-chip:hover { border-color: var(--heat-20); color: var(--heat); }
|
||
.pc-main-h .tb-chip svg { width: 10px; height: 10px; opacity: .6; }
|
||
.pc-main-h .tb-chip.active {
|
||
background: var(--heat-12);
|
||
border-color: var(--heat-40);
|
||
color: var(--heat);
|
||
}
|
||
.pc-main-h .tb-chip.active svg { opacity: .8; }
|
||
|
||
/* search · 折叠图标态 + 展开输入框 */
|
||
.pc-main-h .tb-search-wrap { display: flex; align-items: center; }
|
||
.pc-main-h .tb-search-input {
|
||
width: 0; height: 32px;
|
||
padding: 0; margin: 0;
|
||
border: 1px solid transparent;
|
||
background: transparent;
|
||
border-radius: var(--r-sm);
|
||
font-size: 12.5px; color: var(--accent-black);
|
||
font-family: inherit; outline: none;
|
||
transition: width var(--t-base), padding var(--t-base), border-color var(--t-base), margin var(--t-base), background var(--t-base);
|
||
}
|
||
.pc-main-h .tb-search-wrap.expanded .tb-search-input {
|
||
width: 220px;
|
||
padding: 0 10px;
|
||
margin-left: 6px;
|
||
background: var(--surface);
|
||
border-color: var(--border-faint);
|
||
}
|
||
.pc-main-h .tb-search-wrap.expanded .tb-search-input:focus { border-color: var(--heat-40); }
|
||
.pc-main-h .tb-search-wrap.expanded .search-btn { border-color: var(--heat-40); color: var(--heat); }
|
||
|
||
/* dropdown · chip 下拉菜单 */
|
||
.pc-main-h .tb-menu-wrap { position: relative; }
|
||
.pc-main-h .tb-menu {
|
||
position: absolute;
|
||
top: calc(100% + 6px); right: 0;
|
||
min-width: 160px; max-width: 260px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
box-shadow: 0 8px 24px rgba(0,0,0,.08);
|
||
padding: 4px;
|
||
z-index: 50;
|
||
display: none;
|
||
max-height: 320px; overflow-y: auto;
|
||
}
|
||
.pc-main-h .tb-menu-wrap.open .tb-menu { display: block; }
|
||
.pc-main-h .tb-menu-item {
|
||
display: flex; align-items: center; gap: 8px;
|
||
width: 100%; padding: 7px 10px;
|
||
background: transparent; border: 0;
|
||
border-radius: 4px;
|
||
font-size: 12.5px; color: var(--black-alpha-72);
|
||
font-family: inherit; cursor: pointer; text-align: left;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.pc-main-h .tb-menu-item:hover { background: var(--background-lighter); color: var(--accent-black); }
|
||
.pc-main-h .tb-menu-item.active {
|
||
background: var(--heat-12); color: var(--heat); font-weight: 500;
|
||
}
|
||
.pc-main-h .tb-menu-empty {
|
||
padding: 10px;
|
||
font-size: 11.5px; color: var(--black-alpha-48);
|
||
font-family: var(--font-mono); letter-spacing: .02em;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 批次被筛选隐藏 */
|
||
.pv-platform-section[data-hidden="1"] { display: none !important; }
|
||
.pc-main-body {
|
||
flex: 1; min-height: 0;
|
||
display: grid;
|
||
grid-template-columns: 320px 1fr;
|
||
}
|
||
@media (max-width: 1280px) {
|
||
.pc-main-body { grid-template-columns: 300px 1fr; }
|
||
}
|
||
.pc-main-body > .pc-form {
|
||
border: 0; border-radius: 0;
|
||
border-right: 1px solid var(--border-faint);
|
||
background: var(--surface);
|
||
}
|
||
.pc-main-body > .pc-preview {
|
||
border: 0; border-radius: 0;
|
||
background: var(--background-base);
|
||
}
|
||
|
||
/* ─── 商品空间 (最左 · 单选 · 当前商品决定结果区批次) ─── */
|
||
.pc-prod-space {
|
||
background: var(--surface);
|
||
border-right: 1px solid var(--border-faint);
|
||
display: flex; flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
/* 顶部工具栏: 返回 + 折叠 (跟 image-optimize 视觉一致) */
|
||
.pc-side-top {
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; gap: 8px;
|
||
padding: 14px 14px 10px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
.pc-side-top .back-pill {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
height: 28px; padding: 0 12px 0 8px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-pill);
|
||
color: var(--accent-black);
|
||
font-size: 12.5px; font-weight: 500;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-side-top .back-pill:hover {
|
||
background: var(--heat-12);
|
||
border-color: var(--heat-20);
|
||
color: var(--heat);
|
||
}
|
||
.pc-side-top .back-pill svg { width: 14px; height: 14px; }
|
||
.pc-side-top .fold {
|
||
margin-left: auto;
|
||
width: 26px; height: 26px;
|
||
background: transparent; border: 0; border-radius: var(--r-sm);
|
||
display: grid; place-items: center;
|
||
color: var(--black-alpha-48); cursor: pointer;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-side-top .fold:hover { background: var(--black-alpha-4); color: var(--accent-black); }
|
||
.pc-side-top .fold svg { width: 14px; height: 14px; }
|
||
.pc-ps-h {
|
||
flex-shrink: 0;
|
||
padding: 12px 14px 10px;
|
||
}
|
||
/* 商品列表 头部 (// 商品空间 + 新建商品 主 CTA · 右上显眼橙色) */
|
||
.pc-list-h {
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; gap: 8px;
|
||
padding: 4px 14px 10px;
|
||
}
|
||
.pc-list-h .mono {
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .06em;
|
||
text-transform: uppercase;
|
||
}
|
||
.pc-list-h .new-prod {
|
||
margin-left: auto;
|
||
height: 28px; padding: 0 12px 0 10px;
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
background: var(--heat); color: #fff;
|
||
border: 1px solid var(--heat);
|
||
border-radius: var(--r-sm);
|
||
font-size: 12px; font-weight: 600;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
box-shadow:
|
||
inset 0 -2px 4px rgba(250, 93, 25, 0.20),
|
||
0 1px 1px rgba(250, 93, 25, 0.12),
|
||
0 2px 4px rgba(250, 93, 25, 0.10);
|
||
transition: filter var(--t-base), transform var(--t-fast), box-shadow var(--t-base);
|
||
}
|
||
.pc-list-h .new-prod:hover {
|
||
filter: brightness(.96);
|
||
box-shadow:
|
||
inset 0 -2px 4px rgba(250, 93, 25, 0.20),
|
||
0 1px 1px rgba(250, 93, 25, 0.16),
|
||
0 4px 8px rgba(250, 93, 25, 0.20);
|
||
}
|
||
.pc-list-h .new-prod:active { transform: scale(.98); }
|
||
.pc-list-h .new-prod svg { width: 12px; height: 12px; }
|
||
.pc-ps-search {
|
||
position: relative;
|
||
height: 32px;
|
||
}
|
||
.pc-ps-search svg {
|
||
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
||
width: 13px; height: 13px;
|
||
color: var(--black-alpha-48);
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
}
|
||
.pc-ps-search input {
|
||
width: 100%; height: 100%;
|
||
padding: 0 10px 0 30px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-size: 12.5px; color: var(--accent-black);
|
||
font-family: inherit;
|
||
outline: none;
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
}
|
||
.pc-ps-search input:focus { border-color: var(--heat-40); background: var(--surface); }
|
||
.pc-ps-search input::placeholder { color: var(--black-alpha-48); }
|
||
.pc-ps-list {
|
||
flex: 1; min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 4px 10px 10px;
|
||
display: flex; flex-direction: column; gap: 4px;
|
||
}
|
||
.pc-ps-empty {
|
||
padding: 24px 14px;
|
||
text-align: center;
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
line-height: 1.7;
|
||
}
|
||
.pc-prod-item {
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 8px;
|
||
border: 1px solid transparent;
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
transition: background var(--t-base), border-color var(--t-base);
|
||
}
|
||
.pc-prod-item:hover { background: var(--black-alpha-4); }
|
||
.pc-prod-item.active { background: var(--heat-12); border-color: var(--heat-20); }
|
||
.pc-prod-item .thumb {
|
||
flex-shrink: 0;
|
||
width: 36px; height: 36px;
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden;
|
||
}
|
||
.pc-prod-item.active .thumb { border-color: var(--heat); }
|
||
.pc-prod-item .body { flex: 1; min-width: 0; }
|
||
.pc-prod-item .nm {
|
||
font-size: 12.5px;
|
||
color: var(--accent-black); font-weight: 500;
|
||
line-height: 1.3;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.pc-prod-item.active .nm { color: var(--heat); font-weight: 600; }
|
||
.pc-prod-item .sub {
|
||
margin-top: 2px;
|
||
font-family: var(--font-mono); font-size: 10px;
|
||
color: var(--black-alpha-48); letter-spacing: .02em;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.pc-ps-all {
|
||
flex-shrink: 0;
|
||
margin: 0 10px 12px;
|
||
padding: 9px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-72);
|
||
font-size: 12px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
display: flex; align-items: center; gap: 8px;
|
||
transition: border-color var(--t-base), color var(--t-base), background var(--t-base);
|
||
}
|
||
.pc-ps-all:hover { border-color: var(--heat); color: var(--heat); background: var(--heat-12); }
|
||
.pc-ps-all .ct {
|
||
margin-left: auto;
|
||
color: var(--black-alpha-48);
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
}
|
||
.pc-ps-all:hover .ct { color: var(--heat); }
|
||
.pc-ps-all svg { width: 12px; height: 12px; }
|
||
/* 左栏 表单 */
|
||
.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-row .swap {
|
||
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 .swap:hover { background: var(--heat-12); color: var(--heat); }
|
||
.prod-row .swap svg { width: 13px; height: 13px; }
|
||
.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; }
|
||
.prod-add[hidden] { display: none; }
|
||
|
||
/* 商品库全屏弹窗 */
|
||
.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; }
|
||
/* 商品图片 grid (对齐 product-detail .ov-images-grid) */
|
||
.pc-imgs {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 8px;
|
||
}
|
||
.pc-imgs .thumb {
|
||
aspect-ratio: 1 / 1;
|
||
border-radius: var(--r-sm);
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.pc-imgs .thumb .ph-frame {
|
||
position: absolute; inset: 0;
|
||
display: grid; place-items: center;
|
||
font-family: var(--font-mono); font-size: 10px;
|
||
color: var(--black-alpha-32);
|
||
background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(0,0,0,.03) 6px 7px);
|
||
}
|
||
.pc-imgs .thumb .rm {
|
||
position: absolute; top: 4px; right: 4px;
|
||
width: 18px; height: 18px;
|
||
background: rgba(0,0,0,.5);
|
||
color: #fff; border: 0;
|
||
border-radius: var(--r-sm);
|
||
display: grid; place-items: center;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
transition: opacity var(--t-base);
|
||
}
|
||
.pc-imgs .thumb:hover .rm { opacity: 1; }
|
||
.pc-imgs .thumb .rm svg { width: 10px; height: 10px; }
|
||
.pc-imgs .img-upload {
|
||
aspect-ratio: 1 / 1;
|
||
border-radius: var(--r-sm);
|
||
background: var(--heat-12);
|
||
border: 1.5px dashed var(--heat-40);
|
||
display: grid; place-items: center;
|
||
color: var(--heat);
|
||
cursor: pointer;
|
||
transition: background var(--t-base), border-color var(--t-base);
|
||
}
|
||
.pc-imgs .img-upload:hover { background: var(--heat-20); border-color: var(--heat); }
|
||
.pc-imgs .img-upload svg { width: 18px; height: 18px; }
|
||
.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;
|
||
}
|
||
/* prompt-style summary 卡片 (引号 icon + 灰底 + 右上 meta) */
|
||
.pc-pv-h {
|
||
position: relative;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 14px 18px 14px 44px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.pc-pv-h .quote-icon {
|
||
position: absolute;
|
||
top: 13px; left: 16px;
|
||
width: 18px; height: 18px;
|
||
color: var(--black-alpha-24);
|
||
}
|
||
.pc-pv-h .pv-meta {
|
||
float: right;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
line-height: 1.5;
|
||
}
|
||
.pc-pv-h .pv-meta b { color: var(--accent-black); font-weight: 600; }
|
||
.pc-pv-h .pv-line {
|
||
font-size: 13px;
|
||
color: var(--accent-black);
|
||
line-height: 1.6;
|
||
display: flex; align-items: center;
|
||
}
|
||
.pc-pv-h .pv-line .k {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
margin-right: 8px;
|
||
min-width: 36px;
|
||
}
|
||
.pc-pv-h .pv-line .v { font-weight: 500; }
|
||
.pc-pv-h .pv-line .swap {
|
||
margin-left: 10px;
|
||
font-size: 11.5px; color: var(--heat);
|
||
cursor: pointer;
|
||
}
|
||
.pc-pv-h .pv-line .swap:hover { text-decoration: underline; }
|
||
|
||
.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-h .batch-lab {
|
||
font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
|
||
padding: 2px 8px; border-radius: var(--r-pill); letter-spacing: .04em;
|
||
}
|
||
.pv-platform-section .ps-h .batch-lab.gen { background: var(--background-lighter); color: var(--black-alpha-56); }
|
||
.pv-platform-section .ps-h .batch-lab.rerun { background: var(--heat-12); color: var(--heat); }
|
||
.pv-platform-section .ps-h .sep { color: var(--black-alpha-32); font-weight: 400; }
|
||
.pv-platform-section .ps-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 12px;
|
||
}
|
||
@media (max-width: 1400px) {
|
||
.pv-platform-section .ps-grid { gap: 10px; }
|
||
}
|
||
/* 结果卡片 · 与图片创作 .io-cell 视觉一致 */
|
||
.pv-platform-section .ps-grid .mp-result {
|
||
position: relative;
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-md);
|
||
overflow: hidden;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
cursor: pointer;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result:hover { border-color: var(--black-alpha-32); }
|
||
.pv-platform-section .ps-grid .mp-result .mp-r-thumb { position: absolute; inset: 0; }
|
||
.pv-platform-section .ps-grid .mp-result .mp-r-thumb .ph-frame {
|
||
position: absolute; inset: 0;
|
||
display: grid; place-items: center;
|
||
font-family: var(--font-mono); font-size: 11px;
|
||
color: var(--black-alpha-32); letter-spacing: .02em;
|
||
background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(0,0,0,.03) 6px 7px);
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result.gen .mp-r-thumb .ph-frame { animation: pc-pulse 1.4s ease-in-out infinite; }
|
||
@keyframes pc-pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: .55; }
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result.err { border-color: var(--accent-crimson, #c43d3d); }
|
||
.pv-platform-section .ps-grid .mp-result.err .mp-r-thumb .ph-frame {
|
||
color: var(--accent-crimson, #c43d3d);
|
||
background: rgba(196, 61, 61, .05);
|
||
}
|
||
/* 右上 hover 操作组 · 同 spec §4.18 .gen-image-actions */
|
||
.pv-platform-section .ps-grid .mp-result .cell-ops {
|
||
position: absolute; top: 8px; right: 8px;
|
||
display: flex; gap: 2px;
|
||
padding: 2px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
box-shadow: 0 2px 8px rgba(0,0,0,.08);
|
||
opacity: 0;
|
||
transition: opacity var(--t-base);
|
||
z-index: 2;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result:hover .cell-ops { opacity: 1; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-ops button {
|
||
width: 28px; height: 28px;
|
||
background: transparent;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
color: var(--black-alpha-56);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-ops button:hover {
|
||
background: var(--black-alpha-4);
|
||
color: var(--accent-black);
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-ops button svg { width: 14px; height: 14px; }
|
||
.pv-platform-section .ps-grid .mp-result.adopted .cell-ops .r-check { display: none; }
|
||
/* 中央反馈 toast · 同 spec §4.18 .gen-image-feedback */
|
||
.pv-platform-section .ps-grid .mp-result .cell-feedback {
|
||
position: absolute; inset: 0;
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
|
||
background: rgba(38, 38, 38, .88);
|
||
color: var(--accent-white);
|
||
border-radius: var(--r-md);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity .2s var(--t-base, ease);
|
||
z-index: 3;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result.show-feedback .cell-feedback { opacity: 1; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-feedback svg { width: 20px; height: 20px; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-feedback span { font-size: 12.5px; font-weight: 500; letter-spacing: .02em; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-wrap { position: relative; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-menu {
|
||
position: absolute; top: calc(100% + 4px); right: 0;
|
||
min-width: 132px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
box-shadow: 0 6px 24px rgba(0,0,0,.10);
|
||
padding: 4px;
|
||
display: none;
|
||
z-index: 12;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-wrap.open .cell-more-menu { display: block; }
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-menu button {
|
||
width: 100%;
|
||
display: inline-flex !important; align-items: center; gap: 8px;
|
||
padding: 7px 10px !important;
|
||
background: transparent !important;
|
||
border: 0 !important; border-radius: var(--r-sm) !important;
|
||
font-size: 12.5px;
|
||
color: var(--accent-black) !important;
|
||
font-family: inherit;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
backdrop-filter: none !important;
|
||
height: auto !important;
|
||
justify-content: flex-start !important;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-menu button:hover {
|
||
background: var(--background-lighter) !important;
|
||
color: var(--heat) !important;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-menu button.danger:hover {
|
||
color: var(--accent-crimson) !important;
|
||
background: var(--crimson-bg, #fdebea) !important;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result .cell-more-menu button svg { width: 13px !important; height: 13px !important; }
|
||
/* 已采用角标 */
|
||
.pv-platform-section .ps-grid .mp-result .adopt-badge {
|
||
position: absolute; top: 6px; left: 6px;
|
||
background: var(--accent-forest, #42c366);
|
||
color: #fff;
|
||
font-family: var(--font-mono); font-size: 10px; font-weight: 600;
|
||
letter-spacing: .04em;
|
||
padding: 1px 6px;
|
||
border-radius: var(--r-pill);
|
||
z-index: 3;
|
||
display: none;
|
||
}
|
||
.pv-platform-section .ps-grid .mp-result.adopted .adopt-badge { display: inline-block; }
|
||
|
||
/* 每批次下方的批量操作 (胶囊按钮组 · 左对齐) */
|
||
.pc-pv-batch {
|
||
margin-top: 10px;
|
||
display: flex; gap: 10px; align-items: center; justify-content: flex-start;
|
||
}
|
||
.pc-pv-batch[hidden] { display: none; }
|
||
.pv-platform-section .pc-pv-batch.batch-foot { margin-top: 10px; }
|
||
|
||
/* 预览区空态 (新任务且未生成) */
|
||
.pc-pv-empty-state {
|
||
flex: 1;
|
||
display: flex; flex-direction: column;
|
||
align-items: center; justify-content: center;
|
||
text-align: center;
|
||
padding: 40px 24px;
|
||
gap: 6px;
|
||
}
|
||
.pc-pv-empty-state[hidden] { display: none; }
|
||
.pc-pv-empty-state .mono {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .06em;
|
||
margin-bottom: 4px;
|
||
}
|
||
.pc-pv-empty-state .title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.pc-pv-empty-state .hint {
|
||
font-size: 12.5px;
|
||
color: var(--black-alpha-48);
|
||
line-height: 1.6;
|
||
max-width: 320px;
|
||
}
|
||
.pc-pv-empty-state .hint b { color: var(--heat); font-weight: 600; }
|
||
/* 预览区 hidden 时收起所有内容元素 */
|
||
#pv-summary[hidden], #pv-results[hidden], #pv-foot[hidden] { display: none; }
|
||
.pc-pv-batch .summary {
|
||
margin-right: auto;
|
||
font-family: var(--font-mono); font-size: 11px;
|
||
color: var(--black-alpha-48); letter-spacing: .02em;
|
||
}
|
||
.pc-pv-batch .summary b { color: var(--heat); font-weight: 700; }
|
||
/* 底栏按钮 · 同 spec §4.18 + image-optimize .io-msg-ops button */
|
||
.pc-pv-batch .pill-btn {
|
||
height: 30px;
|
||
padding: 0 12px;
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
color: var(--accent-black);
|
||
font-family: inherit; font-size: 12.5px;
|
||
cursor: pointer;
|
||
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.pc-pv-batch .pill-btn:hover {
|
||
border-color: var(--heat-20); color: var(--heat); background: var(--heat-12);
|
||
}
|
||
.pc-pv-batch .pill-btn svg { width: 13px; height: 13px; }
|
||
.pc-pv-batch .pill-btn.icon { width: 30px; padding: 0; justify-content: center; }
|
||
/* 批次更多气泡 */
|
||
.pc-pv-batch .batch-more-wrap { position: relative; display: inline-flex; }
|
||
.pc-pv-batch .batch-more-menu {
|
||
position: absolute; bottom: calc(100% + 6px); right: 0;
|
||
min-width: 168px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
box-shadow: 0 6px 24px rgba(0,0,0,.10);
|
||
padding: 4px;
|
||
display: none;
|
||
z-index: 12;
|
||
}
|
||
.pc-pv-batch .batch-more-wrap.open .batch-more-menu { display: block; }
|
||
.pc-pv-batch .batch-more-menu button {
|
||
width: 100%;
|
||
display: inline-flex !important; align-items: center; gap: 8px;
|
||
height: auto !important;
|
||
padding: 7px 10px !important;
|
||
background: transparent !important;
|
||
border: 0 !important; border-radius: var(--r-sm) !important;
|
||
font-size: 12.5px;
|
||
color: var(--accent-black) !important;
|
||
text-align: left;
|
||
justify-content: flex-start !important;
|
||
cursor: pointer; font-family: inherit;
|
||
}
|
||
.pc-pv-batch .batch-more-menu button:hover {
|
||
background: var(--background-lighter) !important;
|
||
color: var(--heat) !important;
|
||
}
|
||
.pc-pv-batch .batch-more-menu button.danger:hover {
|
||
color: var(--accent-crimson) !important;
|
||
background: var(--crimson-bg, #fdebea) !important;
|
||
}
|
||
.pc-pv-batch .batch-more-menu button svg { width: 13px !important; height: 13px !important; flex-shrink: 0; }
|
||
|
||
.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="pc-layout">
|
||
|
||
<!-- ===== 最左栏 · 商品空间 (单选 · 当前商品决定结果区批次) ===== -->
|
||
<aside class="pc-prod-space" id="prod-space">
|
||
<!-- 顶部 · 返回 + 折叠 (跟图片创作风格一致) -->
|
||
<div class="pc-side-top">
|
||
<button class="back-pill" type="button" onclick="history.length > 1 ? history.back() : location.href='asset-factory.html'" title="返回">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
||
<span>返回</span>
|
||
</button>
|
||
<button class="fold" type="button" title="折叠侧栏" style="margin-left:auto">
|
||
<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="M9 3v18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="pc-ps-h">
|
||
<div class="pc-ps-search">
|
||
<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>
|
||
<input type="text" id="ps-search-input" placeholder="搜索商品 / 分类">
|
||
</div>
|
||
</div>
|
||
<!-- 商品列表 标题行 · 右上显眼新建按钮 -->
|
||
<div class="pc-list-h">
|
||
<span class="mono">// 商品空间</span>
|
||
<button class="new-prod" type="button" id="ps-new-btn" title="新建商品">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
||
<span>新建商品</span>
|
||
</button>
|
||
</div>
|
||
<div class="pc-ps-list" id="ps-list"></div>
|
||
<button class="pc-ps-all" type="button" id="ps-all-btn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
|
||
<span>全部商品</span>
|
||
<span class="ct" id="ps-all-ct">0 个</span>
|
||
<svg style="margin-left:0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||
</button>
|
||
</aside>
|
||
|
||
<!-- ===== 主区 · 头部 + 参数/结果 双栏 ===== -->
|
||
<section class="pc-main">
|
||
|
||
<!-- 主区顶部 · toolbar (商品标题 + 搜索 + 筛选 · 跟图片创作一致) -->
|
||
<div class="pc-main-h">
|
||
<div class="cur-title">
|
||
<span class="crumb">// 商品空间</span>
|
||
<span class="nm placeholder" id="cur-prod-nm">未选择 · 请在左侧商品空间选一个</span>
|
||
</div>
|
||
<span class="spacer"></span>
|
||
<div class="tb-search-wrap" id="pc-search-wrap">
|
||
<button class="search-btn" type="button" title="搜索批次/平台" id="pc-search-toggle">
|
||
<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>
|
||
<input type="text" class="tb-search-input" id="pc-search-input" placeholder="搜索批次/平台…" autocomplete="off">
|
||
</div>
|
||
<div class="tb-menu-wrap" data-filter="time">
|
||
<button class="tb-chip" type="button" id="pc-chip-time">
|
||
<span class="lbl">时间</span>
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
<div class="tb-menu" id="pc-menu-time" role="listbox" aria-labelledby="pc-chip-time">
|
||
<button class="tb-menu-item active" type="button" data-val="all">全部时间</button>
|
||
<button class="tb-menu-item" type="button" data-val="today">今天</button>
|
||
<button class="tb-menu-item" type="button" data-val="1h">1 小时内</button>
|
||
<button class="tb-menu-item" type="button" data-val="10min">10 分钟内</button>
|
||
</div>
|
||
</div>
|
||
<div class="tb-menu-wrap" data-filter="platform">
|
||
<button class="tb-chip" type="button" id="pc-chip-platform">
|
||
<span class="lbl">平台</span>
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
<div class="tb-menu" id="pc-menu-platform" role="listbox" aria-labelledby="pc-chip-platform">
|
||
<button class="tb-menu-item active" type="button" data-val="all">全部平台</button>
|
||
<div class="tb-menu-empty">暂无批次,生成后可按平台筛选</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pc-main-body">
|
||
|
||
<!-- 左 · 参数 -->
|
||
<div class="pc-form">
|
||
|
||
<!-- ① 选择平台 (单选, 10 个热门电商平台) -->
|
||
<div class="pc-step">
|
||
<div class="pc-step-h">
|
||
<span class="num">1</span>
|
||
<span class="title">选择平台</span>
|
||
</div>
|
||
<div class="platform-grid" id="platform-grid">
|
||
<div class="platform-card" 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">2</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-empty-state" id="pv-empty">
|
||
<div class="mono">// EMPTY STATE</div>
|
||
<div class="title">还没有生成结果</div>
|
||
<div class="hint">先选商品、选平台,点击 <b>立即生成</b> 后,效果图会出现在这里</div>
|
||
</div>
|
||
|
||
<div class="pc-pv-h" id="pv-summary">
|
||
<svg class="quote-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 7c-2.76 0-5 2.24-5 5 0 1.84 1 3.45 2.48 4.32C5.99 17.36 4.99 18 4 18v2c3.31 0 6-2.69 6-6V8c0-.55-.45-1-1-1zm9 0c-2.76 0-5 2.24-5 5 0 1.84 1 3.45 2.48 4.32-.49 1.04-1.49 1.68-2.48 1.68v2c3.31 0 6-2.69 6-6V8c0-.55-.45-1-1-1z"/></svg>
|
||
<div class="pv-meta"><b id="pv-count">4 张</b></div>
|
||
<div class="pv-line"><span class="k">商品</span><span class="v" id="pv-prod">未选择</span></div>
|
||
<div class="pv-line"><span class="k">平台</span><span class="v" id="pv-platforms">未选择</span></div>
|
||
</div>
|
||
<div id="pv-results">
|
||
<!-- 默认占位 -->
|
||
</div>
|
||
|
||
<div class="pc-pv-foot" id="pv-foot">
|
||
// 采用即扣费并入对应商品的 <a href="products.html">AI 素材库 →</a>;未采用的图不扣费、不保存
|
||
<br>// 切换左侧商品空间 · 查看其他商品的批次记录
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /.pc-main-body -->
|
||
</section><!-- /.pc-main -->
|
||
|
||
</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 style="color:var(--black-alpha-48);margin-left:2px">(<span id="pcf-imgs-ct">6</span>)</span></label>
|
||
<div class="pc-imgs" id="pcf-imgs"></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.5"><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.5" 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/icons.js?v=2026052608"></script>
|
||
<script src="assets/shell.js?v=2026052607"></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: null, // string | null
|
||
selectedPlatform: null, // string | null
|
||
count: 4,
|
||
};
|
||
const UNIT_PRICE = 0.50;
|
||
|
||
// ─── 商品空间 (左侧栏) 渲染 ───
|
||
let _psQuery = '';
|
||
function renderProdSpace() {
|
||
const listEl = document.getElementById('ps-list');
|
||
const ctEl = document.getElementById('ps-count');
|
||
const allCtEl = document.getElementById('ps-all-ct');
|
||
if (!listEl) return;
|
||
if (ctEl) ctEl.textContent = PRODUCTS.length;
|
||
if (allCtEl) allCtEl.textContent = PRODUCTS.length + ' 个';
|
||
const q = _psQuery.trim();
|
||
const filtered = q
|
||
? PRODUCTS.filter(p => p.name.includes(q) || p.cat.includes(q))
|
||
: PRODUCTS;
|
||
if (!filtered.length) {
|
||
listEl.innerHTML = `<div class="pc-ps-empty">// NO MATCH<br>试试其他关键词</div>`;
|
||
return;
|
||
}
|
||
listEl.innerHTML = filtered.map(p => `
|
||
<div class="pc-prod-item${state.selectedProd === p.id ? ' active' : ''}" data-id="${p.id}">
|
||
<div class="placeholder thumb"></div>
|
||
<div class="body">
|
||
<div class="nm">${p.name}</div>
|
||
<div class="sub">// ${p.cat}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
listEl.querySelectorAll('.pc-prod-item').forEach(el => {
|
||
el.addEventListener('click', () => selectProduct(el.dataset.id));
|
||
});
|
||
}
|
||
|
||
// 选中商品 (sidebar 单选 · 同步更新表单/预览/Cost)
|
||
function selectProduct(id) {
|
||
state.selectedProd = id;
|
||
document.querySelectorAll('.pc-prod-item').forEach(el => {
|
||
el.classList.toggle('active', el.dataset.id === id);
|
||
});
|
||
updateCurProdHeader();
|
||
if (typeof renderBatchesForCurrentProd === 'function') renderBatchesForCurrentProd();
|
||
const p = PRODUCTS.find(x => x.id === id);
|
||
document.getElementById('pv-prod').textContent = p ? p.name : '未选择';
|
||
updateCost();
|
||
renderPreviewSections();
|
||
}
|
||
|
||
// 当前商品 header strip
|
||
function updateCurProdHeader() {
|
||
const nmEl = document.getElementById('cur-prod-nm');
|
||
const statsEl = document.getElementById('cur-prod-stats');
|
||
const batchesEl = document.getElementById('cur-prod-batches');
|
||
if (!nmEl) return;
|
||
const p = state.selectedProd ? PRODUCTS.find(x => x.id === state.selectedProd) : null;
|
||
if (!p) {
|
||
nmEl.textContent = '未选择 · 请在左侧商品空间选一个';
|
||
nmEl.classList.add('placeholder');
|
||
if (statsEl) statsEl.hidden = true;
|
||
} else {
|
||
nmEl.textContent = p.name;
|
||
nmEl.classList.remove('placeholder');
|
||
const ct = (window._countBatchesForProd ? window._countBatchesForProd(p.id) : 0);
|
||
if (batchesEl) batchesEl.textContent = ct;
|
||
if (statsEl) statsEl.hidden = false;
|
||
}
|
||
}
|
||
|
||
// 保留旧函数名 alias (兼容旧 call site)
|
||
function renderSelectedProds() {
|
||
renderProdSpace();
|
||
updateCurProdHeader();
|
||
const p = state.selectedProd ? PRODUCTS.find(x => x.id === state.selectedProd) : null;
|
||
document.getElementById('pv-prod').textContent = p ? 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.5" 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.5" 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 长效补水', '官方授权正品', '通勤补妆神器'], imgs: 6 },
|
||
p2: { target: '通勤党 · 18-30 岁 · 大学生 / 白领', bullets: ['主动降噪 35dB', '蓝牙 5.4 双设备', '32h 续航'], imgs: 6 },
|
||
p3: { target: '加班党 · 独居青年 · 一人食场景', bullets: ['一杯水即可', '原切牛肉块充足', '6 桶大箱装'], imgs: 6 },
|
||
p4: { target: '通勤防晒 · 油皮 / 敏感肌', bullets: ['SPF50+ PA++++', '物理防晒不刺激', '清透不假白'], imgs: 6 },
|
||
p5: { target: '咖啡入门 · 早八党 · 加班族', bullets: ['冷热水即溶', '原产地豆精选', '24 颗精装'], imgs: 6 },
|
||
p6: { target: '小户型 · 健康饮食 · 新手厨房', bullets: ['4L 大容量', '可视玻璃观察', '一键预设'], imgs: 6 },
|
||
p7: { target: '健身房 · 通勤穿搭 · 18-32 岁女性', bullets: ['裸感面料', '高弹收腹', '亲肤透气'], imgs: 6 },
|
||
};
|
||
|
||
// 渲染商品图片 grid · n 张占位 + 上传按钮
|
||
function renderProdImgs(n) {
|
||
const grid = document.getElementById('pcf-imgs');
|
||
const ct = document.getElementById('pcf-imgs-ct');
|
||
if (!grid) return;
|
||
if (ct) ct.textContent = n;
|
||
let html = '';
|
||
for (let i = 0; i < n; i++) {
|
||
html += `<div class="thumb"><span class="ph-frame">1:1</span><button class="rm" type="button" title="删除" data-idx="${i}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button></div>`;
|
||
}
|
||
html += `<div class="img-upload" id="pcf-img-add" title="上传图片"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>`;
|
||
grid.innerHTML = html;
|
||
grid.querySelectorAll('.thumb .rm').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
btn.closest('.thumb').remove();
|
||
if (ct) ct.textContent = grid.querySelectorAll('.thumb').length;
|
||
});
|
||
});
|
||
const addBtn = document.getElementById('pcf-img-add');
|
||
if (addBtn) addBtn.addEventListener('click', () => {
|
||
if (typeof Shell !== 'undefined' && Shell.toast) Shell.toast('上传图片', '// 演示版暂不支持真实上传');
|
||
});
|
||
}
|
||
|
||
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: [], imgs: 0 };
|
||
document.getElementById('pcf-target').value = extra.target || '';
|
||
// 渲染商品图片 (n 张占位)
|
||
renderProdImgs(typeof extra.imgs === 'number' ? extra.imgs : 6);
|
||
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, '"')}">
|
||
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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, '"')}">
|
||
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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);
|
||
const imgs = document.querySelectorAll('#pcf-imgs .thumb').length;
|
||
PRODUCT_EXTRA[_editingProdId] = { target: newTarget, bullets, imgs };
|
||
Shell.toast('已保存', newName);
|
||
closeEditProductDrawer();
|
||
renderProdLib();
|
||
renderSelectedProds();
|
||
});
|
||
|
||
// 全部商品 入口 (左侧栏底部 · 打开商品库 modal)
|
||
function openProdLibModal() {
|
||
_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('ps-all-btn').addEventListener('click', openProdLibModal);
|
||
|
||
// 商品空间 · 搜索框 · 新建按钮
|
||
document.getElementById('ps-search-input').addEventListener('input', e => {
|
||
_psQuery = e.target.value;
|
||
renderProdSpace();
|
||
});
|
||
document.getElementById('ps-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);
|
||
renderProdSpace();
|
||
selectProduct(product.id);
|
||
Shell.toast('已加入商品库', '+ ' + product.name);
|
||
}
|
||
});
|
||
});
|
||
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; }
|
||
document.getElementById('pl-modal-bg').classList.remove('show');
|
||
selectProduct(_plDraft);
|
||
});
|
||
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;
|
||
_plCatFilter = '';
|
||
_plQuery = '';
|
||
const searchInput = document.getElementById('pl-search-input');
|
||
if (searchInput) searchInput.value = '';
|
||
renderProdLib();
|
||
renderProdSpace();
|
||
selectProduct(product.id);
|
||
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 showPreviewEmpty() {
|
||
const empty = document.getElementById('pv-empty');
|
||
const sum = document.getElementById('pv-summary');
|
||
const results = document.getElementById('pv-results');
|
||
const foot = document.getElementById('pv-foot');
|
||
if (empty) empty.hidden = false;
|
||
if (sum) sum.hidden = true;
|
||
if (results) results.hidden = true;
|
||
if (foot) foot.hidden = true;
|
||
}
|
||
function showPreviewContent() {
|
||
const empty = document.getElementById('pv-empty');
|
||
const sum = document.getElementById('pv-summary');
|
||
const results = document.getElementById('pv-results');
|
||
const foot = document.getElementById('pv-foot');
|
||
if (empty) empty.hidden = true;
|
||
if (sum) sum.hidden = false;
|
||
if (results) results.hidden = false;
|
||
if (foot) foot.hidden = false;
|
||
// pv-batch 由 renderResultCards 单独控制
|
||
}
|
||
|
||
// 渲染右侧预览 (占位 — 切平台/数量时显示)
|
||
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 placeholder-only">
|
||
<div class="mp-r-thumb"><span class="ph-frame">待生成</span></div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 渲染生成结果 (点立即生成时调,带 hover overlay + 批量 bar)
|
||
const RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>';
|
||
const ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
||
const CELL_RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>';
|
||
const CELL_DL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>';
|
||
const CELL_MORE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>';
|
||
const CELL_ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>';
|
||
const CELL_DEL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>';
|
||
const CELL_EDIT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>';
|
||
|
||
let _batchSeq = 0;
|
||
function appendBatch(n, kind) {
|
||
const container = document.getElementById('pv-results');
|
||
const id = state.selectedPlatform;
|
||
const c = id ? document.querySelector('.platform-card[data-id="' + id + '"]') : null;
|
||
if (!c) return;
|
||
_batchSeq += 1;
|
||
const ts = new Date();
|
||
const tsStr = `${String(ts.getHours()).padStart(2,'0')}:${String(ts.getMinutes()).padStart(2,'0')}:${String(ts.getSeconds()).padStart(2,'0')}`;
|
||
const labCls = kind === 'gen' ? 'gen' : 'rerun';
|
||
const labTxt = kind === 'gen' ? `批次 ${_batchSeq} · 初始生成` : (kind === 'rerun-all' ? `批次 ${_batchSeq} · 全部重跑` : `批次 ${_batchSeq} · 单张重跑`);
|
||
const section = document.createElement('div');
|
||
section.className = 'pv-platform-section';
|
||
section.dataset.kind = kind;
|
||
section.dataset.ts = String(ts.getTime());
|
||
section.dataset.platformId = id || '';
|
||
section.dataset.platformName = c.dataset.name || '';
|
||
section.dataset.search = [labTxt, c.dataset.name || '', n + '张'].join(' ').toLowerCase();
|
||
section.innerHTML = `
|
||
<div class="ps-h">
|
||
<span class="batch-lab ${labCls}">${labTxt}</span>
|
||
<span class="sep">·</span>
|
||
<span>${c.dataset.name} 套图</span>
|
||
<span class="ct">${n} 张 · ${tsStr}</span>
|
||
</div>
|
||
<div class="ps-grid">
|
||
${Array(n).fill(0).map((_, i) => `
|
||
<div class="mp-result gen" data-idx="${i}">
|
||
<div class="mp-r-thumb"><span class="ph-frame">${c.dataset.name}</span></div>
|
||
<span class="adopt-badge">已采用</span>
|
||
<div class="cell-feedback" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
<span>已采用</span>
|
||
</div>
|
||
<div class="cell-ops">
|
||
<button class="r-rerun" type="button" title="再次生成" aria-label="再次生成">${CELL_RERUN_SVG}</button>
|
||
<button class="r-dl" type="button" title="下载" aria-label="下载">${CELL_DL_SVG}</button>
|
||
<div class="cell-more-wrap">
|
||
<button class="r-more" type="button" title="更多" aria-label="更多">${CELL_MORE_SVG}</button>
|
||
<div class="cell-more-menu">
|
||
<button class="r-adopt" type="button">${CELL_ADOPT_SVG}<span>加入资产库</span></button>
|
||
<button class="r-del danger" type="button">${CELL_DEL_SVG}<span>删除</span></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
<div class="pc-pv-batch batch-foot">
|
||
<button class="pill-btn edit-batch" type="button" title="重新编辑">
|
||
${CELL_EDIT_SVG}
|
||
<span>重新编辑</span>
|
||
</button>
|
||
<button class="pill-btn rerun-batch" type="button" title="再次生成这一批">
|
||
${CELL_RERUN_SVG}
|
||
<span>再次生成</span>
|
||
</button>
|
||
<div class="batch-more-wrap">
|
||
<button class="pill-btn icon batch-more" type="button" title="更多" aria-label="更多">${CELL_MORE_SVG}</button>
|
||
<div class="batch-more-menu" role="menu">
|
||
<button class="batch-save-all" type="button">${CELL_ADOPT_SVG}<span>全部加入资产库</span></button>
|
||
<button class="batch-del danger" type="button">${CELL_DEL_SVG}<span>删除该批结果</span></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.appendChild(section);
|
||
section.querySelectorAll('.mp-result.gen').forEach(card => {
|
||
setTimeout(() => card.classList.remove('gen'), 1200);
|
||
});
|
||
section.querySelectorAll('.r-rerun').forEach(b => b.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
rerunOne(b.closest('.mp-result'));
|
||
}));
|
||
section.querySelectorAll('.r-dl').forEach(b => b.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
Shell.toast('下载', '已开始下载 · MOCK');
|
||
}));
|
||
// 更多 menu 开/合
|
||
section.querySelectorAll('.r-more').forEach(b => b.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const wrap = b.closest('.cell-more-wrap');
|
||
const willOpen = !wrap.classList.contains('open');
|
||
document.querySelectorAll('.pv-platform-section .cell-more-wrap.open').forEach(w => w.classList.remove('open'));
|
||
if (willOpen) wrap.classList.add('open');
|
||
}));
|
||
section.querySelectorAll('.r-adopt').forEach(b => b.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const wrap = b.closest('.cell-more-wrap');
|
||
if (wrap) wrap.classList.remove('open');
|
||
adoptOne(b.closest('.mp-result'));
|
||
}));
|
||
section.querySelectorAll('.r-del').forEach(b => b.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const wrap = b.closest('.cell-more-wrap');
|
||
if (wrap) wrap.classList.remove('open');
|
||
const card = b.closest('.mp-result');
|
||
const sec = card.closest('.pv-platform-section');
|
||
card.remove();
|
||
if (sec && !sec.querySelectorAll('.mp-result:not(.placeholder-only)').length) sec.remove();
|
||
else updateBatchSummary();
|
||
Shell.toast('已删除');
|
||
}));
|
||
section.querySelector('.edit-batch').addEventListener('click', () => {
|
||
const form = document.querySelector('.pc-form');
|
||
if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
Shell.toast('重新编辑', '请在左侧调整平台 / 张数后再生成');
|
||
});
|
||
section.querySelector('.rerun-batch').addEventListener('click', () => {
|
||
appendBatch(n, 'rerun-all');
|
||
Shell.toast('再次生成', n + ' 张图重新生成中 · 新批次已追加');
|
||
});
|
||
const _adoptAll = () => {
|
||
const cards = section.querySelectorAll('.mp-result:not(.adopted)');
|
||
if (!cards.length) { Shell.toast('该批次已全部采用'); return; }
|
||
cards.forEach(c => { c.classList.remove('gen'); c.classList.add('adopted'); });
|
||
updateBatchSummary();
|
||
Shell.toast('已全部加入资产库', cards.length + ' 张图入对应商品的 AI 素材 · 扣 ¥' + (cards.length * UNIT_PRICE).toFixed(2));
|
||
};
|
||
// 批次「更多」按钮 → 开/合 menu
|
||
const _bMoreBtn = section.querySelector('.batch-more');
|
||
const _bMoreWrap = section.querySelector('.batch-more-wrap');
|
||
if (_bMoreBtn && _bMoreWrap) {
|
||
_bMoreBtn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const willOpen = !_bMoreWrap.classList.contains('open');
|
||
document.querySelectorAll('.pc-pv-batch .batch-more-wrap.open').forEach(w => w.classList.remove('open'));
|
||
if (willOpen) _bMoreWrap.classList.add('open');
|
||
});
|
||
}
|
||
section.querySelector('.batch-save-all').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
if (_bMoreWrap) _bMoreWrap.classList.remove('open');
|
||
_adoptAll();
|
||
});
|
||
section.querySelector('.batch-del').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
if (_bMoreWrap) _bMoreWrap.classList.remove('open');
|
||
section.remove();
|
||
updateBatchSummary();
|
||
Shell.toast('已删除该批结果');
|
||
});
|
||
section.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||
updateBatchSummary();
|
||
if (typeof _refreshPlatformMenu === 'function') _refreshPlatformMenu();
|
||
if (typeof applyPvFilters === 'function') applyPvFilters();
|
||
}
|
||
function renderResultCards() {
|
||
const container = document.getElementById('pv-results');
|
||
// 首次生成清掉占位 placeholder-batch,保留已有真实批次(再次「立即生成」追加新批次)
|
||
container.querySelectorAll('.placeholder-batch').forEach(el => el.remove());
|
||
appendBatch(state.count, 'gen');
|
||
}
|
||
|
||
function rerunOne(card) {
|
||
if (!card) return;
|
||
appendBatch(1, 'rerun-one');
|
||
}
|
||
function adoptOne(card) {
|
||
if (!card || card.classList.contains('adopted')) return;
|
||
card.classList.remove('gen');
|
||
card.classList.add('adopted');
|
||
// spec §4.18 · 就地中央反馈
|
||
card.classList.add('show-feedback');
|
||
setTimeout(() => card.classList.remove('show-feedback'), 1500);
|
||
updateBatchSummary();
|
||
}
|
||
// 点击页面其它位置 → 关闭单图/批次 more menu
|
||
document.addEventListener('click', e => {
|
||
if (!e.target.closest('.pv-platform-section .cell-more-wrap')) {
|
||
document.querySelectorAll('.pv-platform-section .cell-more-wrap.open').forEach(w => w.classList.remove('open'));
|
||
}
|
||
if (!e.target.closest('.pc-pv-batch .batch-more-wrap')) {
|
||
document.querySelectorAll('.pc-pv-batch .batch-more-wrap.open').forEach(w => w.classList.remove('open'));
|
||
}
|
||
});
|
||
function updateBatchSummary() {
|
||
document.querySelectorAll('#pv-results .pv-platform-section').forEach(section => {
|
||
const cards = section.querySelectorAll('.mp-result:not(.placeholder-only)');
|
||
const adopted = section.querySelectorAll('.mp-result.adopted').length;
|
||
const adoptedEl = section.querySelector('.adopt-batch .adopted');
|
||
const totalEl = section.querySelector('.adopt-batch .total');
|
||
if (adoptedEl) adoptedEl.textContent = adopted;
|
||
if (totalEl) totalEl.textContent = cards.length;
|
||
});
|
||
}
|
||
|
||
// 立即生成
|
||
document.getElementById('pc-go-btn').addEventListener('click', () => {
|
||
if (!state.selectedPlatform || !state.selectedProd) return;
|
||
const container = document.getElementById('pv-results');
|
||
const hasReal = container && container.querySelector('.mp-result-batch:not(.placeholder-batch)');
|
||
const prod = PRODUCTS.find(p => p.id === state.selectedProd);
|
||
if (hasReal) {
|
||
Shell.toast('已追加批次', state.count + ' 张图新增到下方 · 旧批次保留');
|
||
} else {
|
||
Shell.toast('已提交任务', (prod ? prod.name + ' · ' : '') + state.count + ' 张图生成中');
|
||
}
|
||
showPreviewContent();
|
||
renderResultCards();
|
||
});
|
||
|
||
// ============================================================
|
||
// 工具台头部 · 搜索 / 时间 / 平台 筛选
|
||
// ============================================================
|
||
const _pvFilter = { time: 'all', platform: 'all', search: '' };
|
||
|
||
function _pvTimeMatch(ts, key) {
|
||
if (key === 'all') return true;
|
||
const now = Date.now();
|
||
const diff = now - Number(ts);
|
||
if (key === '10min') return diff <= 10 * 60 * 1000;
|
||
if (key === '1h') return diff <= 60 * 60 * 1000;
|
||
if (key === 'today') {
|
||
const a = new Date(now); const b = new Date(Number(ts));
|
||
return a.toDateString() === b.toDateString();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function applyPvFilters() {
|
||
const container = document.getElementById('pv-results');
|
||
if (!container) return;
|
||
const q = (_pvFilter.search || '').trim().toLowerCase();
|
||
container.querySelectorAll('.pv-platform-section:not(.placeholder-batch)').forEach(sec => {
|
||
let ok = true;
|
||
if (!_pvTimeMatch(sec.dataset.ts, _pvFilter.time)) ok = false;
|
||
if (ok && _pvFilter.platform !== 'all' && sec.dataset.platformId !== _pvFilter.platform) ok = false;
|
||
if (ok && q && !(sec.dataset.search || '').includes(q)) ok = false;
|
||
sec.dataset.hidden = ok ? '0' : '1';
|
||
});
|
||
const hasReal = !!container.querySelector('.pv-platform-section:not(.placeholder-batch)');
|
||
const filterActive = _pvFilter.time !== 'all' || _pvFilter.platform !== 'all' || q.length > 0;
|
||
container.querySelectorAll('.placeholder-batch').forEach(ph => {
|
||
ph.dataset.hidden = (hasReal || filterActive) ? '1' : '0';
|
||
});
|
||
}
|
||
|
||
function _refreshPlatformMenu() {
|
||
const menu = document.getElementById('pc-menu-platform');
|
||
if (!menu) return;
|
||
const container = document.getElementById('pv-results');
|
||
const used = new Map();
|
||
if (container) {
|
||
container.querySelectorAll('.pv-platform-section:not(.placeholder-batch)').forEach(s => {
|
||
const id = s.dataset.platformId; const nm = s.dataset.platformName;
|
||
if (id) used.set(id, nm || id);
|
||
});
|
||
}
|
||
const items = ['<button class="tb-menu-item' + (_pvFilter.platform === 'all' ? ' active' : '') + '" type="button" data-val="all">全部平台</button>'];
|
||
if (used.size === 0) {
|
||
items.push('<div class="tb-menu-empty">暂无批次,生成后可按平台筛选</div>');
|
||
} else {
|
||
used.forEach((nm, id) => {
|
||
items.push(`<button class="tb-menu-item${_pvFilter.platform === id ? ' active' : ''}" type="button" data-val="${id}">${nm}</button>`);
|
||
});
|
||
}
|
||
menu.innerHTML = items.join('');
|
||
}
|
||
|
||
function _setChipLabel(chipId, baseLabel, val) {
|
||
const chip = document.getElementById(chipId);
|
||
if (!chip) return;
|
||
if (val === 'all' || !val) chip.classList.remove('active');
|
||
else chip.classList.add('active');
|
||
}
|
||
|
||
function _closeAllMenus(except) {
|
||
document.querySelectorAll('.pc-main-h .tb-menu-wrap.open').forEach(w => {
|
||
if (w !== except) w.classList.remove('open');
|
||
});
|
||
}
|
||
|
||
document.querySelectorAll('.pc-main-h .tb-menu-wrap').forEach(wrap => {
|
||
const chip = wrap.querySelector('.tb-chip');
|
||
chip.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const willOpen = !wrap.classList.contains('open');
|
||
_closeAllMenus(wrap);
|
||
wrap.classList.toggle('open', willOpen);
|
||
if (willOpen && wrap.dataset.filter === 'platform') _refreshPlatformMenu();
|
||
});
|
||
});
|
||
document.querySelectorAll('#pc-menu-time, #pc-menu-platform').forEach(menu => {
|
||
menu.addEventListener('click', e => {
|
||
const btn = e.target.closest('.tb-menu-item');
|
||
if (!btn) return;
|
||
const val = btn.dataset.val;
|
||
const wrap = menu.closest('.tb-menu-wrap');
|
||
const key = wrap.dataset.filter;
|
||
_pvFilter[key] = val;
|
||
menu.querySelectorAll('.tb-menu-item').forEach(it => it.classList.toggle('active', it === btn));
|
||
wrap.classList.remove('open');
|
||
_setChipLabel(key === 'time' ? 'pc-chip-time' : 'pc-chip-platform', key === 'time' ? '时间' : '平台', val);
|
||
applyPvFilters();
|
||
});
|
||
});
|
||
document.addEventListener('click', e => {
|
||
if (!e.target.closest('.pc-main-h .tb-menu-wrap')) _closeAllMenus(null);
|
||
});
|
||
|
||
(function setupSearch() {
|
||
const wrap = document.getElementById('pc-search-wrap');
|
||
const toggle = document.getElementById('pc-search-toggle');
|
||
const input = document.getElementById('pc-search-input');
|
||
if (!wrap || !toggle || !input) return;
|
||
toggle.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const willExpand = !wrap.classList.contains('expanded');
|
||
wrap.classList.toggle('expanded', willExpand);
|
||
if (willExpand) setTimeout(() => input.focus(), 50);
|
||
else {
|
||
input.value = '';
|
||
_pvFilter.search = '';
|
||
applyPvFilters();
|
||
}
|
||
});
|
||
input.addEventListener('input', () => {
|
||
_pvFilter.search = input.value;
|
||
applyPvFilters();
|
||
});
|
||
input.addEventListener('keydown', e => {
|
||
if (e.key === 'Escape') {
|
||
input.value = '';
|
||
_pvFilter.search = '';
|
||
wrap.classList.remove('expanded');
|
||
applyPvFilters();
|
||
}
|
||
});
|
||
})();
|
||
|
||
// 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;
|
||
})();
|
||
|
||
/* ============================================================
|
||
生成批次 (localStorage) · 按当前商品过滤 · 立即生成时追加
|
||
============================================================ */
|
||
(function () {
|
||
'use strict';
|
||
const TASK_TYPE = 'platform';
|
||
const KEY = 'fs-image-tasks-' + TASK_TYPE;
|
||
|
||
let tasks = [];
|
||
|
||
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 buildSnapshot() {
|
||
return {
|
||
selectedProd: state.selectedProd,
|
||
selectedPlatform: state.selectedPlatform,
|
||
count: state.count,
|
||
};
|
||
}
|
||
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);
|
||
}
|
||
|
||
// 暴露给上层 (给 cur-prod header 用)
|
||
window._countBatchesForProd = function (prodId) {
|
||
return tasks.filter(t => t.snap && t.snap.selectedProd === prodId).length;
|
||
};
|
||
|
||
// 切换商品 → 清空 pv-results (历史批次只在当前 session 持有)
|
||
window.renderBatchesForCurrentProd = function () {
|
||
const container = document.getElementById('pv-results');
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
showPreviewEmpty();
|
||
};
|
||
|
||
// 立即生成 → push 新批次 + persist + 刷新 cur-prod 计数
|
||
document.getElementById('pc-go-btn').addEventListener('click', () => {
|
||
if (!state.selectedPlatform || !state.selectedProd) return;
|
||
const snap = buildSnapshot();
|
||
const _prod = PRODUCTS.find(p => p.id === state.selectedProd);
|
||
const _platCard = document.querySelector('.platform-card[data-id="' + state.selectedPlatform + '"]');
|
||
const _platName = (_platCard && _platCard.dataset.name) || '平台';
|
||
const _name = ((_prod && _prod.name) || '商品') + ' × ' + _platName;
|
||
const task = {
|
||
id: 'task-' + Date.now(),
|
||
type: TASK_TYPE,
|
||
name: _name,
|
||
snap,
|
||
status: 'ok',
|
||
time: timeNow(),
|
||
createdAt: Date.now(),
|
||
};
|
||
tasks.push(task);
|
||
save(tasks);
|
||
updateCurProdHeader();
|
||
});
|
||
|
||
tasks = load();
|
||
})();
|
||
|
||
// 初始
|
||
renderProdSpace();
|
||
renderSelectedProds();
|
||
updatePlatforms();
|
||
showPreviewEmpty(); // 默认 → 右侧显示空态
|
||
// 默认选中: URL ?product= 优先, 否则选 PRODUCTS 首位 (= 最近编辑)
|
||
const defaultProdId = state.selectedProd || (PRODUCTS[0] && PRODUCTS[0].id);
|
||
if (defaultProdId) selectProduct(defaultProdId);
|
||
</script>
|
||
</body>
|
||
</html>
|