1672 lines
64 KiB
HTML
1672 lines
64 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>商品详情 · 流·Studio</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/restraint.css?v=202605211643">
|
||
<style>
|
||
/* ─── 顶部 标题 + 状态 ─── */
|
||
.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-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; }
|
||
|
||
/* 字段 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; }
|
||
.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;
|
||
}
|
||
.ov-actions .qa-row-1 .qa-item { width: 100%; }
|
||
.qa-item {
|
||
display: flex; flex-direction: column; align-items: 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;
|
||
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: 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>
|
||
<!-- 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.7" 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.7" 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.6" 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.6" 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.6" 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.6" 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">任务记录</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.6"><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.6"><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>
|
||
<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">
|
||
<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" 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 pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-20 12:08</span></div></div>
|
||
<div class="asset-card"><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 pass" data-status="pass" title="点击切换状态">通过</span><span class="date">2026-05-19 10:24</span></div></div>
|
||
<div class="asset-card"><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 archive" data-status="archive" title="点击切换状态">归档</span><span class="date">2026-05-18 21:42</span></div></div>
|
||
<div class="asset-card"><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 fail" data-status="fail" title="点击切换状态">不通过</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>
|
||
|
||
<script src="assets/shell.js?v=202605211643"></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;
|
||
// 图片优化暂复用 model-photo.html?mode=tri (后端实际生成人物 / 商品三视图)
|
||
let url;
|
||
if (go === 'image-optimize') {
|
||
url = 'model-photo.html?mode=tri&t=' + Date.now() + '&product=' + 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();
|
||
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];
|
||
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:status': ['全部状态', '通过', '不通过', '归档'],
|
||
'videos:sort': ['最新导出', '最早导出'],
|
||
'tasks:type': ['全部类型', '模特上身图', '平台套图', '视频素材', '三视图'],
|
||
'tasks:status': ['全部状态', '已完成', '生成中', '排队中', '失败'],
|
||
'tasks:date': ['全部', '今天', '近 7 天', '近 30 天'],
|
||
'tasks:sort': ['提交时间倒序', '提交时间正序'],
|
||
};
|
||
// 任务行 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);
|
||
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 + '"]');
|
||
return !btn || 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);
|
||
} 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 = '';
|
||
}
|
||
}
|
||
})();
|
||
|
||
// 编辑商品信息 · 在卡片内 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('上传图片', '请选择本地图片 (占位)');
|
||
}
|
||
})();
|
||
|
||
// 从 create 跳来时显示 toast
|
||
if (location.search.includes('id=new')) {
|
||
setTimeout(() => Shell.toast('商品已创建', '开始创建 AI 资产'), 200);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|