All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 10s
2247 lines
91 KiB
HTML
2247 lines
91 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<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>
|
||
/* ─── 顶部 标题 + 状态 ─── */
|
||
.pd-title {
|
||
display: flex; align-items: center; gap: 12px;
|
||
margin-bottom: 22px;
|
||
}
|
||
.pd-title h1 {
|
||
font-size: 24px; font-weight: 600;
|
||
letter-spacing: -.015em;
|
||
color: var(--accent-black);
|
||
line-height: 1.25;
|
||
}
|
||
.pd-title .status {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
padding: 3px 10px;
|
||
background: var(--accent-emerald-bg, #e6f4ec);
|
||
color: var(--accent-emerald, #1f8a51);
|
||
border: 1px solid var(--accent-emerald-bd, #c4e3d1);
|
||
border-radius: var(--r-sm);
|
||
font-size: 11.5px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ─── 商品信息(含图片) + 快速操作(辅助) · 3 : 2 两栏 · 高度对齐 ─── */
|
||
.pd-overview {
|
||
display: grid;
|
||
grid-template-columns: 3fr 2fr;
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
align-items: stretch;
|
||
}
|
||
.pd-overview .ov-card { height: 100%; box-sizing: border-box; }
|
||
.pd-overview .ov-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 20px 22px;
|
||
min-width: 0;
|
||
position: relative;
|
||
}
|
||
/* 编辑按钮 · 放在 .ov-h 标题行右侧 (flex item, 不再 absolute) */
|
||
.pd-overview .ov-h { align-items: center; }
|
||
.ov-edit {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
height: 28px;
|
||
padding: 0 12px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--black-alpha-12);
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-72);
|
||
font-size: 12px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: border-color var(--t-base), color var(--t-base), background var(--t-base);
|
||
}
|
||
.ov-edit-single { margin-left: auto; }
|
||
.ov-edit:hover {
|
||
border-color: var(--heat-40);
|
||
color: var(--heat);
|
||
background: var(--heat-12);
|
||
}
|
||
.ov-edit svg { width: 12px; height: 12px; }
|
||
.ov-edit.primary {
|
||
background: var(--heat);
|
||
color: var(--accent-white);
|
||
border-color: var(--heat);
|
||
white-space: nowrap;
|
||
}
|
||
.ov-edit.primary:hover { filter: brightness(1.05); background: var(--heat); color: var(--accent-white); }
|
||
.ov-edit:disabled { cursor: not-allowed; color: var(--heat); border-color: var(--heat-40); background: var(--heat-12); opacity: 1; }
|
||
.ov-edit:disabled:hover { color: var(--heat); border-color: var(--heat-40); background: var(--heat-12); }
|
||
.ov-edit:disabled svg { color: var(--heat); }
|
||
|
||
/* 编辑模式按钮组 (重置 + 取消 + 保存) */
|
||
.ov-edit-group {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-left: auto;
|
||
}
|
||
.ov-card.editing .ov-edit-single { display: none; }
|
||
.ov-card.editing .ov-edit-group { display: inline-flex; }
|
||
|
||
/* AI 生成三视图 · 按钮 + 弹出 panel(布局复刻 pipeline.html stage 2 三视图预览) */
|
||
.ov-tri-wrap { position: relative; margin-left: auto; }
|
||
/* 当 AI 入口存在时,编辑信息按钮不再独占 ml-auto,与 AI 按钮紧贴 */
|
||
.ov-tri-wrap + .ov-edit-single { margin-left: 0; }
|
||
.ov-tri-trigger { white-space: nowrap; }
|
||
.ov-tri-trigger.is-open { border-color: var(--heat); color: var(--heat); background: var(--heat-12); }
|
||
.ov-card.editing .ov-tri-wrap { display: none; }
|
||
|
||
.ov-tri-pop {
|
||
position: absolute;
|
||
top: calc(100% + 6px); right: 0;
|
||
width: 360px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
box-shadow: 0 8px 24px rgba(0,0,0,.10), 0 2px 6px rgba(0,0,0,.06);
|
||
padding: 14px 14px 12px;
|
||
display: none;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
z-index: 40;
|
||
}
|
||
.ov-tri-pop.show { display: flex; }
|
||
.ov-tri-pop::before {
|
||
content: ''; position: absolute;
|
||
top: -5px; right: 36px;
|
||
width: 9px; height: 9px;
|
||
background: var(--surface);
|
||
border-left: 1px solid var(--border-faint);
|
||
border-top: 1px solid var(--border-faint);
|
||
transform: rotate(45deg);
|
||
}
|
||
.ov-tri-close {
|
||
position: absolute;
|
||
top: 8px; right: 8px;
|
||
width: 22px; height: 22px;
|
||
display: grid; place-items: center;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-56);
|
||
cursor: pointer;
|
||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||
z-index: 2;
|
||
}
|
||
.ov-tri-close:hover { background: var(--black-alpha-08); color: var(--accent-black); border-color: var(--border-faint); }
|
||
.ov-tri-close svg { width: 12px; height: 12px; }
|
||
|
||
/* 复刻 pipeline.html .prod-preview-* 内部样式 */
|
||
.ov-tri-pop .prod-preview-h { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-56); letter-spacing: .04em; text-transform: uppercase; padding-right: 28px; }
|
||
.ov-tri-pop .prod-preview-img { aspect-ratio: 16/9; }
|
||
.ov-tri-pop .prod-preview-foot { display: flex; align-items: center; gap: 8px; min-height: 30px; }
|
||
.ov-tri-pop .prod-preview-history { display: none; flex-direction: column; gap: 6px; }
|
||
.ov-tri-pop .prod-preview-history.show { display: flex; }
|
||
.ov-tri-pop .prod-preview-history .h-lbl { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; }
|
||
.ov-tri-pop .prod-preview-history .h-lbl .ct { color: var(--accent-black); font-weight: 600; }
|
||
.ov-tri-pop .prod-preview-history .h-row { display: flex; gap: 6px; overflow-x: auto; padding: 2px; scrollbar-width: thin; }
|
||
.ov-tri-pop .prod-preview-history .h-row::-webkit-scrollbar { height: 4px; }
|
||
.ov-tri-pop .prod-preview-history .h-row::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb {
|
||
flex: 0 0 auto;
|
||
width: 72px; aspect-ratio: 16/9;
|
||
background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm);
|
||
position: relative; cursor: pointer; transition: border-color var(--t-base);
|
||
display: grid; place-items: center; overflow: hidden;
|
||
}
|
||
.ov-tri-pop .prod-preview-history .h-thumb:hover { border-color: var(--heat-40); }
|
||
/* 已采用版本:主橙描边 + 「已采用」徽标 */
|
||
.ov-tri-pop .prod-preview-history .h-thumb.adopted { border-color: var(--heat); border-width: 2px; box-shadow: 0 0 0 2px var(--heat-12); }
|
||
/* 仅预览(未采用):黑色描边,无徽标 */
|
||
.ov-tri-pop .prod-preview-history .h-thumb.previewing { border-color: var(--accent-black); border-width: 2px; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb .v { font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-56); letter-spacing: .02em; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb.adopted .v { color: var(--heat); font-weight: 600; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb.previewing .v { color: var(--accent-black); font-weight: 600; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb .badge { position: absolute; top: 2px; left: 2px; font-family: var(--font-mono); font-size: 8.5px; padding: 0 4px; line-height: 12px; background: var(--heat); color: var(--accent-white); border-radius: 2px; letter-spacing: .02em; display: none; }
|
||
.ov-tri-pop .prod-preview-history .h-thumb.adopted .badge { display: block; }
|
||
|
||
/* 主图可点击放大 */
|
||
.ov-tri-pop .prod-preview-img.is-zoomable { cursor: zoom-in; transition: border-color var(--t-base); position: relative; }
|
||
.ov-tri-pop .prod-preview-img.is-zoomable:hover { border-color: var(--heat-40); }
|
||
.ov-tri-pop .prod-preview-img.is-zoomable::after {
|
||
content: '';
|
||
position: absolute; top: 8px; right: 8px;
|
||
width: 22px; height: 22px;
|
||
background: rgba(21,20,15,.72) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.3-4.3M8 11h6M11 8v6'/></svg>") center/14px no-repeat;
|
||
border-radius: var(--r-sm);
|
||
opacity: 0; transition: opacity var(--t-base);
|
||
pointer-events: none;
|
||
}
|
||
.ov-tri-pop .prod-preview-img.is-zoomable:hover::after { opacity: 1; }
|
||
|
||
/* 三视图放大查看 lightbox */
|
||
#ov-tri-lightbox-bg { z-index: 80; }
|
||
#ov-tri-lightbox-bg .tri-lightbox {
|
||
position: relative;
|
||
width: min(1100px, 92vw);
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 18px 20px 20px;
|
||
display: flex; flex-direction: column; gap: 12px;
|
||
box-shadow: 0 24px 64px rgba(0,0,0,.24);
|
||
}
|
||
.tri-lightbox-head {
|
||
display: flex; align-items: center; gap: 8px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
|
||
color: var(--black-alpha-56);
|
||
padding-right: 32px;
|
||
}
|
||
.tri-lightbox-head .lb-ver { color: var(--heat); font-weight: 600; }
|
||
.tri-lightbox-head .lb-tag {
|
||
margin-left: 6px;
|
||
padding: 2px 6px;
|
||
background: var(--heat-12); color: var(--heat);
|
||
border-radius: 3px;
|
||
font-size: 10px;
|
||
}
|
||
.tri-lightbox-close {
|
||
position: absolute;
|
||
top: 12px; right: 12px;
|
||
width: 28px; height: 28px;
|
||
display: grid; place-items: center;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-56);
|
||
cursor: pointer;
|
||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||
z-index: 2;
|
||
}
|
||
.tri-lightbox-close:hover { background: var(--black-alpha-08); color: var(--accent-black); border-color: var(--black-alpha-12); }
|
||
.tri-lightbox-close svg { width: 14px; height: 14px; }
|
||
.tri-lightbox-img { aspect-ratio: 16/9; width: 100%; }
|
||
.tri-lightbox-foot { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); }
|
||
.tri-lightbox-foot .spc { flex: 1; }
|
||
.tri-lightbox-foot kbd {
|
||
display: inline-block;
|
||
padding: 1px 5px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-bottom-width: 2px;
|
||
border-radius: 3px;
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
color: var(--black-alpha-72);
|
||
}
|
||
|
||
/* 字段 view ↔ edit 状态切换 */
|
||
.v-edit { display: none; }
|
||
.ov-card.editing .v-static { display: none; }
|
||
.ov-card.editing .v-edit { display: block; }
|
||
|
||
/* 输入控件 · 对齐新建表单 V2.1 规范 */
|
||
.v-input,
|
||
.v-select {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 38px;
|
||
border: 1px solid var(--black-alpha-12);
|
||
border-radius: var(--r-md);
|
||
padding: 0 14px;
|
||
font-size: 13.5px;
|
||
color: var(--accent-black);
|
||
background: var(--background-lighter);
|
||
font-family: inherit;
|
||
outline: none;
|
||
transition: border-color var(--t-base), box-shadow var(--t-base);
|
||
}
|
||
.v-input:focus,
|
||
.v-select:focus {
|
||
border-color: var(--heat-40);
|
||
box-shadow: inset 0 0 0 1px var(--heat-40);
|
||
}
|
||
|
||
/* 编辑模式下 · 核心卖点 bullet-list (与新建表单完全一致) */
|
||
.v-bullet-list {
|
||
list-style: none;
|
||
padding: 0; margin: 0;
|
||
}
|
||
.v-bullet-list .bl-item,
|
||
.v-bullet-list .bl-add {
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 8px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
margin-bottom: 6px;
|
||
font-size: 13.5px;
|
||
}
|
||
.v-bullet-list .bl-add { background: transparent; border-style: dashed; }
|
||
.v-bullet-list .bl-add:focus-within { border-color: var(--heat-40); background: var(--surface); }
|
||
.v-bullet-list .num {
|
||
width: 22px; height: 22px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--heat);
|
||
font-weight: 700;
|
||
display: grid; place-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.v-bullet-list .bl-add .num {
|
||
background: transparent;
|
||
color: var(--heat);
|
||
border-color: var(--heat-40);
|
||
}
|
||
.v-bullet-list .bl-text { flex: 1; color: var(--accent-black); }
|
||
.v-bullet-list .bl-input {
|
||
flex: 1;
|
||
background: transparent; border: 0; outline: none;
|
||
font-size: 13.5px;
|
||
color: var(--accent-black);
|
||
font-family: inherit;
|
||
}
|
||
.v-bullet-list .bl-input::placeholder { color: var(--black-alpha-48); }
|
||
.v-bullet-list .bl-x {
|
||
width: 22px; height: 22px;
|
||
color: var(--black-alpha-48);
|
||
cursor: pointer;
|
||
display: grid; place-items: center;
|
||
border-radius: var(--r-sm);
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
.v-bullet-list .bl-x:hover { color: var(--accent-crimson, #c43d3d); background: var(--crimson-bg, #fdebea); }
|
||
.v-bullet-list .bl-x svg { width: 11px; height: 11px; }
|
||
|
||
/* 编辑模式下,商品图片显示一个 [+ 上传] 占位 */
|
||
.img-upload {
|
||
display: none;
|
||
aspect-ratio: 1;
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
place-items: center;
|
||
color: var(--black-alpha-48);
|
||
background: var(--background-lighter);
|
||
transition: border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.img-upload:hover { border-color: var(--heat); color: var(--heat); }
|
||
.img-upload svg { width: 18px; height: 18px; }
|
||
.ov-card.editing .img-upload { display: grid; }
|
||
.ov-card.editing .ov-images-sub .thumb { cursor: pointer; }
|
||
.ov-card.editing .ov-images-sub .thumb::after {
|
||
content: '×';
|
||
position: absolute;
|
||
top: 4px; right: 4px;
|
||
width: 18px; height: 18px;
|
||
background: rgba(0,0,0,.7);
|
||
color: var(--accent-white);
|
||
border-radius: 50%;
|
||
display: grid; place-items: center;
|
||
font-size: 13px;
|
||
line-height: 1;
|
||
}
|
||
.ov-images-sub .thumb { position: relative; }
|
||
.pd-overview .ov-h {
|
||
display: flex; align-items: baseline; gap: 8px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.pd-overview .ov-h .ti {
|
||
font-size: 14px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.pd-overview .ov-h .ct {
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
}
|
||
.pd-overview .ov-h .more {
|
||
margin-left: auto;
|
||
font-size: 12px;
|
||
color: var(--heat);
|
||
cursor: pointer;
|
||
}
|
||
.pd-overview .ov-h .more:hover { text-decoration: underline; }
|
||
|
||
/* 商品信息卡片内 · 上信息 / 下图片 (堆叠, 图片铺满卡片) */
|
||
.ov-main-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18px;
|
||
}
|
||
.ov-main-grid > .ov-images-sub {
|
||
padding-top: 18px;
|
||
border-top: 1px solid var(--border-faint);
|
||
}
|
||
.ov-info .row {
|
||
display: flex; gap: 12px;
|
||
margin-bottom: 10px;
|
||
font-size: 13px;
|
||
}
|
||
.ov-info .row:last-child { margin-bottom: 0; }
|
||
.ov-info .k {
|
||
width: 64px;
|
||
flex-shrink: 0;
|
||
color: var(--black-alpha-48);
|
||
font-size: 12.5px;
|
||
}
|
||
.ov-info .v {
|
||
flex: 1; min-width: 0;
|
||
color: var(--accent-black);
|
||
line-height: 1.6;
|
||
}
|
||
.ov-info .v .bullet { display: block; }
|
||
.ov-info .v .bullet::before {
|
||
content: '·';
|
||
color: var(--heat);
|
||
margin-right: 6px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* 商品图片 · 卡片内子 section */
|
||
.ov-images-sub .sub-h {
|
||
display: flex; align-items: baseline; gap: 6px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.ov-images-sub .sub-h .ti {
|
||
font-size: 12.5px; font-weight: 500;
|
||
color: var(--black-alpha-72);
|
||
}
|
||
.ov-images-sub .sub-h .ct {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
}
|
||
.ov-images-sub .sub-h .more {
|
||
margin-left: auto;
|
||
font-size: 11.5px;
|
||
color: var(--heat);
|
||
cursor: pointer;
|
||
}
|
||
.ov-images-sub .sub-h .more:hover { text-decoration: underline; }
|
||
.ov-images-sub .grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
.ov-images-sub .thumb {
|
||
aspect-ratio: 1;
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
}
|
||
.ov-images-sub .thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||
|
||
/* 快速操作 · 2 段:图片生成(3 等比 CTA)+ 视频生成(1 CTA) · 两段等高填充容器 */
|
||
.ov-actions { display: flex; flex-direction: column; }
|
||
.ov-actions .qa-section {
|
||
margin-bottom: 14px;
|
||
display: flex; flex-direction: column;
|
||
flex: 1 1 0; min-height: 0;
|
||
}
|
||
.ov-actions .qa-section:last-child { margin-bottom: 0; }
|
||
.ov-actions .qa-section-h {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .06em;
|
||
text-transform: uppercase;
|
||
margin-bottom: 8px;
|
||
}
|
||
.ov-actions .qa-row-3 {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
flex: 1 1 0; min-height: 0;
|
||
}
|
||
.ov-actions .qa-row-1 {
|
||
display: flex;
|
||
flex: 1 1 0; min-height: 0;
|
||
}
|
||
.ov-actions .qa-row-1 .qa-item { width: 100%; }
|
||
.qa-item {
|
||
display: flex; flex-direction: column;
|
||
align-items: center; justify-content: center; gap: 8px;
|
||
padding: 14px 10px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
cursor: pointer;
|
||
font-size: 12.5px;
|
||
color: var(--accent-black);
|
||
text-align: center;
|
||
transition: border-color var(--t-base), background var(--t-base), color var(--t-base);
|
||
}
|
||
.qa-item:hover { border-color: var(--heat); background: var(--heat-12); color: var(--heat); }
|
||
.qa-item .ic {
|
||
width: 32px; height: 32px;
|
||
display: grid; place-items: center;
|
||
color: var(--heat);
|
||
flex-shrink: 0;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
}
|
||
.qa-item:hover .ic { border-color: var(--heat-20); background: var(--surface); }
|
||
.qa-item .ic svg { width: 16px; height: 16px; }
|
||
.qa-item.primary {
|
||
background: var(--heat);
|
||
color: var(--accent-white);
|
||
border-color: var(--heat);
|
||
}
|
||
.qa-item.primary .ic { background: rgba(255,255,255,.16); color: var(--accent-white); border-color: rgba(255,255,255,.24); }
|
||
.qa-item.primary:hover { color: var(--accent-white); box-shadow: var(--shadow-cta-hover); }
|
||
|
||
/* 状态 pill 三态(通过/不通过/归档) */
|
||
.asset-card .meta .pill.pass {
|
||
background: var(--accent-emerald-bg, #e6f4ec);
|
||
color: var(--accent-emerald, #1f8a51);
|
||
border: 1px solid var(--accent-emerald-bd, #c4e3d1);
|
||
cursor: pointer;
|
||
}
|
||
.asset-card .meta .pill.fail {
|
||
background: var(--crimson-bg, #fdebea);
|
||
color: var(--accent-crimson, #c43d3d);
|
||
border: 1px solid var(--crimson-bd, #f5c2bf);
|
||
cursor: pointer;
|
||
}
|
||
.asset-card .meta .pill.archive {
|
||
background: var(--background-lighter);
|
||
color: var(--black-alpha-56);
|
||
border: 1px solid var(--border-faint);
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ─── Tabs ─── */
|
||
.pd-tabs {
|
||
display: flex; gap: 4px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
margin-bottom: 18px;
|
||
}
|
||
.pd-tabs .tab {
|
||
padding: 10px 14px;
|
||
font-size: 13.5px;
|
||
color: var(--black-alpha-56);
|
||
background: transparent;
|
||
border: 0;
|
||
border-bottom: 2px solid transparent;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
font-weight: 500;
|
||
transition: color var(--t-base), border-color var(--t-base);
|
||
}
|
||
.pd-tabs .tab:hover { color: var(--accent-black); }
|
||
.pd-tabs .tab.active {
|
||
color: var(--accent-black);
|
||
border-bottom-color: var(--heat);
|
||
font-weight: 600;
|
||
}
|
||
.tab-pane { display: none; }
|
||
.tab-pane.active { display: block; }
|
||
|
||
/* ─── AI 素材 工具栏 ─── */
|
||
.pd-toolbar {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin-bottom: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.pd-toolbar .total {
|
||
font-size: 14px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
}
|
||
.pd-toolbar .total .ct {
|
||
font-family: var(--font-mono);
|
||
font-size: 11.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
margin-left: 4px;
|
||
font-weight: 500;
|
||
}
|
||
.pd-toolbar .filter {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
height: 30px;
|
||
padding: 0 10px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
cursor: pointer;
|
||
font-size: 12.5px;
|
||
color: var(--black-alpha-72);
|
||
font-family: inherit;
|
||
transition: border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.pd-toolbar .filter:hover { border-color: var(--black-alpha-24); }
|
||
.pd-toolbar .filter.open, .pd-toolbar .filter.filtered { border-color: var(--heat); color: var(--heat); background: var(--heat-12); }
|
||
.pd-toolbar .filter.open svg, .pd-toolbar .filter.filtered svg { opacity: 1; }
|
||
.pd-toolbar .filter svg { width: 10px; height: 10px; opacity: .6; transition: transform var(--t-base); }
|
||
.pd-toolbar .filter.open svg { transform: rotate(180deg); }
|
||
|
||
/* 筛选下拉 · 挂在 body 上避免被祖先 overflow 裁切 */
|
||
.filter-pop {
|
||
position: fixed;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 4px;
|
||
box-shadow: 0 6px 20px var(--black-alpha-12);
|
||
z-index: 1500;
|
||
min-width: 130px;
|
||
display: none;
|
||
flex-direction: column;
|
||
}
|
||
.filter-pop.show { display: flex; }
|
||
.filter-pop button {
|
||
background: transparent; border: 0;
|
||
padding: 8px 12px;
|
||
text-align: left;
|
||
font-size: 12.5px;
|
||
color: var(--accent-black);
|
||
cursor: pointer;
|
||
border-radius: var(--r-sm);
|
||
font-family: inherit;
|
||
white-space: nowrap;
|
||
transition: background var(--t-base);
|
||
}
|
||
.filter-pop button:hover { background: var(--background-lighter); }
|
||
.filter-pop button.selected { background: var(--heat-12); color: var(--heat); font-weight: 600; }
|
||
.filter-pop button.selected::before { content: '✓ '; }
|
||
.pd-toolbar .right { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
|
||
.pd-toolbar .view-tog {
|
||
display: inline-flex;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
padding: 2px;
|
||
}
|
||
.pd-toolbar .view-tog button {
|
||
width: 28px; height: 26px;
|
||
display: grid; place-items: center;
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--black-alpha-48);
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
}
|
||
.pd-toolbar .view-tog button.active {
|
||
background: var(--accent-black);
|
||
color: var(--accent-white);
|
||
}
|
||
.pd-toolbar .view-tog button svg { width: 13px; height: 13px; }
|
||
|
||
/* ─── AI 素材 网格 ─── */
|
||
.asset-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
/* 列表视图:卡片横排,缩略图缩到 88px */
|
||
.asset-grid.list-view {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.asset-grid.list-view .asset-card {
|
||
display: grid;
|
||
grid-template-columns: 88px minmax(0, 1fr);
|
||
gap: 0;
|
||
align-items: center;
|
||
}
|
||
.asset-grid.list-view .asset-card .thumb {
|
||
aspect-ratio: 1;
|
||
width: 88px;
|
||
border-right: 1px solid var(--border-faint);
|
||
}
|
||
.asset-grid.list-view .asset-card .thumb .type-pill {
|
||
font-size: 9.5px;
|
||
padding: 2px 6px;
|
||
top: 4px;
|
||
left: 4px;
|
||
}
|
||
.asset-grid.list-view .asset-card .thumb .ph-frame { font-size: 10px; }
|
||
.asset-grid.list-view .asset-card .meta {
|
||
padding: 10px 14px;
|
||
}
|
||
|
||
/* 空筛选结果 */
|
||
.empty-filter {
|
||
padding: 56px 24px;
|
||
text-align: center;
|
||
color: var(--black-alpha-48);
|
||
font-family: var(--font-mono);
|
||
font-size: 12.5px;
|
||
letter-spacing: .02em;
|
||
border: 1px dashed var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
background: var(--background-lighter);
|
||
}
|
||
.empty-filter .reset {
|
||
display: inline-block; margin-top: 12px;
|
||
color: var(--heat); cursor: pointer; text-decoration: underline;
|
||
}
|
||
.asset-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: border-color var(--t-base), transform var(--t-fast);
|
||
}
|
||
.asset-card:hover { border-color: var(--black-alpha-24); transform: translateY(-1px); }
|
||
.asset-card .thumb {
|
||
aspect-ratio: 3/4;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.asset-card .thumb .type-pill {
|
||
position: absolute; top: 8px; left: 8px;
|
||
padding: 3px 8px;
|
||
background: rgba(0,0,0,.65);
|
||
color: var(--accent-white);
|
||
border-radius: var(--r-sm);
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
.asset-card .meta {
|
||
padding: 10px 12px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.asset-card .meta .pill {
|
||
padding: 2px 8px;
|
||
border-radius: var(--r-sm);
|
||
font-size: 10.5px;
|
||
font-weight: 500;
|
||
}
|
||
.asset-card .meta .date {
|
||
margin-left: auto;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .02em;
|
||
}
|
||
.pd-more {
|
||
text-align: center;
|
||
padding: 18px 0 32px;
|
||
}
|
||
.pd-more button {
|
||
height: 32px;
|
||
padding: 0 18px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
color: var(--black-alpha-72);
|
||
font-size: 12.5px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
}
|
||
.pd-more button:hover { border-color: var(--heat-40); color: var(--heat); }
|
||
|
||
/* ─── 任务记录 · 表格 ─── */
|
||
.task-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 12px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.task-stat {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 14px 18px;
|
||
}
|
||
.task-stat .lbl {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
margin-bottom: 6px;
|
||
}
|
||
.task-stat .v {
|
||
font-size: 22px; font-weight: 600;
|
||
color: var(--accent-black);
|
||
letter-spacing: -.01em;
|
||
}
|
||
.task-stat .v small {
|
||
font-size: 13px;
|
||
color: var(--black-alpha-48);
|
||
font-weight: 400;
|
||
margin-left: 4px;
|
||
}
|
||
.task-stat.ok .v { color: var(--accent-emerald, #1f8a51); }
|
||
.task-stat.gen .v { color: var(--heat); }
|
||
.task-stat.err .v { color: var(--accent-crimson, #c43d3d); }
|
||
|
||
.task-table {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
overflow: hidden;
|
||
}
|
||
.task-row {
|
||
display: grid;
|
||
grid-template-columns: 36px 1.8fr 0.7fr 1fr 1.1fr 1.1fr 0.7fr 100px;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 18px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
font-size: 13px;
|
||
}
|
||
.task-row:last-child { border-bottom: 0; }
|
||
.task-row.head {
|
||
background: var(--background-lighter);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
letter-spacing: .04em;
|
||
font-weight: 500;
|
||
padding: 10px 18px;
|
||
}
|
||
.task-row .ph {
|
||
width: 36px; height: 36px;
|
||
border-radius: var(--r-sm);
|
||
flex-shrink: 0;
|
||
}
|
||
.task-row .nm {
|
||
color: var(--accent-black);
|
||
font-weight: 500;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.task-row .nm .id-mono {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--black-alpha-48);
|
||
font-weight: 400;
|
||
}
|
||
.task-row .qty { color: var(--black-alpha-72); font-family: var(--font-mono); }
|
||
.task-row .time {
|
||
color: var(--black-alpha-72);
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
letter-spacing: .01em;
|
||
}
|
||
.task-row .dur {
|
||
color: var(--black-alpha-56);
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
}
|
||
.task-row .pill {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
padding: 3px 9px;
|
||
border-radius: var(--r-sm);
|
||
font-size: 11.5px;
|
||
font-weight: 500;
|
||
width: fit-content;
|
||
}
|
||
.task-row .pill .dot {
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
}
|
||
.task-row .pill.ok {
|
||
background: var(--accent-emerald-bg, #e6f4ec);
|
||
color: var(--accent-emerald, #1f8a51);
|
||
border: 1px solid var(--accent-emerald-bd, #c4e3d1);
|
||
}
|
||
.task-row .pill.ok .dot { background: var(--accent-emerald, #1f8a51); }
|
||
.task-row .pill.gen {
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border: 1px solid var(--heat-20);
|
||
}
|
||
.task-row .pill.gen .dot { background: var(--heat); animation: pulse 1.6s ease-in-out infinite; }
|
||
.task-row .pill.err {
|
||
background: var(--crimson-bg, #fdebea);
|
||
color: var(--accent-crimson, #c43d3d);
|
||
border: 1px solid var(--crimson-bd, #f5c2bf);
|
||
}
|
||
.task-row .pill.err .dot { background: var(--accent-crimson, #c43d3d); }
|
||
.task-row .pill.wait {
|
||
background: var(--background-lighter);
|
||
color: var(--black-alpha-56);
|
||
border: 1px solid var(--border-faint);
|
||
}
|
||
.task-row .pill.wait .dot { background: var(--black-alpha-32); }
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: .3; }
|
||
}
|
||
.task-row .status-cell { display: flex; flex-direction: column; gap: 4px; }
|
||
.task-row .progress {
|
||
width: 100%; height: 3px;
|
||
background: var(--black-alpha-12);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
.task-row .progress > span {
|
||
display: block;
|
||
height: 100%;
|
||
background: var(--heat);
|
||
}
|
||
.task-row .ops {
|
||
display: inline-flex; gap: 4px;
|
||
justify-self: end;
|
||
}
|
||
.task-row .ops button {
|
||
padding: 4px 10px;
|
||
height: 26px;
|
||
background: transparent;
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
color: var(--black-alpha-72);
|
||
font-size: 11.5px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
transition: border-color var(--t-base), color var(--t-base);
|
||
}
|
||
.task-row .ops button:hover { border-color: var(--heat-40); color: var(--heat); }
|
||
.task-row .ops button.danger:hover { border-color: var(--crimson-bd, #f5c2bf); color: var(--accent-crimson, #c43d3d); }
|
||
|
||
@media (max-width: 1100px) {
|
||
.pd-overview { grid-template-columns: 1fr; }
|
||
.ov-actions .qa-grid {
|
||
display: grid !important;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
}
|
||
@media (max-width: 900px) {
|
||
.ov-actions .qa-grid { grid-template-columns: 1fr 1fr; }
|
||
.task-stats { grid-template-columns: repeat(2, 1fr); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="page">
|
||
|
||
<!-- 顶部 标题 + 状态 -->
|
||
<div class="pd-title">
|
||
<h1 id="pd-name">补水保湿精华液</h1>
|
||
</div>
|
||
|
||
<!-- 商品信息(含图片) + 快速操作 · 主辅两栏 -->
|
||
<div class="pd-overview">
|
||
|
||
<div class="ov-card ov-main" id="ov-main-card">
|
||
<div class="ov-h">
|
||
<span class="ti">商品信息</span>
|
||
<!-- AI 生成三视图 · 按钮 + 弹出 panel(view 模式可见) -->
|
||
<div class="ov-tri-wrap">
|
||
<button class="ov-edit ov-tri-trigger" type="button" id="ov-tri-btn" title="AI 生成商品三视图" aria-haspopup="dialog" aria-expanded="false">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/><path d="M19 14l.9 2.1L22 17l-2.1.9L19 20l-.9-2.1L16 17l2.1-.9L19 14z"/></svg>
|
||
AI 生成三视图
|
||
</button>
|
||
<div class="ov-tri-pop" id="ov-tri-pop" role="dialog" aria-label="AI 生成三视图">
|
||
<button class="ov-tri-close" type="button" id="ov-tri-close" aria-label="关闭">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="prod-preview-h">// 三视图预览 · <span id="ov-tri-status">待生成</span></div>
|
||
<div class="placeholder prod-preview-img" id="ov-tri-img"><span class="ph-frame">// 尚未生成 · 点击下方按钮开始</span></div>
|
||
<div class="prod-preview-foot" id="ov-tri-foot">
|
||
<button class="ov-edit primary" type="button" id="ov-tri-start" style="height:28px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/></svg>
|
||
生成
|
||
</button>
|
||
<span style="flex:1;"></span>
|
||
<span class="mono" style="font-size:11px; color: var(--black-alpha-56);">~¥0.30 / 次</span>
|
||
</div>
|
||
<div class="prod-preview-history" id="ov-tri-history">
|
||
<div class="h-lbl">// 历史版本 · <span class="ct" id="ov-tri-history-count">0</span> 版</div>
|
||
<div class="h-row" id="ov-tri-history-row"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- view 模式: 单个 [编辑信息] -->
|
||
<button class="ov-edit ov-edit-single" type="button" id="ov-edit-btn" title="编辑商品信息">
|
||
<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.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||
编辑信息
|
||
</button>
|
||
<!-- edit 模式: [重置] [取消] [保存] -->
|
||
<div class="ov-edit-group">
|
||
<button class="ov-edit" type="button" id="ov-reset-btn" title="重置为修改前">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
|
||
重置
|
||
</button>
|
||
<button class="ov-edit" type="button" id="ov-cancel-btn">取消</button>
|
||
<button class="ov-edit primary" type="button" id="ov-save-btn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
|
||
保存
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ov-main-grid">
|
||
|
||
<div class="ov-info">
|
||
<div class="row" data-field="name">
|
||
<div class="k">商品名称</div>
|
||
<div class="v">
|
||
<span class="v-static">补水保湿精华液</span>
|
||
<input class="v-edit v-input" type="text" value="补水保湿精华液" maxlength="100">
|
||
</div>
|
||
</div>
|
||
<div class="row" data-field="cat">
|
||
<div class="k">品类</div>
|
||
<div class="v">
|
||
<span class="v-static">美妆个护 / 精华液</span>
|
||
<select class="v-edit v-select">
|
||
<option>美妆个护 / 精华液</option>
|
||
<option>美妆个护</option>
|
||
<option>服饰内衣</option>
|
||
<option>食品饮料</option>
|
||
<option>家居家电</option>
|
||
<option>数码 3C</option>
|
||
<option>个护清洁</option>
|
||
<option>运动户外</option>
|
||
<option>母婴亲子</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="row" data-field="target">
|
||
<div class="k">目标人群</div>
|
||
<div class="v">
|
||
<span class="v-static">22-32 岁女性、敏感肌、办公室通勤</span>
|
||
<input class="v-edit v-input" type="text" value="22-32 岁女性、敏感肌、办公室通勤">
|
||
</div>
|
||
</div>
|
||
<div class="row" data-field="bullets">
|
||
<div class="k">核心卖点</div>
|
||
<div class="v">
|
||
<div class="v-static">
|
||
<span class="bullet">透明质酸 + B5,敷完不黏不闷</span>
|
||
<span class="bullet">30g 大容量精华液</span>
|
||
<span class="bullet">0 香精 0 酒精,敏感肌可用</span>
|
||
</div>
|
||
<ul class="v-edit v-bullet-list" id="v-bullets-list">
|
||
<!-- li.bl-item × N + li.bl-add 由 JS 在进入编辑模式时渲染 -->
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ov-images-sub">
|
||
<div class="sub-h">
|
||
<span class="ti">商品图片</span>
|
||
<span class="ct">(6)</span>
|
||
</div>
|
||
<div class="grid" id="ov-images-grid">
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="thumb placeholder"><span class="ph-frame">1:1</span></div>
|
||
<div class="img-upload" id="ov-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>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ov-card ov-actions">
|
||
<div class="ov-h"><span class="ti">快速操作</span></div>
|
||
<div class="qa-section">
|
||
<div class="qa-section-h">// 图片生成</div>
|
||
<div class="qa-row-3">
|
||
<div class="qa-item" data-go="model-photo" role="button" tabindex="0">
|
||
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a4 4 0 014-4h8a4 4 0 014 4v2"/></svg></span>
|
||
模特上身图
|
||
</div>
|
||
<div class="qa-item" data-go="platform-cover" role="button" tabindex="0">
|
||
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg></span>
|
||
平台套图
|
||
</div>
|
||
<div class="qa-item" data-go="image-optimize" role="button" tabindex="0">
|
||
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M3 12h18M5 5l14 14M5 19l14-14"/></svg></span>
|
||
图片创作
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="qa-section">
|
||
<div class="qa-section-h">// 视频生成</div>
|
||
<div class="qa-row-1">
|
||
<div class="qa-item primary" data-go="projects-new" role="button" tabindex="0">
|
||
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg></span>
|
||
生成视频
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="pd-tabs">
|
||
<button class="tab active" type="button" data-tab="assets">AI 生成素材</button>
|
||
<button class="tab" type="button" data-tab="videos">视频项目</button>
|
||
<button class="tab" type="button" data-tab="tasks" hidden>任务记录</button>
|
||
</div>
|
||
|
||
<!-- ===== AI 生成素材 ===== -->
|
||
<div class="tab-pane active" data-pane="assets">
|
||
|
||
<div class="pd-toolbar">
|
||
<div class="total">全部 AI 素材 <span class="ct">(32)</span></div>
|
||
<button class="filter" type="button" data-key="type">
|
||
全部类型
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
<button class="filter" type="button" data-key="status">
|
||
通过
|
||
<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="right">
|
||
<div class="view-tog">
|
||
<button type="button" class="active" title="网格视图">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||
</button>
|
||
<button type="button" title="列表视图">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
|
||
</button>
|
||
</div>
|
||
<button class="filter" type="button" data-key="sort">
|
||
最新生成
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="asset-grid">
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">模特上身图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">模特上身图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">模特上身图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill fail" data-status="fail" title="点击切换状态">不通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">模特上身图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">模特上身图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill archive" data-status="archive" title="点击切换状态">归档</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">平台套图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">平台套图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">平台套图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill fail" data-status="fail" title="点击切换状态">不通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">平台套图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill archive" data-status="archive" title="点击切换状态">归档</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">平台套图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">三视图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
<div class="asset-card"><div class="thumb placeholder"><span class="type-pill">三视图</span><span class="ph-frame">3:4</span></div><div class="meta"><span class="pill archive" data-status="archive" title="点击切换状态">归档</span><span class="date">2026-05-19 15:30</span></div></div>
|
||
</div>
|
||
|
||
<div class="pd-more"><button type="button">加载更多</button></div>
|
||
</div>
|
||
|
||
<!-- ===== 视频项目 ===== -->
|
||
<div class="tab-pane" data-pane="videos">
|
||
<div class="pd-toolbar">
|
||
<div class="total">该商品视频项目 <span class="ct">(4)</span></div>
|
||
<div class="right">
|
||
<button class="filter" type="button" data-key="sort">
|
||
最新导出
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="asset-grid">
|
||
<div class="asset-card" data-proj-status="done"><div class="thumb placeholder" style="aspect-ratio: 9/16;"><span class="type-pill">视频 · 9:16</span><span class="ph-frame">补水面膜 · v3</span></div><div class="meta"><span class="pill ok"><span class="dot"></span>已完成</span><span class="date">2026-05-20 12:08</span></div></div>
|
||
<div class="asset-card" data-proj-status="wip"><div class="thumb placeholder" style="aspect-ratio: 9/16;"><span class="type-pill">视频 · 9:16</span><span class="ph-frame">补水面膜 · v2</span></div><div class="meta"><span class="pill info"><span class="dot"></span>视频生成 4/6</span><span class="date">2026-05-19 10:24</span></div></div>
|
||
<div class="asset-card" data-proj-status="archived"><div class="thumb placeholder" style="aspect-ratio: 9/16;"><span class="type-pill">视频 · 9:16</span><span class="ph-frame">熬夜急救 · v1</span></div><div class="meta"><span class="pill neutral"><span class="dot"></span>已归档</span><span class="date">2026-05-18 21:42</span></div></div>
|
||
<div class="asset-card" data-proj-status="fail"><div class="thumb placeholder" style="aspect-ratio: 9/16;"><span class="type-pill">视频 · 9:16</span><span class="ph-frame">补水面膜 · v1</span></div><div class="meta"><span class="pill err"><span class="dot"></span>故事板失败</span><span class="date">2026-05-17 16:00</span></div></div>
|
||
</div>
|
||
|
||
<div class="pd-more"><button type="button">加载更多</button></div>
|
||
</div>
|
||
|
||
<!-- ===== 任务记录 ===== -->
|
||
<div class="tab-pane" data-pane="tasks">
|
||
|
||
<!-- 顶部统计概览 -->
|
||
<div class="task-stats">
|
||
<div class="task-stat">
|
||
<div class="lbl">// TOTAL</div>
|
||
<div class="v">12 <small>个任务</small></div>
|
||
</div>
|
||
<div class="task-stat ok">
|
||
<div class="lbl">// SUCCESS</div>
|
||
<div class="v">9</div>
|
||
</div>
|
||
<div class="task-stat gen">
|
||
<div class="lbl">// RUNNING</div>
|
||
<div class="v">2</div>
|
||
</div>
|
||
<div class="task-stat err">
|
||
<div class="lbl">// FAILED</div>
|
||
<div class="v">1</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工具栏 -->
|
||
<div class="pd-toolbar">
|
||
<div class="total">任务记录 <span class="ct">(12)</span></div>
|
||
<button class="filter" type="button" data-key="type">
|
||
全部类型
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
<button class="filter" type="button" data-key="status">
|
||
全部状态
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
<button class="filter" type="button" data-key="date">
|
||
近 7 天
|
||
<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="right">
|
||
<button class="filter" type="button" data-key="sort">
|
||
提交时间倒序
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 任务表格 -->
|
||
<div class="task-table">
|
||
<div class="task-row head">
|
||
<span></span>
|
||
<span>任务 / 编号</span>
|
||
<span>数量</span>
|
||
<span>状态</span>
|
||
<span>提交时间</span>
|
||
<span>完成时间</span>
|
||
<span>耗时</span>
|
||
<span style="justify-self:end">操作</span>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">视频素材 <span class="id-mono">// T-2026-0519-0007</span></div>
|
||
<div class="qty">1 个</div>
|
||
<div class="status-cell">
|
||
<span class="pill gen"><span class="dot"></span>生成中 60%</span>
|
||
<span class="progress"><span style="width:60%"></span></span>
|
||
</div>
|
||
<div class="time">2026-05-19 16:00</div>
|
||
<div class="time">—</div>
|
||
<div class="dur">—</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
<button type="button" class="danger">取消</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">模特上身图 <span class="id-mono">// T-2026-0519-0006</span></div>
|
||
<div class="qty">3 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill wait"><span class="dot"></span>排队中</span>
|
||
</div>
|
||
<div class="time">2026-05-19 15:58</div>
|
||
<div class="time">—</div>
|
||
<div class="dur">—</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
<button type="button" class="danger">取消</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">模特上身图 <span class="id-mono">// T-2026-0519-0005</span></div>
|
||
<div class="qty">5 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill ok"><span class="dot"></span>已完成</span>
|
||
</div>
|
||
<div class="time">2026-05-19 15:30</div>
|
||
<div class="time">2026-05-19 15:32</div>
|
||
<div class="dur">2m 14s</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">平台套图 <span class="id-mono">// T-2026-0519-0004</span></div>
|
||
<div class="qty">4 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill ok"><span class="dot"></span>已完成</span>
|
||
</div>
|
||
<div class="time">2026-05-19 14:20</div>
|
||
<div class="time">2026-05-19 14:23</div>
|
||
<div class="dur">3m 02s</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">模特上身图 <span class="id-mono">// T-2026-0519-0003</span></div>
|
||
<div class="qty">4 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill ok"><span class="dot"></span>已完成</span>
|
||
</div>
|
||
<div class="time">2026-05-19 13:10</div>
|
||
<div class="time">2026-05-19 13:13</div>
|
||
<div class="dur">2m 50s</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">三视图 <span class="id-mono">// T-2026-0519-0002</span></div>
|
||
<div class="qty">3 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill err"><span class="dot"></span>失败</span>
|
||
</div>
|
||
<div class="time">2026-05-19 12:00</div>
|
||
<div class="time">2026-05-19 12:01</div>
|
||
<div class="dur">30s</div>
|
||
<div class="ops">
|
||
<button type="button">重试</button>
|
||
<button type="button">日志</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-row">
|
||
<div class="ph placeholder"></div>
|
||
<div class="nm">平台套图 <span class="id-mono">// T-2026-0518-0001</span></div>
|
||
<div class="qty">6 张</div>
|
||
<div class="status-cell">
|
||
<span class="pill ok"><span class="dot"></span>已完成</span>
|
||
</div>
|
||
<div class="time">2026-05-18 18:42</div>
|
||
<div class="time">2026-05-18 18:46</div>
|
||
<div class="dur">4m 10s</div>
|
||
<div class="ops">
|
||
<button type="button">查看</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="pd-more"><button type="button">加载更多</button></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- 三视图 · 放大查看 lightbox -->
|
||
<div class="modal-bg" id="ov-tri-lightbox-bg" onclick="if(event.target===this)Shell.closeModal('ov-tri-lightbox-bg')">
|
||
<div class="tri-lightbox" role="dialog" aria-label="三视图放大查看">
|
||
<button class="tri-lightbox-close" type="button" onclick="Shell.closeModal('ov-tri-lightbox-bg')" aria-label="关闭">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="tri-lightbox-head">
|
||
// 三视图(正/侧/背) · <span class="lb-ver" id="ov-tri-lightbox-label">v1</span>
|
||
<span class="lb-tag" id="ov-tri-lightbox-tag" hidden>已采用</span>
|
||
</div>
|
||
<div class="placeholder tri-lightbox-img" id="ov-tri-lightbox-img"></div>
|
||
<div class="tri-lightbox-foot">
|
||
<span id="ov-tri-lightbox-meta">// 生成于 --:--</span>
|
||
<span class="spc"></span>
|
||
<span><kbd>Esc</kbd> 关闭</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="assets/icons.js?v=2026052608"></script>
|
||
<script src="assets/shell.js?v=2026052607"></script>
|
||
<script>
|
||
// 从 URL ?product= 读出商品名,注入 crumb / h1 / 商品名字段
|
||
const _urlProductName = (function () {
|
||
try {
|
||
const q = new URLSearchParams(location.search);
|
||
const v = q.get('product') || q.get('name');
|
||
return v ? decodeURIComponent(v) : '';
|
||
} catch (e) { return ''; }
|
||
})();
|
||
const _productDisplayName = _urlProductName || '补水保湿精华液';
|
||
Shell.render({
|
||
active: 'products',
|
||
crumbs: [
|
||
{ label: '工作台', href: 'index.html' },
|
||
{ label: '商品库', href: 'products.html' },
|
||
{ label: _productDisplayName }
|
||
]
|
||
});
|
||
if (_urlProductName) {
|
||
const h1 = document.getElementById('pd-name');
|
||
if (h1) h1.textContent = _urlProductName;
|
||
const nameRow = document.querySelector('[data-field="name"] .v-static');
|
||
if (nameRow) nameRow.textContent = _urlProductName;
|
||
const nameInput = document.querySelector('[data-field="name"] .v-input');
|
||
if (nameInput) nameInput.value = _urlProductName;
|
||
}
|
||
|
||
// 快速操作 · 跳转至对应工作台/wizard 并携带商品名
|
||
(function bindQuickActions() {
|
||
const productName = (document.querySelector('.pd-title h1, .pd-title, .ov-h .ti, h1') || {}).textContent || '';
|
||
const crumbName = (document.querySelector('.crumb-current') || {}).textContent
|
||
|| (document.querySelectorAll('.crumb-item').length ? document.querySelectorAll('.crumb-item')[document.querySelectorAll('.crumb-item').length-1].textContent : '')
|
||
|| '补水保湿精华液';
|
||
const name = (crumbName || productName || '').trim();
|
||
document.querySelectorAll('.qa-item[data-go]').forEach(item => {
|
||
item.style.cursor = 'pointer';
|
||
let go = item.dataset.go;
|
||
// 图片创作 → 独立工作台 image-optimize.html(自由创作),带 product 作为提示词种子
|
||
let url;
|
||
if (go === 'image-optimize') {
|
||
url = 'image-optimize.html?t=' + Date.now() + '&prompt=' + encodeURIComponent(name);
|
||
} else {
|
||
url = go + '.html?t=' + Date.now() + '&product=' + encodeURIComponent(name);
|
||
}
|
||
item.addEventListener('click', () => { location.href = url; });
|
||
item.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); location.href = url; }
|
||
});
|
||
});
|
||
})();
|
||
|
||
// 任务记录 · 「查看」按钮 / 行点击 → 跳对应工作台
|
||
(function bindTaskRowJump() {
|
||
const TYPE_MAP = {
|
||
'模特上身图': 'model-photo.html',
|
||
'平台套图': 'platform-cover.html',
|
||
'视频素材': 'projects-new.html',
|
||
'视频': 'projects-new.html',
|
||
'三视图': 'model-photo.html?mode=tri',
|
||
};
|
||
const productName = (document.getElementById('pd-name')?.textContent || '').trim();
|
||
document.querySelectorAll('.task-table .task-row:not(.head)').forEach(row => {
|
||
const nameCell = row.querySelector('.nm');
|
||
if (!nameCell) return;
|
||
// 任务类型 = nm 的第一段文本(去掉 id-mono)
|
||
const type = nameCell.firstChild?.nodeValue?.trim() || '';
|
||
let url = TYPE_MAP[type];
|
||
if (!url) return;
|
||
url += (url.includes('?') ? '&' : '?') + 't=' + Date.now()
|
||
+ (productName ? '&product=' + encodeURIComponent(productName) : '');
|
||
row.style.cursor = 'pointer';
|
||
row.addEventListener('click', e => {
|
||
if (e.target.closest('.ops button')) return;
|
||
location.href = url;
|
||
});
|
||
// 「查看」按钮
|
||
row.querySelectorAll('.ops button').forEach(btn => {
|
||
if (btn.textContent.trim() === '查看') {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
location.href = url;
|
||
});
|
||
}
|
||
});
|
||
});
|
||
})();
|
||
|
||
// 状态 pill · 三态循环(通过 → 不通过 → 归档 → 通过)
|
||
(function bindStatusPills() {
|
||
const labels = { pass: '通过', fail: '不通过', archive: '归档' };
|
||
const order = ['pass', 'fail', 'archive'];
|
||
document.addEventListener('click', e => {
|
||
const pill = e.target.closest('.asset-card .meta .pill[data-status]');
|
||
if (!pill) return;
|
||
e.stopPropagation();
|
||
if (pill.closest('.asset-card[data-tri-version]')) {
|
||
window.ProductDetailFilters?.applyAssets?.();
|
||
Shell.toast('三视图状态由采用版本决定', '请在三视图面板选择「采用此版本」');
|
||
return;
|
||
}
|
||
const cur = pill.dataset.status;
|
||
const next = order[(order.indexOf(cur) + 1) % order.length];
|
||
pill.dataset.status = next;
|
||
pill.classList.remove('pass', 'fail', 'archive');
|
||
pill.classList.add(next);
|
||
pill.textContent = labels[next];
|
||
window.ProductDetailFilters?.applyAssets?.();
|
||
Shell.toast('状态已更新', labels[next]);
|
||
});
|
||
})();
|
||
|
||
// Tab 切换
|
||
document.querySelectorAll('.pd-tabs .tab').forEach(t => {
|
||
t.onclick = () => {
|
||
document.querySelectorAll('.pd-tabs .tab').forEach(x => x.classList.remove('active'));
|
||
t.classList.add('active');
|
||
const target = t.dataset.tab;
|
||
document.querySelectorAll('.tab-pane').forEach(p => {
|
||
p.classList.toggle('active', p.dataset.pane === target);
|
||
});
|
||
};
|
||
});
|
||
|
||
// ─── 工具栏:筛选 / 排序 / 视图 / 加载更多(per pane)───
|
||
(function setupToolbars() {
|
||
// 每个 (pane, key) 的可选项 + 默认值(数组首项)
|
||
const OPTIONS = {
|
||
'assets:type': ['全部类型', '模特上身图', '平台套图', '三视图'],
|
||
// 状态:默认 通过 · 没有「全部状态」选项,只能切换 通过 / 不通过 / 归档
|
||
'assets:status': ['通过', '不通过', '归档'],
|
||
'assets:sort': ['最新生成', '最早生成'],
|
||
'videos:sort': ['最新导出', '最早导出'],
|
||
'tasks:type': ['全部类型', '模特上身图', '平台套图', '视频素材', '三视图'],
|
||
'tasks:status': ['全部状态', '已完成', '生成中', '排队中', '失败'],
|
||
'tasks:date': ['全部', '今天', '近 7 天', '近 30 天'],
|
||
'tasks:sort': ['提交时间倒序', '提交时间正序'],
|
||
};
|
||
// 「状态」永远视为已生效筛选(没有"全部"选项),即便选了默认值也走过滤逻辑
|
||
const ALWAYS_APPLY_KEYS = new Set(['status']);
|
||
// 任务行 status pill 的 class → 中文标签
|
||
const TASK_STATUS_MAP = { ok: '已完成', gen: '生成中', wait: '排队中', err: '失败', fail: '失败' };
|
||
// assets 卡片 data-status → 中文标签
|
||
const ASSET_STATUS_MAP = { pass: '通过', fail: '不通过', archive: '归档' };
|
||
|
||
let openPop = null;
|
||
function closeOpenPop() {
|
||
if (!openPop) return;
|
||
openPop.pop.classList.remove('show');
|
||
openPop.btn.classList.remove('open');
|
||
openPop = null;
|
||
}
|
||
document.addEventListener('click', closeOpenPop);
|
||
window.addEventListener('resize', closeOpenPop);
|
||
// .content 区域滚动时也关
|
||
(document.querySelector('.content') || document).addEventListener('scroll', closeOpenPop, true);
|
||
|
||
document.querySelectorAll('.tab-pane').forEach(paneEl => {
|
||
const paneId = paneEl.dataset.pane;
|
||
|
||
paneEl.querySelectorAll('.pd-toolbar .filter[data-key]').forEach(btn => {
|
||
const key = btn.dataset.key;
|
||
const opts = OPTIONS[paneId + ':' + key];
|
||
if (!opts) return;
|
||
btn.dataset.value = opts[0];
|
||
btn.dataset.default = opts[0];
|
||
|
||
const pop = document.createElement('div');
|
||
pop.className = 'filter-pop';
|
||
pop.innerHTML = opts.map(o =>
|
||
`<button type="button" data-val="${o}">${o}</button>`
|
||
).join('');
|
||
document.body.appendChild(pop);
|
||
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
if (openPop && openPop.btn === btn) { closeOpenPop(); return; }
|
||
closeOpenPop();
|
||
const r = btn.getBoundingClientRect();
|
||
pop.style.top = (r.bottom + 4) + 'px';
|
||
pop.style.left = r.left + 'px';
|
||
pop.style.minWidth = r.width + 'px';
|
||
pop.classList.add('show');
|
||
btn.classList.add('open');
|
||
pop.querySelectorAll('button').forEach(b =>
|
||
b.classList.toggle('selected', b.dataset.val === btn.dataset.value));
|
||
openPop = { btn, pop };
|
||
});
|
||
pop.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const opt = e.target.closest('button[data-val]');
|
||
if (!opt) return;
|
||
setFilterValue(btn, opt.dataset.val);
|
||
closeOpenPop();
|
||
applyFilters(paneEl);
|
||
});
|
||
});
|
||
|
||
// 视图切换:网格 / 列表
|
||
const viewTog = paneEl.querySelector('.view-tog');
|
||
if (viewTog) {
|
||
const btns = viewTog.querySelectorAll('button');
|
||
btns.forEach((b, i) => {
|
||
b.onclick = () => {
|
||
btns.forEach(x => x.classList.remove('active'));
|
||
b.classList.add('active');
|
||
const grid = paneEl.querySelector('.asset-grid');
|
||
if (grid) grid.classList.toggle('list-view', i === 1);
|
||
};
|
||
});
|
||
}
|
||
|
||
// 加载更多:复用前 N 个卡片克隆,演示用
|
||
const moreBtn = paneEl.querySelector('.pd-more button');
|
||
if (moreBtn) {
|
||
let loaded = 0;
|
||
moreBtn.onclick = () => {
|
||
loaded++;
|
||
if (loaded > 2) {
|
||
moreBtn.textContent = '已加载全部';
|
||
moreBtn.disabled = true;
|
||
moreBtn.style.opacity = '.5';
|
||
moreBtn.style.cursor = 'not-allowed';
|
||
Shell.toast('已到末尾', '没有更多素材了');
|
||
return;
|
||
}
|
||
const grid = paneEl.querySelector('.asset-grid');
|
||
if (!grid) return;
|
||
const src = [...grid.querySelectorAll('.asset-card')].slice(0, 4);
|
||
src.forEach(c => grid.appendChild(c.cloneNode(true)));
|
||
applyFilters(paneEl);
|
||
Shell.toast('已加载更多', '+' + src.length + ' 个');
|
||
};
|
||
}
|
||
|
||
// 首次 apply 一遍(同步 count)
|
||
applyFilters(paneEl);
|
||
});
|
||
|
||
function setFilterValue(btn, val) {
|
||
btn.dataset.value = val;
|
||
[...btn.childNodes].forEach(n => { if (n.nodeType === 3) n.remove(); });
|
||
btn.insertBefore(document.createTextNode(val + ' '), btn.firstChild);
|
||
// 仅在切到非默认值时才高亮(状态 chip 即便永远过滤,视觉也保持中性,与「全部类型」一致)
|
||
btn.classList.toggle('filtered', val !== btn.dataset.default);
|
||
}
|
||
|
||
function applyFilters(paneEl) {
|
||
const paneId = paneEl.dataset.pane;
|
||
const f = {};
|
||
paneEl.querySelectorAll('.pd-toolbar .filter[data-key]').forEach(b => {
|
||
f[b.dataset.key] = b.dataset.value;
|
||
});
|
||
const isDefault = (key) => {
|
||
const btn = paneEl.querySelector('.pd-toolbar .filter[data-key="' + key + '"]');
|
||
if (!btn) return true; // 该 pane 没这个 filter → 等同默认,不过滤
|
||
if (ALWAYS_APPLY_KEYS.has(key)) return false; // 状态有按钮时,永远走过滤
|
||
return btn.dataset.value === btn.dataset.default;
|
||
};
|
||
|
||
if (paneId === 'assets' || paneId === 'videos') {
|
||
const cards = [...paneEl.querySelectorAll('.asset-card')];
|
||
let visible = 0;
|
||
cards.forEach(c => {
|
||
let show = true;
|
||
if (paneId === 'assets' && !isDefault('type')) {
|
||
const t = c.querySelector('.type-pill')?.textContent?.trim() || '';
|
||
if (t !== f.type) show = false;
|
||
}
|
||
if (!isDefault('status')) {
|
||
const s = c.querySelector('.pill[data-status]')?.dataset.status || '';
|
||
if ((ASSET_STATUS_MAP[s] || '') !== f.status) show = false;
|
||
}
|
||
c.style.display = show ? '' : 'none';
|
||
if (show) visible++;
|
||
});
|
||
if (!isDefault('sort')) {
|
||
const grid = paneEl.querySelector('.asset-grid');
|
||
const asc = (f.sort || '').includes('最早');
|
||
[...grid.querySelectorAll('.asset-card')]
|
||
.filter(c => c.style.display !== 'none')
|
||
.sort((a, b) => {
|
||
const ad = a.querySelector('.date')?.textContent || '';
|
||
const bd = b.querySelector('.date')?.textContent || '';
|
||
return asc ? ad.localeCompare(bd) : bd.localeCompare(ad);
|
||
})
|
||
.forEach(c => grid.appendChild(c));
|
||
}
|
||
updateCount(paneEl, visible);
|
||
toggleEmpty(paneEl, visible === 0);
|
||
// 不足 2 行时不显示「加载更多」按钮(布局后用 offsetTop 统计行数)
|
||
const moreEl = paneEl.querySelector('.pd-more');
|
||
if (moreEl) {
|
||
requestAnimationFrame(() => {
|
||
const visCards = [...paneEl.querySelectorAll('.asset-card')].filter(c => c.style.display !== 'none');
|
||
const rows = new Set(visCards.map(c => c.offsetTop)).size;
|
||
moreEl.style.display = rows >= 2 ? '' : 'none';
|
||
});
|
||
}
|
||
} else if (paneId === 'tasks') {
|
||
const rows = [...paneEl.querySelectorAll('.task-table .task-row:not(.head)')];
|
||
let visible = 0;
|
||
rows.forEach(row => {
|
||
let show = true;
|
||
if (!isDefault('type')) {
|
||
const t = row.querySelector('.nm')?.firstChild?.nodeValue?.trim() || '';
|
||
if (t !== f.type) show = false;
|
||
}
|
||
if (!isDefault('status')) {
|
||
const pill = row.querySelector('.status-cell .pill');
|
||
const cls = pill?.className || '';
|
||
const matched = Object.entries(TASK_STATUS_MAP)
|
||
.some(([k, v]) => cls.split(/\s+/).includes(k) && v === f.status);
|
||
if (!matched) show = false;
|
||
}
|
||
// date 过滤 · mock 数据都是 5/19,简单按天数差近似
|
||
if (!isDefault('date')) {
|
||
const submitTxt = row.querySelectorAll('.time')[0]?.textContent || '';
|
||
const m = submitTxt.match(/(\d{4})-(\d{2})-(\d{2})/);
|
||
if (m) {
|
||
const d = new Date(m[1] + '-' + m[2] + '-' + m[3]);
|
||
const now = new Date();
|
||
const days = Math.floor((now - d) / 86400000);
|
||
if (f.date === '今天' && days > 0) show = false;
|
||
if (f.date === '近 7 天' && days > 7) show = false;
|
||
if (f.date === '近 30 天' && days > 30) show = false;
|
||
}
|
||
}
|
||
row.style.display = show ? '' : 'none';
|
||
if (show) visible++;
|
||
});
|
||
if (!isDefault('sort')) {
|
||
const table = paneEl.querySelector('.task-table');
|
||
const asc = (f.sort || '').includes('正序');
|
||
[...table.querySelectorAll('.task-row:not(.head)')]
|
||
.filter(r => r.style.display !== 'none')
|
||
.sort((a, b) => {
|
||
const ad = a.querySelectorAll('.time')[0]?.textContent || '';
|
||
const bd = b.querySelectorAll('.time')[0]?.textContent || '';
|
||
return asc ? ad.localeCompare(bd) : bd.localeCompare(ad);
|
||
})
|
||
.forEach(r => table.appendChild(r));
|
||
}
|
||
updateCount(paneEl, visible);
|
||
toggleEmpty(paneEl, visible === 0);
|
||
}
|
||
}
|
||
|
||
function updateCount(paneEl, n) {
|
||
const ct = paneEl.querySelector('.pd-toolbar .total .ct');
|
||
if (ct) ct.textContent = '(' + n + ')';
|
||
}
|
||
|
||
function toggleEmpty(paneEl, isEmpty) {
|
||
let empty = paneEl.querySelector('.empty-filter');
|
||
const container = paneEl.querySelector('.asset-grid, .task-table');
|
||
const more = paneEl.querySelector('.pd-more');
|
||
if (isEmpty) {
|
||
if (!empty) {
|
||
empty = document.createElement('div');
|
||
empty.className = 'empty-filter';
|
||
empty.innerHTML = '// 当前筛选下没有结果<br><a class="reset">点这里重置筛选</a>';
|
||
container?.after(empty);
|
||
empty.querySelector('.reset').addEventListener('click', () => {
|
||
paneEl.querySelectorAll('.pd-toolbar .filter[data-key]').forEach(b => {
|
||
setFilterValue(b, b.dataset.default);
|
||
});
|
||
applyFilters(paneEl);
|
||
});
|
||
}
|
||
empty.style.display = '';
|
||
if (more) more.style.display = 'none';
|
||
} else if (empty) {
|
||
empty.style.display = 'none';
|
||
if (more) more.style.display = '';
|
||
}
|
||
}
|
||
|
||
window.ProductDetailFilters = {
|
||
apply(pane = 'assets') {
|
||
const paneEl = document.querySelector(`.tab-pane[data-pane="${pane}"]`);
|
||
if (paneEl) applyFilters(paneEl);
|
||
},
|
||
applyAssets() {
|
||
this.apply('assets');
|
||
}
|
||
};
|
||
})();
|
||
|
||
// 编辑商品信息 · 在卡片内 inline 切换 view ↔ edit
|
||
(function initEdit() {
|
||
const card = document.getElementById('ov-main-card');
|
||
const editBtn = document.getElementById('ov-edit-btn');
|
||
const cancelBtn = document.getElementById('ov-cancel-btn');
|
||
const saveBtn = document.getElementById('ov-save-btn');
|
||
if (!card || !editBtn || !cancelBtn || !saveBtn) return;
|
||
|
||
// 同时同步顶部 h1 标题 — 商品名称改动后,顶部大标题也跟着更新
|
||
const pdName = document.getElementById('pd-name');
|
||
|
||
function escapeHtml(s) {
|
||
return s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
}
|
||
const X_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
|
||
// bullet-list 编辑模式 · 操作 helpers
|
||
function renumberBullets(list) {
|
||
[...list.querySelectorAll('.bl-item .num')].forEach((n, i) => n.textContent = i + 1);
|
||
}
|
||
function bindBulletX(x, list) {
|
||
x.addEventListener('click', () => {
|
||
x.closest('.bl-item').remove();
|
||
renumberBullets(list);
|
||
});
|
||
}
|
||
function makeBulletLi(text) {
|
||
const li = document.createElement('li');
|
||
li.className = 'bl-item';
|
||
const safe = (text || '').replace(/"/g, '"');
|
||
li.innerHTML = `<span class="num"></span><input class="bl-input bl-edit" type="text" value="${safe}" placeholder="卖点内容"><span class="bl-x" title="删除">${X_SVG}</span>`;
|
||
return li;
|
||
}
|
||
function addBulletItem(list, text) {
|
||
const li = makeBulletLi(text);
|
||
list.querySelector('.bl-add').before(li);
|
||
bindBulletX(li.querySelector('.bl-x'), list);
|
||
renumberBullets(list);
|
||
}
|
||
function renderBulletEditor(list, items) {
|
||
list.innerHTML = '';
|
||
// 已有项 (每条都是 input 可直接修改)
|
||
items.forEach(text => list.appendChild(makeBulletLi(text)));
|
||
// 添加行
|
||
const addLi = document.createElement('li');
|
||
addLi.className = 'bl-add';
|
||
addLi.innerHTML = `<span class="num">+</span><input class="bl-input" placeholder="添加新卖点 · 回车确认">`;
|
||
list.appendChild(addLi);
|
||
// 绑定已有项的删除
|
||
list.querySelectorAll('.bl-item .bl-x').forEach(x => bindBulletX(x, list));
|
||
// 绑定回车追加
|
||
const addInput = addLi.querySelector('.bl-input');
|
||
addInput.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
const v = addInput.value.trim();
|
||
if (!v) return;
|
||
addBulletItem(list, v);
|
||
addInput.value = '';
|
||
addInput.focus();
|
||
}
|
||
});
|
||
renumberBullets(list);
|
||
}
|
||
|
||
// 进入编辑模式: 把当前静态值灌进 input
|
||
function enterEdit() {
|
||
card.querySelectorAll('[data-field]').forEach(row => {
|
||
const stat = row.querySelector('.v-static');
|
||
const inp = row.querySelector('.v-edit');
|
||
if (!stat || !inp) return;
|
||
if (row.dataset.field === 'bullets') {
|
||
const items = [...stat.querySelectorAll('.bullet')].map(b => b.textContent.trim());
|
||
renderBulletEditor(inp, items);
|
||
} else if (inp.tagName === 'SELECT') {
|
||
const cur = stat.textContent.trim();
|
||
[...inp.options].forEach((o, i) => { if (o.textContent.trim() === cur) inp.selectedIndex = i; });
|
||
} else {
|
||
inp.value = stat.textContent.trim();
|
||
}
|
||
});
|
||
card.classList.add('editing');
|
||
}
|
||
function exitEdit() {
|
||
card.classList.remove('editing');
|
||
}
|
||
function save() {
|
||
card.querySelectorAll('[data-field]').forEach(row => {
|
||
const stat = row.querySelector('.v-static');
|
||
const inp = row.querySelector('.v-edit');
|
||
if (!stat || !inp) return;
|
||
if (row.dataset.field === 'bullets') {
|
||
// 从 .bl-item .bl-edit input 读值 (允许修改已有条目)
|
||
const items = [...inp.querySelectorAll('.bl-item .bl-edit')]
|
||
.map(t => t.value.trim()).filter(Boolean);
|
||
stat.innerHTML = items.map(s => `<span class="bullet">${escapeHtml(s)}</span>`).join('');
|
||
} else {
|
||
const val = (inp.tagName === 'SELECT') ? inp.options[inp.selectedIndex].textContent : inp.value;
|
||
stat.textContent = val.trim();
|
||
if (row.dataset.field === 'name' && pdName) {
|
||
pdName.textContent = val.trim() || pdName.textContent;
|
||
}
|
||
}
|
||
});
|
||
card.classList.remove('editing');
|
||
Shell.toast('已保存', '商品信息已更新');
|
||
}
|
||
// 重置 · 在编辑模式下,把所有输入框回退到当前静态值 (相当于重新进入 edit)
|
||
function resetEdit() {
|
||
card.querySelectorAll('[data-field]').forEach(row => {
|
||
const stat = row.querySelector('.v-static');
|
||
const inp = row.querySelector('.v-edit');
|
||
if (!stat || !inp) return;
|
||
if (row.dataset.field === 'bullets') {
|
||
const items = [...stat.querySelectorAll('.bullet')].map(b => b.textContent.trim());
|
||
renderBulletEditor(inp, items);
|
||
} else if (inp.tagName === 'SELECT') {
|
||
const cur = stat.textContent.trim();
|
||
[...inp.options].forEach((o, i) => { if (o.textContent.trim() === cur) inp.selectedIndex = i; });
|
||
} else {
|
||
inp.value = stat.textContent.trim();
|
||
}
|
||
});
|
||
Shell.toast('已重置');
|
||
}
|
||
|
||
const resetBtn = document.getElementById('ov-reset-btn');
|
||
// 防御性: 先清空可能存在的 inline onclick, 再用 addEventListener 绑定
|
||
editBtn.onclick = null;
|
||
cancelBtn.onclick = null;
|
||
saveBtn.onclick = null;
|
||
if (resetBtn) resetBtn.onclick = null;
|
||
editBtn.addEventListener('click', (e) => { e.preventDefault(); enterEdit(); });
|
||
cancelBtn.addEventListener('click', (e) => { e.preventDefault(); exitEdit(); });
|
||
saveBtn.addEventListener('click', (e) => { e.preventDefault(); save(); });
|
||
if (resetBtn) resetBtn.addEventListener('click', (e) => { e.preventDefault(); resetEdit(); });
|
||
|
||
// 编辑模式下,点缩略图 → 删除
|
||
const grid = document.getElementById('ov-images-grid');
|
||
if (grid) {
|
||
grid.addEventListener('click', e => {
|
||
if (!card.classList.contains('editing')) return;
|
||
const thumb = e.target.closest('.thumb');
|
||
if (thumb && !thumb.classList.contains('img-upload')) {
|
||
thumb.remove();
|
||
}
|
||
});
|
||
// [+] 上传占位
|
||
const addBtn = document.getElementById('ov-img-add');
|
||
if (addBtn) addBtn.onclick = () => Shell.toast('上传图片', '请选择本地图片 (占位)');
|
||
}
|
||
})();
|
||
|
||
// AI 生成三视图 · 按钮悬浮 panel(可重复打开 / X 关闭 / 点击外部关闭 / Esc 关闭)
|
||
(function initTriView() {
|
||
const btn = document.getElementById('ov-tri-btn');
|
||
const pop = document.getElementById('ov-tri-pop');
|
||
const closeBtn = document.getElementById('ov-tri-close');
|
||
const startBtn = document.getElementById('ov-tri-start');
|
||
const img = document.getElementById('ov-tri-img');
|
||
const statusEl = document.getElementById('ov-tri-status');
|
||
const foot = document.getElementById('ov-tri-foot');
|
||
const history = document.getElementById('ov-tri-history');
|
||
const historyRow = document.getElementById('ov-tri-history-row');
|
||
const historyCount = document.getElementById('ov-tri-history-count');
|
||
if (!btn || !pop || !closeBtn || !startBtn) return;
|
||
|
||
const versions = []; // [{ ts, label }]
|
||
let previewIdx = -1; // 主图当前正在「预览」哪一版(浏览态)
|
||
let adoptedIdx = -1; // 真正被「采用」的那一版 · 与素材库通过状态联动
|
||
let generating = false;
|
||
|
||
function prodName() {
|
||
return (document.getElementById('pd-name')?.textContent || '商品').trim();
|
||
}
|
||
|
||
function refreshAssetFilters() {
|
||
window.ProductDetailFilters?.applyAssets?.();
|
||
}
|
||
|
||
function open() {
|
||
pop.classList.add('show');
|
||
btn.classList.add('is-open');
|
||
btn.setAttribute('aria-expanded', 'true');
|
||
}
|
||
function close() {
|
||
pop.classList.remove('show');
|
||
btn.classList.remove('is-open');
|
||
btn.setAttribute('aria-expanded', 'false');
|
||
}
|
||
function toggle() {
|
||
if (pop.classList.contains('show')) close(); else open();
|
||
}
|
||
|
||
function renderHistory() {
|
||
if (versions.length === 0) { history.classList.remove('show'); return; }
|
||
history.classList.add('show');
|
||
historyCount.textContent = versions.length;
|
||
historyRow.innerHTML = versions.map((ver, i) => {
|
||
const isAdopted = i === adoptedIdx;
|
||
const isPreview = i === previewIdx;
|
||
const cls = [
|
||
isAdopted ? 'adopted' : '',
|
||
isPreview && !isAdopted ? 'previewing' : '',
|
||
].filter(Boolean).join(' ');
|
||
const titleParts = [ver.label, ver.ts];
|
||
if (isAdopted) titleParts.push('已采用');
|
||
else if (isPreview) titleParts.push('预览中');
|
||
return `
|
||
<div class="h-thumb ${cls}" data-idx="${i}" title="${titleParts.join(' · ')}">
|
||
<span class="badge">已采用</span>
|
||
<span class="v">${ver.label}</span>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
historyRow.querySelectorAll('.h-thumb').forEach(el => {
|
||
el.addEventListener('click', () => {
|
||
const idx = Number(el.dataset.idx);
|
||
if (idx === previewIdx) return;
|
||
setPreview(idx);
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderMain() {
|
||
if (previewIdx < 0) return;
|
||
const ver = versions[previewIdx];
|
||
const isAdopted = previewIdx === adoptedIdx;
|
||
img.innerHTML = `<span class="ph-frame">${prodName()} · 三视图(正/侧/背) · ${ver.label}</span>`;
|
||
img.classList.add('is-zoomable');
|
||
img.title = '点击放大查看';
|
||
statusEl.textContent = isAdopted
|
||
? `${ver.label} · 已采用,不满意可重跑`
|
||
: `${ver.label} · 预览中(未采用)`;
|
||
foot.innerHTML = `
|
||
<button class="ov-edit" type="button" id="ov-tri-rerun" style="height:28px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
|
||
重跑
|
||
</button>
|
||
<button class="ov-edit ${isAdopted ? '' : 'primary'}" type="button" id="ov-tri-adopt" style="height:28px;" ${isAdopted ? 'disabled title="此版本已采用"' : 'title="将此版本设为唯一通过版本,其他版本变为不通过"'}>
|
||
${isAdopted
|
||
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg> 已采用'
|
||
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg> 采用此版本'}
|
||
</button>
|
||
<span style="flex:1;"></span>
|
||
<span class="mono" style="font-size:11px; color: var(--black-alpha-56);">~¥0.30 / 次</span>
|
||
`;
|
||
document.getElementById('ov-tri-rerun')?.addEventListener('click', start);
|
||
document.getElementById('ov-tri-adopt')?.addEventListener('click', adoptPreview);
|
||
}
|
||
|
||
function syncLibraryStatus() {
|
||
const grid = document.querySelector('.tab-pane[data-pane="assets"] .asset-grid');
|
||
if (!grid) return;
|
||
const adoptedLabel = versions[adoptedIdx]?.label;
|
||
grid.querySelectorAll('.asset-card[data-tri-version]').forEach(c => {
|
||
const pill = c.querySelector('.pill');
|
||
if (!pill) return;
|
||
const isAdopted = c.dataset.triVersion === adoptedLabel;
|
||
pill.className = 'pill ' + (isAdopted ? 'pass' : 'fail');
|
||
pill.textContent = isAdopted ? '通过' : '不通过';
|
||
pill.setAttribute('data-status', isAdopted ? 'pass' : 'fail');
|
||
pill.setAttribute('title', isAdopted ? '当前采用版本' : '未被采用');
|
||
});
|
||
refreshAssetFilters();
|
||
}
|
||
|
||
function appendLibraryCard(ver) {
|
||
const grid = document.querySelector('.tab-pane[data-pane="assets"] .asset-grid');
|
||
if (!grid) return;
|
||
const now = new Date();
|
||
const pad = n => String(n).padStart(2, '0');
|
||
const dateStr = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
||
const card = document.createElement('div');
|
||
card.className = 'asset-card';
|
||
card.dataset.triVersion = ver.label;
|
||
// 新生成的版本默认 `不通过`,等用户点「采用此版本」才转为通过
|
||
card.innerHTML = `
|
||
<div class="thumb placeholder"><span class="type-pill">三视图</span><span class="ph-frame">${prodName()} · ${ver.label}</span></div>
|
||
<div class="meta"><span class="pill fail" data-status="fail" title="未被采用">不通过</span><span class="date">${dateStr}</span></div>
|
||
`;
|
||
grid.prepend(card);
|
||
// 更新「全部 AI 素材 (N)」计数
|
||
const ct = document.querySelector('.pd-toolbar .total .ct');
|
||
if (ct) {
|
||
const m = ct.textContent.match(/(\d+)/);
|
||
const n = m ? Number(m[1]) + 1 : 1;
|
||
ct.textContent = `(${n})`;
|
||
}
|
||
refreshAssetFilters();
|
||
}
|
||
|
||
// 切换主图预览(不动采用状态、不动素材库)
|
||
function setPreview(idx) {
|
||
previewIdx = idx;
|
||
renderHistory();
|
||
renderMain();
|
||
}
|
||
|
||
// 显式「采用」当前预览版本 · 同步素材库通过/不通过
|
||
function adoptPreview() {
|
||
if (previewIdx < 0) return;
|
||
if (previewIdx === adoptedIdx) return;
|
||
adoptedIdx = previewIdx;
|
||
renderHistory();
|
||
renderMain();
|
||
syncLibraryStatus();
|
||
if (window.Shell?.toast) {
|
||
Shell.toast('已采用 ' + versions[adoptedIdx].label,
|
||
prodName() + ' · 该版本通过,其余版本转为不通过');
|
||
}
|
||
}
|
||
|
||
function renderLoading() {
|
||
img.innerHTML = `<div style="display:flex;flex-direction:column;gap:6px;align-items:center;"><div class="spinner"></div><span class="ph-frame" style="font-size:10.5px;">生成中</span></div>`;
|
||
img.classList.remove('is-zoomable');
|
||
img.removeAttribute('title');
|
||
statusEl.textContent = '生成中 · 约 12s';
|
||
foot.innerHTML = '<span class="mono" style="font-size:11px; color: var(--black-alpha-48);">// POST /assets/tri-view</span>';
|
||
}
|
||
|
||
function openLightbox() {
|
||
if (previewIdx < 0) return;
|
||
const ver = versions[previewIdx];
|
||
const isAdopted = previewIdx === adoptedIdx;
|
||
const lbImg = document.getElementById('ov-tri-lightbox-img');
|
||
const lbLabel = document.getElementById('ov-tri-lightbox-label');
|
||
const lbTag = document.getElementById('ov-tri-lightbox-tag');
|
||
const lbMeta = document.getElementById('ov-tri-lightbox-meta');
|
||
if (lbImg) lbImg.innerHTML = `<span class="ph-frame">${prodName()} · 三视图(正/侧/背) · ${ver.label}</span>`;
|
||
if (lbLabel) lbLabel.textContent = ver.label;
|
||
if (lbTag) {
|
||
lbTag.hidden = !isAdopted;
|
||
lbTag.textContent = '已采用';
|
||
}
|
||
if (lbMeta) lbMeta.textContent = `// 生成于 ${ver.ts}`;
|
||
window.Shell?.openModal?.('ov-tri-lightbox-bg');
|
||
}
|
||
|
||
function start() {
|
||
if (generating) return;
|
||
generating = true;
|
||
open();
|
||
renderLoading();
|
||
setTimeout(() => {
|
||
generating = false;
|
||
const now = new Date();
|
||
const ts = String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');
|
||
const newVer = { ts, label: 'v' + (versions.length + 1) };
|
||
versions.push(newVer);
|
||
appendLibraryCard(newVer);
|
||
const newIdx = versions.length - 1;
|
||
previewIdx = newIdx;
|
||
// 第一次生成 · 自动采用新版本(无选择可言);之后只切预览,不动采用
|
||
if (adoptedIdx === -1) {
|
||
adoptedIdx = newIdx;
|
||
syncLibraryStatus();
|
||
}
|
||
renderHistory();
|
||
renderMain();
|
||
if (window.Shell?.toast) {
|
||
const tip = (adoptedIdx === newIdx)
|
||
? `${newVer.label} · 已采用并同步到素材库`
|
||
: `${newVer.label} · 预览中 · 满意请点「采用此版本」`;
|
||
Shell.toast('三视图已生成', `${prodName()} · ${tip}`);
|
||
}
|
||
}, 1800);
|
||
}
|
||
|
||
btn.addEventListener('click', (e) => { e.stopPropagation(); toggle(); });
|
||
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
|
||
startBtn.addEventListener('click', (e) => { e.stopPropagation(); start(); });
|
||
pop.addEventListener('click', (e) => e.stopPropagation());
|
||
img.addEventListener('click', (e) => {
|
||
if (!img.classList.contains('is-zoomable')) return;
|
||
e.stopPropagation();
|
||
openLightbox();
|
||
});
|
||
document.addEventListener('click', (e) => {
|
||
if (!pop.classList.contains('show')) return;
|
||
if (pop.contains(e.target) || btn.contains(e.target)) return;
|
||
close();
|
||
});
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && pop.classList.contains('show')) close();
|
||
});
|
||
})();
|
||
|
||
// 从 create 跳来时显示 toast + 清空三个 tab 数据(新商品没有素材/项目/任务)
|
||
if (location.search.includes('id=new')) {
|
||
setTimeout(() => Shell.toast('商品已创建', '开始创建 AI 资产'), 200);
|
||
|
||
// 从 sessionStorage 读出 drawer 刚保存的完整 product,注入到「商品信息」卡
|
||
(function injectFromSession() {
|
||
let p = null;
|
||
try {
|
||
const raw = sessionStorage.getItem('npd-last-created');
|
||
if (raw) p = JSON.parse(raw);
|
||
sessionStorage.removeItem('npd-last-created'); // 读完即清,避免污染
|
||
} catch (e) { /* ignore */ }
|
||
if (!p) return;
|
||
|
||
const esc = s => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
|
||
// 商品名称(URL 已经设过,这里兜底)
|
||
if (p.name) {
|
||
const nm = document.querySelector('[data-field="name"] .v-static');
|
||
if (nm) nm.textContent = p.name;
|
||
const nmI = document.querySelector('[data-field="name"] .v-input');
|
||
if (nmI) nmI.value = p.name;
|
||
}
|
||
// 品类
|
||
if (p.cat) {
|
||
const catRow = document.querySelector('[data-field="cat"] .v-static');
|
||
if (catRow) catRow.textContent = p.cat;
|
||
const catSel = document.querySelector('[data-field="cat"] .v-select');
|
||
if (catSel) {
|
||
// 找到匹配 option 并 select;若没有则插入到首位
|
||
let matched = false;
|
||
[...catSel.options].forEach(o => { if (o.value === p.cat || o.textContent === p.cat) { o.selected = true; matched = true; } });
|
||
if (!matched) {
|
||
const opt = document.createElement('option');
|
||
opt.value = p.cat; opt.textContent = p.cat; opt.selected = true;
|
||
catSel.insertBefore(opt, catSel.firstChild);
|
||
}
|
||
}
|
||
}
|
||
// 目标人群(可空)
|
||
const tgtRow = document.querySelector('[data-field="target"] .v-static');
|
||
const tgtIn = document.querySelector('[data-field="target"] .v-input');
|
||
if (tgtRow) tgtRow.textContent = p.target || '—';
|
||
if (tgtIn) tgtIn.value = p.target || '';
|
||
// 卖点
|
||
if (Array.isArray(p.points)) {
|
||
const blStatic = document.querySelector('[data-field="bullets"] .v-static');
|
||
if (blStatic) {
|
||
blStatic.innerHTML = p.points.length
|
||
? p.points.map(t => `<span class="bullet">${esc(t)}</span>`).join('')
|
||
: '<span class="bullet" style="color:var(--black-alpha-48)">—</span>';
|
||
}
|
||
}
|
||
// 商品图片 — 替换 6 张占位为真实 dataUrl;数量按上传数定
|
||
const grid = document.getElementById('ov-images-grid');
|
||
const addBtn = document.getElementById('ov-img-add');
|
||
if (grid) {
|
||
// 移除现有所有 .thumb 占位(保留末尾 #ov-img-add)
|
||
[...grid.querySelectorAll('.thumb')].forEach(t => t.remove());
|
||
if (Array.isArray(p.images) && p.images.length) {
|
||
p.images.forEach(img => {
|
||
const t = document.createElement('div');
|
||
t.className = 'thumb';
|
||
t.style.cssText = 'background-image:url(' + img.dataUrl + ');background-size:cover;background-position:center;';
|
||
if (addBtn) grid.insertBefore(t, addBtn);
|
||
else grid.appendChild(t);
|
||
});
|
||
}
|
||
// 同步计数
|
||
const ct = document.querySelector('.ov-images-sub .sub-h .ct');
|
||
if (ct) ct.textContent = '(' + ((p.images || []).length) + ')';
|
||
}
|
||
})();
|
||
|
||
const EMPTY_HTML = (txt) => `<div class="empty-filter">// NO DATA<br><span style="margin-top:6px;display:inline-block">${txt}</span></div>`;
|
||
|
||
// AI 生成素材
|
||
const assetsPane = document.querySelector('.tab-pane[data-pane="assets"]');
|
||
if (assetsPane) {
|
||
const grid = assetsPane.querySelector('.asset-grid');
|
||
if (grid) grid.outerHTML = EMPTY_HTML('还没有 AI 素材,使用右上角「图片生成」开始创建');
|
||
const ct = assetsPane.querySelector('.total .ct');
|
||
if (ct) ct.textContent = '(0)';
|
||
const more = assetsPane.querySelector('.pd-more');
|
||
if (more) more.remove();
|
||
}
|
||
|
||
// 视频项目
|
||
const videosPane = document.querySelector('.tab-pane[data-pane="videos"]');
|
||
if (videosPane) {
|
||
const grid = videosPane.querySelector('.asset-grid');
|
||
if (grid) grid.outerHTML = EMPTY_HTML('还没有视频项目,前往工作台「新建项目」开始');
|
||
const ct = videosPane.querySelector('.total .ct');
|
||
if (ct) ct.textContent = '(0)';
|
||
const more = videosPane.querySelector('.pd-more');
|
||
if (more) more.remove();
|
||
}
|
||
|
||
// 任务记录
|
||
const tasksPane = document.querySelector('.tab-pane[data-pane="tasks"]');
|
||
if (tasksPane) {
|
||
tasksPane.querySelectorAll('.task-stat .v').forEach(el => {
|
||
const small = el.querySelector('small');
|
||
el.textContent = '0';
|
||
if (small) el.appendChild(small);
|
||
});
|
||
const tbl = tasksPane.querySelector('.task-table');
|
||
if (tbl) tbl.outerHTML = EMPTY_HTML('暂无任务记录');
|
||
const ct = tasksPane.querySelector('.total .ct');
|
||
if (ct) ct.textContent = '(0)';
|
||
const more = tasksPane.querySelector('.pd-more');
|
||
if (more) more.remove();
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|