更新电商AI平台原型交互

This commit is contained in:
iye
2026-05-25 19:12:56 +08:00
parent 04335f3269
commit 553014cc79
23 changed files with 6661 additions and 860 deletions
+84 -50
View File
@@ -890,28 +890,51 @@
color: var(--accent-crimson, #c43d3d);
background: rgba(196, 61, 61, .05);
}
/* 右上 hover 操作组 */
/* 右上 hover 操作组 · 同 spec §4.18 .gen-image-actions */
.pv-platform-section .ps-grid .mp-result .cell-ops {
position: absolute; top: 6px; right: 6px;
display: flex; gap: 4px;
position: absolute; top: 8px; right: 8px;
display: flex; gap: 2px;
padding: 2px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
box-shadow: 0 2px 8px rgba(0,0,0,.08);
opacity: 0;
transition: opacity var(--t-base);
z-index: 2;
}
.pv-platform-section .ps-grid .mp-result:hover .cell-ops { opacity: 1; }
.pv-platform-section .ps-grid .mp-result .cell-ops button {
width: 26px; height: 26px;
background: rgba(255, 255, 255, .92);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
color: var(--accent-black);
width: 28px; height: 28px;
background: transparent;
border: 0;
border-radius: 6px;
color: var(--black-alpha-56);
cursor: pointer;
display: grid; place-items: center;
backdrop-filter: blur(4px);
transition: border-color var(--t-base), color var(--t-base);
transition: background var(--t-base), color var(--t-base);
}
.pv-platform-section .ps-grid .mp-result .cell-ops button:hover { border-color: var(--heat); color: var(--heat); }
.pv-platform-section .ps-grid .mp-result .cell-ops button svg { width: 12px; height: 12px; }
.pv-platform-section .ps-grid .mp-result .cell-ops button:hover {
background: var(--black-alpha-4);
color: var(--accent-black);
}
.pv-platform-section .ps-grid .mp-result .cell-ops button svg { width: 14px; height: 14px; }
.pv-platform-section .ps-grid .mp-result.adopted .cell-ops .r-check { display: none; }
/* 中央反馈 toast · 同 spec §4.18 .gen-image-feedback */
.pv-platform-section .ps-grid .mp-result .cell-feedback {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
background: rgba(38, 38, 38, .88);
color: var(--accent-white);
border-radius: var(--r-md);
opacity: 0;
pointer-events: none;
transition: opacity .2s var(--t-base, ease);
z-index: 3;
}
.pv-platform-section .ps-grid .mp-result.show-feedback .cell-feedback { opacity: 1; }
.pv-platform-section .ps-grid .mp-result .cell-feedback svg { width: 20px; height: 20px; }
.pv-platform-section .ps-grid .mp-result .cell-feedback span { font-size: 12.5px; font-weight: 500; letter-spacing: .02em; }
.pv-platform-section .ps-grid .mp-result .cell-more-wrap { position: relative; }
.pv-platform-section .ps-grid .mp-result .cell-more-menu {
position: absolute; top: calc(100% + 4px); right: 0;
@@ -1008,25 +1031,24 @@
color: var(--black-alpha-48); letter-spacing: .02em;
}
.pc-pv-batch .summary b { color: var(--heat); font-weight: 700; }
/* 底栏按钮 · 同 spec §4.18 + image-optimize .io-msg-ops button */
.pc-pv-batch .pill-btn {
height: 34px;
padding: 0 18px;
height: 30px;
padding: 0 12px;
display: inline-flex; align-items: center; gap: 6px;
background: var(--surface);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-pill);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
color: var(--accent-black);
font-family: inherit; font-size: 12.5px;
cursor: pointer;
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
}
.pc-pv-batch .pill-btn:hover { background: var(--background-lighter); border-color: var(--black-alpha-24); }
.pc-pv-batch .pill-btn.primary {
background: var(--heat); color: #fff; border-color: var(--heat);
.pc-pv-batch .pill-btn:hover {
border-color: var(--heat-20); color: var(--heat); background: var(--heat-12);
}
.pc-pv-batch .pill-btn.primary:hover { filter: brightness(.94); }
.pc-pv-batch .pill-btn svg { width: 13px; height: 13px; }
.pc-pv-batch .pill-btn.icon { width: 34px; padding: 0; justify-content: center; }
.pc-pv-batch .pill-btn.icon { width: 30px; padding: 0; justify-content: center; }
/* 批次更多气泡 */
.pc-pv-batch .batch-more-wrap { position: relative; display: inline-flex; }
.pc-pv-batch .batch-more-menu {
@@ -1102,16 +1124,16 @@
<!-- 顶部 · 返回 + 折叠 (跟图片创作风格一致) -->
<div class="pc-side-top">
<button class="back-pill" type="button" onclick="history.length > 1 ? history.back() : location.href='asset-factory.html'" title="返回">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<span>返回</span>
</button>
<button class="fold" type="button" title="折叠侧栏" style="margin-left:auto">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
</button>
</div>
<div class="pc-ps-h">
<div class="pc-ps-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="ps-search-input" placeholder="搜索商品 / 分类">
</div>
</div>
@@ -1125,10 +1147,10 @@
</div>
<div class="pc-ps-list" id="ps-list"></div>
<button class="pc-ps-all" type="button" id="ps-all-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
<span>全部商品</span>
<span class="ct" id="ps-all-ct">0 个</span>
<svg style="margin-left:0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
<svg style="margin-left:0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</button>
</aside>
@@ -1144,7 +1166,7 @@
<span class="spacer"></span>
<div class="tb-search-wrap" id="pc-search-wrap">
<button class="search-btn" type="button" title="搜索批次/平台" id="pc-search-toggle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
</button>
<input type="text" class="tb-search-input" id="pc-search-input" placeholder="搜索批次/平台…" autocomplete="off">
</div>
@@ -1373,11 +1395,11 @@
<div class="pl-main">
<div class="pl-toolbar">
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="pl-search-input" placeholder="搜索商品名">
</div>
<button class="btn-new" type="button" id="pl-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
</div>
@@ -1518,10 +1540,10 @@ function renderProdLib() {
<div class="pl-card${_plDraft === p.id ? ' selected' : ''}" data-id="${p.id}">
<div class="pl-card-actions">
<button class="pl-act" type="button" data-edit="${p.id}" title="编辑商品" aria-label="编辑">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
</button>
<button class="pl-act danger" type="button" data-del="${p.id}" title="删除商品" aria-label="删除">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>
</button>
</div>
<div class="pl-check"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></div>
@@ -1584,9 +1606,9 @@ function renderProdImgs(n) {
if (ct) ct.textContent = n;
let html = '';
for (let i = 0; i < n; i++) {
html += `<div class="thumb"><span class="ph-frame">1:1</span><button class="rm" type="button" title="删除" data-idx="${i}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button></div>`;
html += `<div class="thumb"><span class="ph-frame">1:1</span><button class="rm" type="button" title="删除" data-idx="${i}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button></div>`;
}
html += `<div class="img-upload" id="pcf-img-add" title="上传图片"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>`;
html += `<div class="img-upload" id="pcf-img-add" title="上传图片"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>`;
grid.innerHTML = html;
grid.querySelectorAll('.thumb .rm').forEach(btn => {
btn.addEventListener('click', () => {
@@ -1621,7 +1643,7 @@ function openEditProductDrawer(id) {
li.innerHTML = `
<span class="num">${i + 1}</span>
<input value="${b.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
@@ -1654,7 +1676,7 @@ document.getElementById('pcf-add-input').addEventListener('keydown', e => {
li.innerHTML = `
<span class="num">0</span>
<input value="${v.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
@@ -1855,13 +1877,14 @@ function renderPreviewSections() {
}
// 渲染生成结果 (点立即生成时调,带 hover overlay + 批量 bar)
const RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>';
const RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>';
const ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
const CELL_RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>';
const CELL_DL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>';
const CELL_MORE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>';
const CELL_ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>';
const CELL_DEL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>';
const CELL_RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>';
const CELL_DL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>';
const CELL_MORE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>';
const CELL_ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>';
const CELL_DEL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>';
const CELL_EDIT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>';
let _batchSeq = 0;
function appendBatch(n, kind) {
@@ -1893,6 +1916,10 @@ function appendBatch(n, kind) {
<div class="mp-result gen" data-idx="${i}">
<div class="mp-r-thumb"><span class="ph-frame">${c.dataset.name}</span></div>
<span class="adopt-badge">已采用</span>
<div class="cell-feedback" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<span>已采用</span>
</div>
<div class="cell-ops">
<button class="r-rerun" type="button" title="再次生成" aria-label="再次生成">${CELL_RERUN_SVG}</button>
<button class="r-dl" type="button" title="下载" aria-label="下载">${CELL_DL_SVG}</button>
@@ -1908,14 +1935,14 @@ function appendBatch(n, kind) {
`).join('')}
</div>
<div class="pc-pv-batch batch-foot">
<button class="pill-btn edit-batch" type="button" title="重新编辑">
${CELL_EDIT_SVG}
<span>重新编辑</span>
</button>
<button class="pill-btn rerun-batch" type="button" title="再次生成这一批">
${CELL_RERUN_SVG}
<span>再次生成</span>
</button>
<button class="pill-btn primary adopt-batch" type="button" title="采用这一批">
${ADOPT_SVG}
<span>全部采用 · <span class="adopted">0</span>/<span class="total">${n}</span></span>
</button>
<div class="batch-more-wrap">
<button class="pill-btn icon batch-more" type="button" title="更多" aria-label="更多">${CELL_MORE_SVG}</button>
<div class="batch-more-menu" role="menu">
@@ -1962,17 +1989,22 @@ function appendBatch(n, kind) {
else updateBatchSummary();
Shell.toast('已删除');
}));
section.querySelector('.edit-batch').addEventListener('click', () => {
const form = document.querySelector('.pc-form');
if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' });
Shell.toast('重新编辑', '请在左侧调整平台 / 张数后再生成');
});
section.querySelector('.rerun-batch').addEventListener('click', () => {
appendBatch(n, 'rerun-all');
Shell.toast('再次生成', n + ' 张图重新生成中 · 新批次已追加');
});
section.querySelector('.adopt-batch').addEventListener('click', () => {
const _adoptAll = () => {
const cards = section.querySelectorAll('.mp-result:not(.adopted)');
if (!cards.length) { Shell.toast('该批次已全部采用'); return; }
cards.forEach(c => { c.classList.remove('gen'); c.classList.add('adopted'); });
updateBatchSummary();
Shell.toast('已全部采用', cards.length + ' 张图入对应商品的 AI 素材 · 扣 ¥' + (cards.length * UNIT_PRICE).toFixed(2));
});
Shell.toast('已全部加入资产库', cards.length + ' 张图入对应商品的 AI 素材 · 扣 ¥' + (cards.length * UNIT_PRICE).toFixed(2));
};
// 批次「更多」按钮 → 开/合 menu
const _bMoreBtn = section.querySelector('.batch-more');
const _bMoreWrap = section.querySelector('.batch-more-wrap');
@@ -1987,7 +2019,7 @@ function appendBatch(n, kind) {
section.querySelector('.batch-save-all').addEventListener('click', e => {
e.stopPropagation();
if (_bMoreWrap) _bMoreWrap.classList.remove('open');
section.querySelector('.adopt-batch').click();
_adoptAll();
});
section.querySelector('.batch-del').addEventListener('click', e => {
e.stopPropagation();
@@ -2016,7 +2048,9 @@ function adoptOne(card) {
if (!card || card.classList.contains('adopted')) return;
card.classList.remove('gen');
card.classList.add('adopted');
Shell.toast('已加入资产库', '入对应商品的 AI 素材 · 扣 ¥' + UNIT_PRICE.toFixed(2));
// spec §4.18 · 就地中央反馈
card.classList.add('show-feedback');
setTimeout(() => card.classList.remove('show-feedback'), 1500);
updateBatchSummary();
}
// 点击页面其它位置 → 关闭单图/批次 more menu