feat(workbench): 三工序图片区视觉对齐 + 任务中心聚合 + 工具台头部筛选

- model-photo / platform-cover · 头部 toolbar 落地: 时间 / 模特(平台) chip 下拉 + 折叠搜索
- model-photo / platform-cover · 图片卡片样式同步图片创作 (.io-cell): bg / hover / .gen 脉冲 / .err 红框
- model-photo / platform-cover · 单图 hover overlay: 再次生成 + 下载 + 更多(加入资产库/删除)
- model-photo / platform-cover · 批次底栏: 再次生成图标统一 + 更多 menu(全部加入资产库/删除该批)
- model-photo · 修 TDZ bug: renderModelMini 调用挪到 MODELS 声明后, 解决整页崩溃
- model-photo · 去掉冗余 pv-summary, 商品自动选最近编辑, task 写入 name 字段
- image-optimize · 单图右上加再次生成图标, 加入 fs-image-tasks-image 与任务中心打通
- image-optimize · 输入区拆 3 行: + 在顶 / textarea 满宽 / 发送在底栏右; 参考图缩略与加号同 64×64
- asset-factory · 任务中心加时间 chip + image 类型 + 跳转表; 删冗余类型列
- pipeline · stage2 商品卡换商品库风格 + AI 生成三视图主 CTA + .tri-missing-badge[hidden] CSS 修复
This commit is contained in:
iye
2026-05-22 19:35:36 +08:00
parent f420af2069
commit 04335f3269
25 changed files with 13042 additions and 2700 deletions
+540 -5
View File
@@ -78,6 +78,9 @@
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 {
@@ -89,6 +92,154 @@
.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; }
@@ -771,6 +922,32 @@
<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.7" 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.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>
@@ -875,7 +1052,7 @@
</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>
@@ -896,7 +1073,7 @@
<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>
<button class="tab" type="button" data-tab="tasks" hidden>任务记录</button>
</div>
<!-- ===== AI 生成素材 ===== -->
@@ -1143,6 +1320,25 @@
</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/shell.js?v=202605211643"></script>
<script>
// 从 URL ?product= 读出商品名,注入 crumb / h1 / 商品名字段
@@ -1181,10 +1377,10 @@
document.querySelectorAll('.qa-item[data-go]').forEach(item => {
item.style.cursor = 'pointer';
let go = item.dataset.go;
// 图片优化暂复用 model-photo.html?mode=tri (后端实际生成人物 / 商品三视图)
// 图片创作 → 独立工作台 image-optimize.html(自由创作),带 product 作为提示词种子
let url;
if (go === 'image-optimize') {
url = 'model-photo.html?mode=tri&t=' + Date.now() + '&product=' + encodeURIComponent(name);
url = 'image-optimize.html?t=' + Date.now() + '&prompt=' + encodeURIComponent(name);
} else {
url = go + '.html?t=' + Date.now() + '&product=' + encodeURIComponent(name);
}
@@ -1669,9 +1865,348 @@
}
})();
// 从 create 跳来时显示 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 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.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 ${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 ? '当前采用版本' : '未被采用');
});
}
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})`;
}
}
// 切换主图预览(不动采用状态、不动素材库)
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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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>