@@ -945,7 +945,7 @@ Shell.render({ active: 'library', crumbs: [{ label: '工作台', href: 'index.ht
/* ─── 给所有资产卡注入下载按钮 · PRD §6.5 所有中间产物可下载 ─── */
(function injectDownloadBtns() {
- const dlSvg = '
';
document.querySelectorAll('.asset-card').forEach(card => {
if (card.querySelector('.card-dl-btn')) return;
const btn = document.createElement('button');
@@ -995,7 +995,7 @@ const TAB_KEYS = ['people', 'scenes', 'products', 'finals', 'uploads', 'unclassi
card.dataset.libId = it.id || '';
card.innerHTML = `
${esc(it.name || '未命名')}
@@ -1262,14 +1262,14 @@ function renderPagination(totalVisible, totalPages) {
const list = document.getElementById('page-list');
const items = pageNumberList(state.page, totalPages);
let html = `
`;
items.forEach(p => {
if (p === '…') html += `
…`;
else html += `
`;
});
html += `
`;
list.innerHTML = html;
}
@@ -1894,7 +1894,7 @@ function renderMoveMenu() {
bulkMoveMenu.innerHTML = TAB_KEYS
.filter(t => t !== cur)
.map(t => `
`).join('');
bulkMoveMenu.querySelectorAll('.mv-item').forEach(btn => {
@@ -1988,9 +1988,13 @@ document.querySelectorAll('.asset-card').forEach(card => {
.asset-detail-lead { display: flex; flex-direction: column; gap: 10px; }
.asset-detail-lead .ad-lead-wrap { position: relative; }
.asset-detail-lead .placeholder.ad-lead-img { aspect-ratio: 3/4; border-radius: var(--r-md); }
- .asset-detail-lead .ad-zoom-btn { position: absolute; right: 10px; bottom: 10px; height: 28px; padding: 0 12px; background: rgba(21,20,15,.7); color: #fff; border: 0; border-radius: var(--r-pill); display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-family: inherit; cursor: pointer; }
- .asset-detail-lead .ad-zoom-btn:hover { background: rgba(21,20,15,.9); }
- .asset-detail-lead .ad-zoom-btn svg { width: 12px; height: 12px; }
+ /* 查看大图 icon · 悬浮容器才显示 · 32×32 icon-only */
+ .ad-zoom-btn { position: absolute; right: 8px; bottom: 8px; width: 32px; height: 32px; padding: 0; background: rgba(21,20,15,.7); color: #fff; border: 0; border-radius: var(--r-sm); display: grid; place-items: center; cursor: pointer; backdrop-filter: blur(4px); opacity: 0; transition: opacity var(--t-base), background var(--t-base); z-index: 3; }
+ .ad-zoom-btn:hover { background: rgba(21,20,15,.92); }
+ .ad-zoom-btn svg { width: 14px; height: 14px; }
+ .asset-detail-lead .ad-lead-wrap:hover .ad-zoom-btn,
+ .asset-detail-tri-row .placeholder:hover .ad-zoom-btn { opacity: 1; }
+ .asset-detail-tri-row .placeholder { position: relative; }
.asset-detail-lead .ad-thumbs { display: flex; gap: 8px; }
.asset-detail-lead .ad-thumbs .thumb { flex: 0 0 64px; aspect-ratio: 3/4; border-radius: var(--r-sm); border: 1px solid var(--border-faint); cursor: pointer; overflow: hidden; }
.asset-detail-lead .ad-thumbs .thumb:hover { border-color: var(--heat-40); }
@@ -2019,13 +2023,38 @@ document.querySelectorAll('.asset-card').forEach(card => {
.ad-props .ad-prop .v { color: var(--accent-black); font-weight: 500; word-break: break-all; }
.asset-detail-tip { margin-top: 10px; padding: 10px 12px; background: var(--heat-12); border: 1px solid var(--heat-20); border-radius: var(--r-sm); font-size: 12px; color: var(--accent-black); display: flex; align-items: center; gap: 8px; line-height: 1.5; }
.asset-detail-tip svg { width: 14px; height: 14px; color: var(--heat); flex-shrink: 0; }
- .asset-detail-tip .ai-gen-btn { margin-left: auto; height: 26px; padding: 0 10px; background: var(--heat); color: #fff; border: 1px solid var(--heat); border-radius: var(--r-sm); font-size: 11.5px; cursor: pointer; font-family: inherit; flex-shrink: 0; }
+ .asset-detail-tip .ai-gen-btn { margin-left: auto; height: 26px; padding: 0 10px; background: var(--heat); color: #fff; border: 1px solid var(--heat); border-radius: var(--r-sm); font-size: 11.5px; cursor: pointer; font-family: inherit; flex-shrink: 0; display: inline-flex; align-items: center; }
+ .asset-detail-tip .ai-gen-btn:disabled { opacity: .55; cursor: not-allowed; }
+ .asset-detail-tip.is-loading svg { animation: ad-spin 1s linear infinite; }
+ @keyframes ad-spin { to { transform: rotate(360deg); } }
+ .asset-detail-history { margin-top: 10px; padding: 10px 12px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm); }
+ .asset-detail-history .adh-h { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-56); margin-bottom: 8px; letter-spacing: .02em; }
+ .asset-detail-history .adh-h .adh-cur { color: var(--heat); }
+ .asset-detail-history .adh-row { display: flex; gap: 8px; flex-wrap: wrap; }
+ .asset-detail-history .adh-thumb { width: 64px; height: 36px; border-radius: var(--r-sm); background: var(--background-base); border: 1px solid var(--border-faint); display: grid; place-items: center; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-72); cursor: pointer; position: relative; transition: border-color var(--t-base), color var(--t-base); }
+ .asset-detail-history .adh-thumb:hover { border-color: var(--heat-40); color: var(--heat); }
+ .asset-detail-history .adh-thumb.active { border-color: var(--heat); color: var(--heat); background: var(--heat-12); }
+ .asset-detail-history .adh-thumb.active::after { content: ""; position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; background: var(--heat); border: 2px solid var(--surface); border-radius: 50%; }
.asset-modal-f { padding: 14px 20px; border-top: 1px solid var(--border-faint); display: flex; align-items: center; gap: 8px; }
.asset-modal-f .ad-foot-stats { display: flex; gap: 6px; margin-right: auto; }
.asset-modal-f .ad-stat-btn { height: 32px; padding: 0 12px; display: inline-flex; align-items: center; gap: 6px; background: transparent; border: 1px solid var(--border-faint); border-radius: var(--r-sm); color: var(--black-alpha-72); font-size: 12.5px; font-family: inherit; cursor: pointer; }
.asset-modal-f .ad-stat-btn:hover { background: var(--background-lighter); color: var(--accent-black); border-color: var(--black-alpha-24); }
.asset-modal-f .ad-stat-btn svg { width: 13px; height: 13px; }
.asset-modal-f .ad-stat-btn b { color: var(--accent-black); font-weight: 600; }
+ /* ── 缺保存 · 二次确认弹窗(模仿 model-photo .mc-leave) ── */
+ .lib-confirm-bg { position: fixed; inset: 0; z-index: 1300; background: rgba(21,20,15,.42); display: none; align-items: center; justify-content: center; padding: 40px; }
+ .lib-confirm-bg.show { display: flex; }
+ .lib-confirm { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); width: 440px; max-width: 92vw; box-shadow: 0 24px 64px rgba(0,0,0,.16); overflow: hidden; }
+ .lib-confirm .lc-h { display: flex; align-items: center; gap: 10px; padding: 14px 20px 10px; }
+ .lib-confirm .lc-h .ic { width: 28px; height: 28px; display: grid; place-items: center; background: var(--heat-12); color: var(--heat); border-radius: var(--r-sm); flex-shrink: 0; }
+ .lib-confirm .lc-h .ic svg { width: 16px; height: 16px; }
+ .lib-confirm .lc-h h3 { font-size: 15px; font-weight: 600; color: var(--accent-black); margin: 0; }
+ .lib-confirm .lc-h .mono { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; }
+ .lib-confirm .lc-b { padding: 4px 20px 18px; font-size: 13px; line-height: 1.65; color: var(--black-alpha-72); }
+ .lib-confirm .lc-b b { color: var(--accent-black); font-weight: 600; }
+ .lib-confirm .lc-f { display: flex; align-items: center; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border-faint); background: var(--background-lighter); }
+ .lib-confirm .lc-f .spacer { flex: 1; }
+ .lib-confirm .lc-f .btn { height: 34px; padding: 0 14px; font-size: 13px; }
`;
const style = document.createElement('style');
style.textContent = css;
@@ -2044,9 +2073,8 @@ document.querySelectorAll('.asset-card').forEach(card => {
@@ -2054,23 +2082,30 @@ document.querySelectorAll('.asset-card').forEach(card => {
-
-
暂无三视图,建议用 AI 生成以保证多角度一致性
-
+
+
暂无三视图,建议用 AI 生成以保证多角度一致性
+
+
+
@@ -2083,11 +2118,30 @@ document.querySelectorAll('.asset-card').forEach(card => {
-
+
+
+
+
+
+
+
+
+
+
+
三视图尚未保存
+
// UNSAVED
+
+
+ 已生成 1 版三视图但尚未保存。直接退出会丢失这些版本,且当前资产仍标记为「缺三视图」。
+
+
+
+
+
`;
@@ -2105,6 +2159,86 @@ document.querySelectorAll('.asset-card').forEach(card => {
const tagsEl = document.getElementById('lib-detail-tags');
const propsEl = document.getElementById('lib-detail-props');
const tipEl = document.getElementById('lib-detail-tip');
+ const tipTextEl = document.getElementById('lib-detail-tip-text');
+ const aigenBtn = document.getElementById('lib-detail-aigen');
+ const aigenLabel = document.getElementById('lib-detail-aigen-label');
+ const historyEl = document.getElementById('lib-detail-history');
+ const historyRowEl = document.getElementById('lib-detail-history-row');
+ const historyCountEl = document.getElementById('lib-detail-history-count');
+ const historyCurEl = document.getElementById('lib-detail-history-cur');
+ const applyBtn = document.getElementById('lib-detail-apply');
+
+ // 当前打开资产的状态(仅 isActor + missing tri 时启用)
+ let _curCard = null;
+ let _curName = '';
+ let _versions = []; // [{ ts, label }]
+ let _curIdx = -1;
+ let _dirty = false; // 已生成但未保存
+ let _generating = false;
+ let _allowGen = false; // 是否启用生成入口(missing tri-view 才启用)
+
+ function _renderHistory() {
+ if (!_versions.length) { historyEl.style.display = 'none'; return; }
+ historyEl.style.display = 'block';
+ historyCountEl.textContent = _versions.length;
+ historyCurEl.textContent = _versions[_curIdx]?.label || '';
+ historyRowEl.innerHTML = _versions.map((v, i) =>
+ '
' + v.label + '
'
+ ).join('');
+ historyRowEl.querySelectorAll('.adh-thumb').forEach(el => {
+ el.addEventListener('click', () => {
+ const i = Number(el.dataset.idx);
+ if (i === _curIdx) return;
+ _curIdx = i;
+ _renderTriPreview();
+ _renderHistory();
+ });
+ });
+ }
+
+ function _renderTriPreview() {
+ if (_curIdx < 0) return;
+ const ver = _versions[_curIdx];
+ triEl.innerHTML = '
' + _curName + ' · 三视图(正/侧/背)· ' + ver.label + ' ';
+ triEl.querySelector('[data-zoom-tri]')?.addEventListener('click', e => {
+ e.stopPropagation();
+ if (window.Shell?._openLightbox) Shell._openLightbox('', _curName + ' · 三视图 · ' + ver.label);
+ });
+ }
+
+ function _renderTriLoading() {
+ triEl.innerHTML = '
';
+ }
+
+ function _setAigenLabel(text, loading) {
+ aigenLabel.textContent = text;
+ aigenBtn.disabled = !!loading;
+ tipEl.classList.toggle('is-loading', !!loading);
+ }
+
+ function _startGenerate() {
+ if (!_allowGen || _generating) return;
+ _generating = true;
+ _setAigenLabel(_versions.length ? '生成中…' : '生成中…', true);
+ _renderTriLoading();
+ setTimeout(() => {
+ _generating = false;
+ const now = new Date();
+ const ts = String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');
+ _versions.push({ ts, label: 'v' + (_versions.length + 1) });
+ _curIdx = _versions.length - 1;
+ _dirty = true;
+ _renderTriPreview();
+ _renderHistory();
+ // 第一次生成后,按钮文案 → 再次生成;并隐藏 tip 文案,只留按钮在右侧
+ tipEl.style.display = 'flex';
+ tipTextEl.textContent = '已生成 ' + _versions.length + ' 版三视图 · 不满意可重跑,保存后写入资产';
+ _setAigenLabel('再次生成', false);
+ if (window.Shell?.toast) {
+ Shell.toast('三视图已生成', _curName + ' · ' + _versions[_curIdx].label + ' · 满意请点「保存」');
+ }
+ }, 1500);
+ }
function _hash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0; return Math.abs(h); }
function _fmtAssetId(name, k) { return 'ASSET-20240520-' + (k === 'person' ? 'M' : k === 'scene' ? 'S' : 'P') + String(_hash(name) % 1000).padStart(3, '0'); }
@@ -2113,7 +2247,19 @@ document.querySelectorAll('.asset-card').forEach(card => {
function _fmtDl(name) { const n = 200 + _hash(name) % 1800; return n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n); }
function open(card) {
+ // 重置本次打开的状态
+ _curCard = card;
+ _versions = [];
+ _curIdx = -1;
+ _dirty = false;
+ _generating = false;
+ _allowGen = false;
+ historyEl.style.display = 'none';
+ tipEl.classList.remove('is-loading');
+ _setAigenLabel('AI 生成三视图', false);
+
const name = card.dataset.name || '资产';
+ _curName = name;
const used = card.dataset.used || '0';
const source = card.dataset.source || '平台预设';
let tagText = 'AI 素材', intro = '', tags = [], props = [], hasTri = false, isActor = false;
@@ -2163,51 +2309,142 @@ document.querySelectorAll('.asset-card').forEach(card => {
titleEl.textContent = name;
kindEl.textContent = '/ ' + tagText;
leadImg.innerHTML = '
' + name + '';
+ // 立绘 zoom 按钮(单次绑定 · 通过 name 闭包始终读最新 _curName)
+ const _leadZoomBtn = document.getElementById('lib-detail-lead-zoom');
+ if (_leadZoomBtn && !_leadZoomBtn.dataset.bound) {
+ _leadZoomBtn.dataset.bound = '1';
+ _leadZoomBtn.addEventListener('click', e => {
+ e.stopPropagation();
+ if (window.Shell?._openLightbox) Shell._openLightbox('', _curName || titleEl.textContent);
+ });
+ }
- thumbsEl.innerHTML = ['v1','v2','v3'].map((t, i) => `
${t}
`).join('');
- thumbsEl.querySelectorAll('.thumb').forEach(t => t.addEventListener('click', () => {
- thumbsEl.querySelectorAll('.thumb').forEach(x => x.classList.remove('active'));
- t.classList.add('active');
- }));
+ // 平台预设资产 · 仅 1 张缩略图(用户上传多张的场景在 pipeline 工作台详情中处理)
+ const _thumbLabel = card.dataset.sceneType ? '场景' : (isActor ? '立绘' : '主图');
+ thumbsEl.innerHTML = `
${_thumbLabel}
`;
+
+ // 卡片是否标注「缺三视图」(data-triview="0")
+ const cardMissingTri = card.dataset.triview === '0';
if (card.dataset.sceneType) {
triSection.style.display = 'none';
} else if (isActor) {
triSection.style.display = '';
triEl.classList.remove('actor');
- triEl.innerHTML = '
' + name + ' · 三视图 (正/侧/背)
';
ratioChip.textContent = '16:9';
- tipEl.style.display = 'none';
+ if (cardMissingTri) {
+ // 人物 · 缺三视图 → 显示生成入口
+ _allowGen = true;
+ triEl.innerHTML = '
';
+ tipTextEl.textContent = '暂无三视图,建议用 AI 生成以保证多角度一致性';
+ _setAigenLabel('AI 生成三视图', false);
+ tipEl.style.display = 'flex';
+ } else {
+ triEl.innerHTML = '
' + name + ' · 三视图 (正/侧/背) ';
+ tipEl.style.display = 'none';
+ }
} else {
triSection.style.display = '';
triEl.classList.remove('actor');
ratioChip.textContent = '16:9';
- if (hasTri) {
- triEl.innerHTML = '
' + name + ' · 三视图
';
+ if (hasTri && !cardMissingTri) {
+ triEl.innerHTML = '
';
tipEl.style.display = 'none';
} else {
+ // 商品/其他 · 缺三视图 → 同样启用生成入口
+ _allowGen = true;
triEl.innerHTML = '
';
+ tipTextEl.textContent = '暂无三视图,建议用 AI 生成以保证多角度一致性';
+ _setAigenLabel('AI 生成三视图', false);
tipEl.style.display = 'flex';
}
}
+ // 三视图 zoom 按钮 click
+ triEl.querySelector('[data-zoom-tri]')?.addEventListener('click', e => {
+ e.stopPropagation();
+ if (window.Shell?._openLightbox) Shell._openLightbox('', name + ' · 三视图');
+ });
+
introEl.textContent = intro || '暂无简介';
tagsEl.innerHTML = tags.map(t => '
' + t + '').join('') +
- '
';
+ '
';
propsEl.innerHTML = props.map(([k, v]) => '
' + k + '' + v + '
').join('');
bg.classList.add('show');
}
- function close() { bg.classList.remove('show'); }
+ // ── 二次确认弹窗 ──
+ const confirmBg = document.getElementById('lib-confirm-bg');
+ const confirmCountEl = document.getElementById('lib-confirm-count');
+ const confirmSaveBtn = document.getElementById('lib-confirm-save');
+ const confirmDiscardBtn = document.getElementById('lib-confirm-discard');
+ function _openConfirm() {
+ confirmCountEl.textContent = _versions.length;
+ confirmBg.classList.add('show');
+ confirmBg.setAttribute('aria-hidden', 'false');
+ }
+ function _closeConfirm() {
+ confirmBg.classList.remove('show');
+ confirmBg.setAttribute('aria-hidden', 'true');
+ }
+ confirmBg.addEventListener('click', e => { if (e.target === confirmBg) _closeConfirm(); });
+
+ function _doSave() {
+ if (_curCard) {
+ const badge = _curCard.querySelector('.tri-missing-badge');
+ if (badge) badge.remove();
+ _curCard.dataset.triview = '1';
+ }
+ _dirty = false;
+ Shell.toast('已保存', _curName + ' · 三视图(' + _versions[_curIdx].label + ')已写入资产');
+ }
+
+ function close(force) {
+ if (!force && _dirty) {
+ _openConfirm();
+ return;
+ }
+ bg.classList.remove('show');
+ _dirty = false;
+ }
bg.addEventListener('click', e => { if (e.target === bg) close(); });
- document.getElementById('lib-detail-x').addEventListener('click', close);
- document.getElementById('lib-detail-apply').addEventListener('click', () => {
- Shell.toast('已应用「' + titleEl.textContent + '」', '已加入当前项目');
- close();
+ document.getElementById('lib-detail-x').addEventListener('click', () => close());
+
+ applyBtn.addEventListener('click', () => {
+ if (_allowGen && _versions.length) {
+ _doSave();
+ close(true);
+ return;
+ }
+ if (_allowGen && !_versions.length) {
+ Shell.toast('请先生成三视图', '点击「AI 生成三视图」开始');
+ return;
+ }
+ Shell.toast('已保存', _curName);
+ close(true);
});
- document.getElementById('lib-detail-aigen').addEventListener('click', () => {
- Shell.toast('AI 生成三视图中', '约 12s · POST /assets/tri-view');
+
+ // 弹窗按钮 · 主按钮「保存并退出」/ 次按钮「退出」
+ confirmSaveBtn.addEventListener('click', () => {
+ if (_versions.length) _doSave();
+ _closeConfirm();
+ close(true);
+ });
+ confirmDiscardBtn.addEventListener('click', () => {
+ _dirty = false;
+ _closeConfirm();
+ close(true);
+ });
+
+ aigenBtn.addEventListener('click', _startGenerate);
+
+ // Esc · 若确认弹窗开着,先关确认;否则尝试关详情(经过 dirty 检查)
+ document.addEventListener('keydown', e => {
+ if (e.key !== 'Escape') return;
+ if (confirmBg.classList.contains('show')) { _closeConfirm(); return; }
+ if (!bg.classList.contains('show')) return;
+ close();
});
// 把所有 .asset-card 的旧 onclick="Shell.toast(...)" 清掉,改成 open(card)
diff --git a/电商AI平台/login.html b/电商AI平台/login.html
index 5c5562f..d882c01 100644
--- a/电商AI平台/login.html
+++ b/电商AI平台/login.html
@@ -107,7 +107,7 @@