diff --git a/电商AI平台/library.html b/电商AI平台/library.html index df56a7f..ded2218 100644 --- a/电商AI平台/library.html +++ b/电商AI平台/library.html @@ -1566,7 +1566,7 @@ submitBtn.addEventListener('click', () => { card.dataset.source = '手动上传'; card.dataset.used = '0'; card.dataset.added = stamp; - card.setAttribute('onclick', `Shell.toast('查看资产', ${JSON.stringify(name)})`); + card.setAttribute('onclick', `if(!document.body.classList.contains('edit-mode')&&window.LibraryAssetDetailOpen){window.LibraryAssetDetailOpen(this)}else if(!document.body.classList.contains('edit-mode')){Shell.toast('查看资产', ${JSON.stringify(name)})}`); let metaText = ''; if (kind === 'people') { @@ -1979,6 +1979,7 @@ document.querySelectorAll('.asset-card').forEach(card => { .asset-modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 1000; display: none; align-items: center; justify-content: center; padding: 40px; } .asset-modal-bg.show { display: flex; } .asset-modal { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); width: min(1040px, 100%); max-height: calc(100vh - 80px); overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 16px 48px rgba(0,0,0,.18); } + .asset-modal.product-mode { width: min(1180px, 100%); } .asset-modal-h { display: flex; align-items: center; gap: 10px; padding: 14px 20px; border-bottom: 1px solid var(--border-faint); } .asset-modal-h h2 { font-size: 15px; font-weight: 600; } .asset-modal-h .ad-tag { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; } @@ -1997,9 +1998,11 @@ document.querySelectorAll('.asset-card').forEach(card => { .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 { flex: 0 0 64px; aspect-ratio: 3/4; border-radius: var(--r-sm); border: 1px solid var(--border-faint); cursor: pointer; overflow: hidden; background-size: cover; background-position: center; display: grid; place-items: center; } .asset-detail-lead .ad-thumbs .thumb:hover { border-color: var(--heat-40); } .asset-detail-lead .ad-thumbs .thumb.active { border-color: var(--heat); border-width: 2px; } + .asset-detail-lead .ad-thumbs .thumb.thumb-wide { aspect-ratio: 16/9; } + .asset-detail-lead .ad-thumbs .thumb .ph-frame { font-size: 10px; } .asset-detail-right .ad-section + .ad-section { margin-top: 18px; } .asset-detail-section-h { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: var(--accent-black); margin-bottom: 10px; } .asset-detail-section-h .ic { width: 14px; height: 14px; color: var(--heat); display: grid; place-items: center; } @@ -2022,6 +2025,91 @@ document.querySelectorAll('.asset-card').forEach(card => { .ad-props .ad-prop:nth-last-child(-n+3) { border-bottom: 0; } .ad-props .ad-prop .k { flex: 0 0 64px; color: var(--black-alpha-56); font-family: var(--font-mono); font-size: 11px; } .ad-props .ad-prop .v { color: var(--accent-black); font-weight: 500; word-break: break-all; } + .asset-detail-product[hidden], .asset-detail-right[hidden] { display: none !important; } + .asset-detail-product { min-width: 0; } + .asset-modal.product-mode .asset-modal-body { padding: 0; } + .asset-modal.product-mode .asset-detail-grid { grid-template-columns: 280px minmax(0, 1fr); gap: 0; min-height: 640px; } + .asset-modal.product-mode .asset-detail-lead { padding: 20px; border-right: 1px solid var(--border-faint); background: var(--background-lighter); gap: 16px; } + .asset-modal.product-mode .asset-detail-lead .placeholder.ad-lead-img { aspect-ratio: 1; background-color: var(--surface); } + .asset-modal.product-mode .asset-detail-lead .placeholder.ad-lead-img.tri-previewing { aspect-ratio: 16/9; background-color: var(--background-lighter); border-style: dashed; color: var(--black-alpha-64); } + .asset-modal.product-mode .asset-detail-lead .placeholder.ad-lead-img.tri-previewing .ph-frame { max-width: 92%; white-space: normal; line-height: 1.5; } + .asset-modal.product-mode .ad-thumbs { display: block; } + .product-side-info { display: grid; gap: 6px; } + .product-side-info .psi-name { font-size: 14px; font-weight: 600; color: var(--accent-black); line-height: 1.45; } + .product-side-info .psi-meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; line-height: 1.6; } + .product-side-nav { display: grid; gap: 6px; padding-top: 10px; border-top: 1px solid var(--border-faint); } + .product-side-nav .psn-item { width: 100%; height: 34px; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 10px; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--black-alpha-64); font: inherit; font-size: 12.5px; cursor: pointer; text-align: left; } + .product-side-nav .psn-item:hover { background: var(--black-alpha-4); color: var(--accent-black); } + .product-side-nav .psn-item.active { background: var(--heat-12); color: var(--heat); } + .product-side-nav .psn-item .ct { font-family: var(--font-mono); font-size: 10.5px; color: currentColor; opacity: .72; } + .product-gallery { padding: 22px 24px 28px; } + .product-gallery-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 20px; } + .product-gallery-head .pgh-title { font-size: 14px; font-weight: 600; color: var(--accent-black); line-height: 1.45; } + .product-gallery-head .pgh-sub { margin-top: 4px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; } + .product-gallery-head .pgh-actions { display: flex; gap: 8px; flex-shrink: 0; } + .product-gallery-head .pgh-actions .btn { height: 32px; } + .product-source-filter { display: flex; flex-wrap: wrap; gap: 8px; margin: -6px 0 20px; } + .product-source-filter .chip { height: 30px; padding: 0 12px; font-size: 12px; } + .product-block + .product-block { margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--border-faint); } + .product-block-h { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; } + .product-block-h .t { font-size: 13px; font-weight: 600; color: var(--accent-black); } + .product-block-h .m { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; } + .product-block-h .spacer { flex: 1; } + .product-block-h .mini-action { height: 26px; padding: 0 10px; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); color: var(--black-alpha-72); font-size: 11.5px; font-family: inherit; cursor: pointer; } + .product-block-h .mini-action:hover { background: var(--black-alpha-4); color: var(--accent-black); border-color: var(--black-alpha-24); } + .tri-header-actions { display: inline-flex; align-items: center; gap: 8px; } + .tri-header-actions[hidden] { display: none; } + .tri-header-actions .btn { height: 26px; padding: 0 10px; font-size: 11.5px; } + .product-media-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; } + .product-media-card { border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; background: var(--surface); cursor: pointer; transition: background var(--t-base), border-color var(--t-base); } + .product-media-card[hidden], .product-triview-card[hidden], .product-block[hidden] { display: none; } + .product-media-card:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); } + .product-media-card.active { border-color: var(--heat); box-shadow: 0 0 0 1px var(--heat) inset; } + .product-media-card .pm-img { aspect-ratio: 1; background: var(--background-lighter); background-size: cover; background-position: center; position: relative; display: grid; place-items: center; } + .product-media-card .pm-img.wide { aspect-ratio: 16/9; } + .product-media-card .pm-img .asset-badge { top: 6px; left: 6px; } + .product-media-card .pm-b { padding: 9px 10px 10px; min-width: 0; } + .product-media-card .pm-t { font-size: 12.5px; font-weight: 600; color: var(--accent-black); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .product-media-card .pm-m { margin-top: 3px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .product-triview-card { display: grid; grid-template-columns: minmax(0, 1fr) 220px; gap: 12px; align-items: stretch; border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px; background: var(--surface); cursor: pointer; } + .product-triview-card:hover { border-color: var(--black-alpha-24); background: var(--black-alpha-4); } + .product-triview-card.active { border-color: var(--heat); box-shadow: 0 0 0 1px var(--heat) inset; } + .product-triview-card.tri-pending { border-color: var(--heat-40); background: var(--heat-12); } + .product-triview-card .tri-preview { min-height: 132px; aspect-ratio: 16/9; border-radius: var(--r-md); background: var(--background-lighter); border: 1px solid var(--border-faint); display: grid; place-items: center; position: relative; overflow: hidden; } + .product-triview-card .tri-preview .ph-frame { font-size: 11px; } + .tri-meta-list { display: flex; flex-direction: column; gap: 8px; } + .tri-meta-list .tm-row { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; padding-bottom: 8px; border-bottom: 1px solid var(--border-faint); } + .tri-meta-list .tm-row:last-child { border-bottom: 0; padding-bottom: 0; } + .tri-meta-list .k { color: var(--black-alpha-56); font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .04em; } + .tri-meta-list .v { color: var(--accent-black); font-weight: 500; text-align: right; } + .tri-version-panel { margin-top: 10px; padding: 9px 10px 10px; border: 1px solid var(--border-faint); border-radius: var(--r-md); background: var(--background-lighter); overflow: hidden; } + .tri-version-panel + .product-triview-card { margin-top: 10px; } + .tri-version-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; } + .tri-version-head .t { font-size: 12.5px; font-weight: 600; color: var(--accent-black); } + .tri-version-head .m { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .04em; color: var(--black-alpha-48); } + .tri-version-list { display: flex; gap: 6px; overflow-x: auto; overflow-y: hidden; padding: 0 0 4px; scrollbar-width: thin; scrollbar-color: var(--black-alpha-24) transparent; } + .tri-version-list::-webkit-scrollbar { height: 6px; } + .tri-version-list::-webkit-scrollbar-track { background: transparent; } + .tri-version-list::-webkit-scrollbar-thumb { background: var(--black-alpha-12); border-radius: var(--r-pill); } + .tri-version-list:hover::-webkit-scrollbar-thumb { background: var(--black-alpha-24); } + .tri-version-item { flex: 0 0 132px; min-height: 44px; padding: 7px 8px; border: 1px solid var(--border-faint); border-radius: var(--r-md); background: var(--surface); color: var(--black-alpha-64); font: inherit; cursor: pointer; text-align: left; display: grid; gap: 3px; transition: background var(--t-base), border-color var(--t-base); } + .tri-version-item:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); color: var(--accent-black); } + .tri-version-item.active { border-color: var(--heat); box-shadow: 0 0 0 1px var(--heat) inset; color: var(--accent-black); } + .tri-version-item.current:not(.active) { border-color: var(--forest-bd); background: var(--forest-bg); } + .tri-version-item.pending { background: var(--heat-12); border-color: var(--heat-20); } + .tri-version-item .tv-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; } + .tri-version-item .tv-v { font-size: 12px; font-weight: 600; color: var(--accent-black); } + .tri-version-item .tv-tag { height: 17px; display: inline-flex; align-items: center; padding: 0 6px; border: 1px solid var(--border-faint); border-radius: var(--r-pill); font-size: 10px; white-space: nowrap; } + .tri-version-item .tv-tag.info { color: var(--heat); background: var(--heat-12); border-color: var(--heat-20); } + .tri-version-item .tv-tag.ok { color: var(--accent-forest); background: var(--forest-bg); border-color: var(--forest-bd); } + .tri-version-item .tv-tag.neutral { color: var(--black-alpha-56); background: var(--black-alpha-4); border-color: var(--border-faint); } + .tri-version-item .tv-meta { font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-48); letter-spacing: .02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + @media (max-width: 1100px) { + .asset-detail-grid { grid-template-columns: 280px 1fr; } + .product-media-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .product-triview-card { grid-template-columns: 1fr; } + .tri-version-item { flex-basis: 124px; } + } .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; display: inline-flex; align-items: center; } @@ -2080,7 +2168,7 @@ document.querySelectorAll('.asset-card').forEach(card => {
-
+
@@ -2114,6 +2202,7 @@ document.querySelectorAll('.asset-card').forEach(card => {
+
@@ -2149,10 +2238,13 @@ document.querySelectorAll('.asset-card').forEach(card => { document.body.insertAdjacentHTML('beforeend', modalHTML); const bg = document.getElementById('lib-detail-bg'); + const modalEl = bg.querySelector('.asset-modal'); const titleEl = document.getElementById('lib-detail-title'); const kindEl = document.getElementById('lib-detail-kind'); const leadImg = document.getElementById('lib-detail-lead-img'); const thumbsEl = document.getElementById('lib-detail-thumbs'); + const genericPane = document.getElementById('lib-detail-generic-pane'); + const productPane = document.getElementById('lib-detail-product-pane'); const triSection = document.getElementById('lib-detail-tri-section'); const triEl = document.getElementById('lib-detail-tri'); const ratioChip = document.getElementById('lib-detail-ratio'); @@ -2178,6 +2270,406 @@ document.querySelectorAll('.asset-card').forEach(card => { let _generating = false; let _allowGen = false; // 是否启用生成入口(missing tri-view 才启用) + function _esc(s) { + return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + } + + function _imgStyle(src) { + return src ? ' style="background-image:url(' + _esc(src) + ')"' : ''; + } + + function _productInfo(productName, assetName) { + const key = productName + ' ' + assetName; + const map = [ + { test: /面膜|补水/i, product: '透真玻尿酸补水面膜', cat: '美妆个护', sku: 'SKU-MASK-0515', hero: 'assets/mock/product-mask.png', tone: '敏感肌 · 熬夜修护' }, + { test: /防晒/i, product: '透真清透物理防晒霜', cat: '美妆个护', sku: 'SKU-SUN-0508', hero: 'assets/mock/product-sunscreen.png', tone: '通勤防晒 · 物理防护' }, + { test: /耳机|南卡/i, product: '南卡 Lite Pro 蓝牙耳机', cat: '数码 3C', sku: 'SKU-EAR-0512', hero: 'assets/mock/product-earbuds.png', tone: '通勤降噪 · 长续航' }, + { test: /速食|牛肉面|面/i, product: '滋啦速食牛肉面 · 6 桶装', cat: '食品饮料', sku: 'SKU-NOODLE-0510', hero: 'assets/mock/product-noodle.png', tone: '加班夜宵 · 热汤治愈' }, + { test: /咖啡|三顿半/i, product: '三顿半同款冻干咖啡粉', cat: '食品饮料', sku: 'SKU-COFFEE-0505', hero: 'assets/mock/product-coffee.png', tone: '早八提神 · 冷萃口感' }, + { test: /炸锅|小熊/i, product: '小熊 4L 可视空气炸锅', cat: '家居家电', sku: 'SKU-FRYER-0503', hero: 'assets/mock/product-air-fryer.png', tone: '小户型 · 低脂快手' }, + { test: /瑜伽|露露/i, product: '露露同款裸感瑜伽裤', cat: '运动户外', sku: 'SKU-YOGA-0430', hero: 'assets/mock/product-yoga-pants.png', tone: '通勤运动 · 裸感高弹' }, + ]; + return map.find(x => x.test.test(key)) || { product: productName || assetName || '未绑定商品', cat: '商品图', sku: 'SKU-ASSET-' + (_hash(assetName || productName) % 1000), hero: '', tone: '商品素材 · 待补充标签' }; + } + + function _assetKind(name, source) { + if (/三视图|正.?侧.?背/.test(name)) return '商品三视图'; + if (/AI|生成|优化/.test(source) || /AI|优化|套图|场景/.test(name)) return 'AI 生成图'; + if (/上传|官方/.test(source) || /官方|实拍|原图/.test(name)) return '上传图'; + return '商品详情图'; + } + + function _productMediaCard(item, idx) { + return `
+
+ ${_esc(item.badge)} + ${item.img ? '' : '' + _esc(item.frame || item.title) + ''} +
+
+
${_esc(item.title)}
+
${_esc(item.meta)}
+
+
`; + } + + function _renderProductDetail(card, name, source, used) { + const productName = card.dataset.product || name.split('·')[0].trim() || name; + const info = _productInfo(productName, name); + const currentKind = _assetKind(name, source); + const hero = info.hero; + const detailImages = [ + { sourceKey: 'detail', badge: '主图', title: '官方主图', meta: '商品详情页 · 1:1', img: hero, frame: info.product }, + { sourceKey: 'detail', badge: '细节', title: '包装细节', meta: '商品详情页 · 4:3', img: hero, frame: '包装细节' }, + { sourceKey: 'detail', badge: '场景', title: '使用场景', meta: '商品详情页 · 3:4', img: hero, frame: '使用场景' }, + { sourceKey: 'detail', badge: '当前', title: name, meta: source + ' · 当前查看', img: hero, frame: name }, + ]; + const triImage = { sourceKey: 'tri', badge: '三视图', title: '正 / 侧 / 背合图', meta: '16:9 · 当前采用 v2', img: '', wide: true, frame: info.product + ' · 商品三视图' }; + const aiImages = [ + { sourceKey: 'tryon', badge: '模特上身', title: 'Ava · 试用图', meta: '图片生成 · 已入库', img: hero, frame: '模特上身图' }, + { sourceKey: 'platform', badge: '平台头图', title: '小红书首图', meta: '图片生成 · 3:4', img: hero, frame: '平台头图' }, + { sourceKey: 'creation', badge: '图片创作', title: '卖点海报', meta: '图片生成 · 1:1', img: hero, frame: '卖点海报' }, + ]; + const allMedia = [...detailImages, triImage, ...aiImages]; + const triIndex = detailImages.length; + let triVersion = 2; + let triVersionSeq = triVersion; + let triGeneratingVersion = null; + let triViewingVersion = triVersion; + let triHistory = [ + { version: 2, state: 'current', date: '2026-05-27', source: 'image-2 · 商品库生成' }, + { version: 1, state: 'history', date: '2026-05-19', source: '商品详情页补录' }, + ]; + + leadImg.style.backgroundImage = hero ? `url("${hero}")` : ''; + leadImg.style.backgroundSize = 'cover'; + leadImg.style.backgroundPosition = 'center'; + leadImg.innerHTML = hero ? '' : '' + _esc(name) + ''; + thumbsEl.innerHTML = ` +
+
${_esc(info.product)}
+
// ${_esc(info.cat)} · ${_esc(info.tone)}
+
// 共 ${allMedia.length} 张 · 商品详情图 ${detailImages.length} · 三视图 1 · AI 生成图 ${aiImages.length}
+
+
+ + + +
`; + + titleEl.textContent = info.product + ' · 商品图片'; + kindEl.textContent = '/ 商品图'; + productPane.innerHTML = ` + + `; + + function triVersionInfo(version) { + const row = triHistory.find(v => v.version === version) || triHistory.find(v => v.state === 'current') || triHistory[0]; + const isGenerating = row.state === 'generating'; + const isCurrent = row.state === 'current' || row.version === triVersion; + const stateText = isGenerating ? '生成中' : (isCurrent ? '当前采用' : '可切换采用'); + const tag = isGenerating ? 'info' : (isCurrent ? 'ok' : ''); + const suffix = isGenerating ? ' 生成中' : (isCurrent ? ' 当前采用' : ''); + return { + ...row, + isGenerating, + isCurrent, + isAdoptable: !isGenerating && !isCurrent, + stateText, + tag, + versionText: `v${row.version} · ${row.date}`, + frame: `${info.product} · 三视图(正 / 侧 / 背) · v${row.version}${suffix}`, + }; + } + + function renderTriVersionHistory() { + const historyEl = productPane.querySelector('[data-tri-history]'); + if (!historyEl) return; + const rows = [...triHistory].sort((a, b) => b.version - a.version).map(row => triVersionInfo(row.version)); + historyEl.innerHTML = rows.map(row => ` + + `).join(''); + historyEl.querySelectorAll('[data-tri-version]').forEach(btn => { + btn.addEventListener('click', e => { + e.stopPropagation(); + viewTriVersion(Number(btn.dataset.triVersion)); + }); + }); + historyEl.querySelector('.tri-version-item.active')?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + } + + function renderTriPreview() { + const triCard = productPane.querySelector('.product-triview-card'); + const headerActions = productPane.querySelector('.tri-header-actions'); + if (!triCard) return; + const preview = triCard.querySelector('.tri-preview'); + const meta = triCard.querySelector('.tri-meta-list'); + const view = triVersionInfo(triViewingVersion); + triCard.classList.toggle('tri-pending', view.isGenerating); + if (headerActions) headerActions.hidden = !view.isAdoptable; + preview.innerHTML = view.isGenerating + ? '
三视图生成中 · 约 12s
' + : `${_esc(view.frame)}`; + meta.innerHTML = ` +
状态${_esc(view.stateText)}
+
版本${_esc(view.versionText)}
+
用途视频镜头一致性 / 商品角度参考
+
来源${_esc(view.source)}
`; + } + + function viewTriVersion(version) { + triViewingVersion = version; + renderTriPreview(); + renderTriVersionHistory(); + selectProductMedia(triIndex); + } + + function renderTriState(mode) { + const triCard = productPane.querySelector('.product-triview-card'); + const regenBtn = productPane.querySelector('[data-product-action="regen-tri"]'); + if (!triCard) return; + if (regenBtn) { + const hasExtraVersion = triHistory.some(row => row.state === 'candidate' || row.state === 'generating'); + regenBtn.disabled = Boolean(triGeneratingVersion); + regenBtn.textContent = triGeneratingVersion ? '生成中...' : (hasExtraVersion ? '再次生成' : '重新生成'); + } + renderTriPreview(); + renderTriVersionHistory(); + } + + function selectProductMedia(idx) { + const item = allMedia[idx] || allMedia[0]; + const isTri = item.sourceKey === 'tri'; + const triView = triVersionInfo(triViewingVersion); + const triLabel = triView.isGenerating + ? info.product + ' · 三视图生成中' + : info.product + ' · 三视图 · v' + triView.version + (triView.isCurrent ? ' 当前采用' : ''); + leadImg.classList.toggle('tri-previewing', isTri && !item.img); + if (!item.img) { + leadImg.classList.remove('has-mock-media', 'mock-label'); + leadImg.style.removeProperty('--mock-media-url'); + leadImg.dataset.mockMediaApplied = '1'; + } + leadImg.style.backgroundImage = item.img ? `url("${item.img}")` : ''; + leadImg.innerHTML = item.img ? '' : '' + _esc(isTri ? triLabel : (item.frame || item.title)) + ''; + productPane.querySelectorAll('[data-product-media]').forEach(x => x.classList.toggle('active', Number(x.dataset.productMedia) === idx)); + } + + function openProductUpload() { + resetUploadModal(); + uploadState.kind = 'products'; + kindSel.value = 'products'; + syncKindFields(); + const productSelect = $('upload-product'); + const matchedOption = [...productSelect.options].find(opt => { + const text = opt.textContent.trim(); + return text && (info.product.includes(text) || text.includes(info.product.replace(/^透真玻尿酸/, '透真').replace(/ ·.*/, ''))); + }); + if (matchedOption) productSelect.value = matchedOption.value; + nameInput.value = info.product + ' · 补充图'; + syncSubmit(); + modalBg.style.zIndex = '1300'; + Shell.openModal('upload-modal-bg'); + setTimeout(() => fileInput.click(), 120); + } + + function startTriRegenerate() { + if (triGeneratingVersion) return; + const nextVersion = ++triVersionSeq; + triGeneratingVersion = nextVersion; + triViewingVersion = nextVersion; + triHistory = [ + { version: nextVersion, state: 'generating', date: '生成中', source: 'image-2 · 资产库重跑' }, + ...triHistory.filter(row => row.version !== nextVersion), + ]; + renderTriState('loading'); + selectProductMedia(triIndex); + setTimeout(() => { + const generated = triHistory.find(row => row.version === nextVersion); + if (!generated) return; + generated.state = 'candidate'; + generated.date = '刚刚生成'; + triGeneratingVersion = null; + triViewingVersion = nextVersion; + triImage.title = '正 / 侧 / 背合图 v' + nextVersion; + triImage.meta = '16:9 · v' + nextVersion; + triImage.frame = info.product + ' · 商品三视图 · v' + nextVersion; + renderTriState('pending'); + selectProductMedia(triIndex); + Shell.toast('三视图已生成', 'v' + nextVersion + ' 已加入版本记录,可切换采用'); + }, 1200); + } + + function adoptTriVersion() { + const selected = triHistory.find(row => row.version === triViewingVersion); + if (!selected || selected.state === 'generating' || selected.version === triVersion) return; + triHistory = triHistory.map(row => { + if (row.version === selected.version) return { ...row, state: 'current' }; + if (row.state === 'generating') return row; + return { ...row, state: 'candidate' }; + }); + triVersion = selected.version; + triVersionSeq = Math.max(triVersionSeq, triVersion); + triViewingVersion = triVersion; + triImage.title = '正 / 侧 / 背合图 v' + triVersion; + triImage.meta = '16:9 · 当前采用 v' + triVersion; + triImage.frame = info.product + ' · 商品三视图 · v' + triVersion; + renderTriState('current'); + selectProductMedia(triIndex); + Shell.toast('已采用此版本三视图', info.product + ' · v' + triVersion); + } + + function applyProductSourceFilter(sourceKey) { + const source = sourceKey || 'all'; + productPane.querySelectorAll('[data-product-media]').forEach(el => { + el.hidden = source !== 'all' && el.dataset.productSource !== source; + }); + productPane.querySelectorAll('.product-block').forEach(block => { + const media = [...block.querySelectorAll('[data-product-media]')]; + block.hidden = media.length > 0 && media.every(el => el.hidden); + }); + const firstVisible = productPane.querySelector('[data-product-media]:not([hidden])'); + if (firstVisible) selectProductMedia(Number(firstVisible.dataset.productMedia)); + } + + thumbsEl.querySelectorAll('[data-product-jump]').forEach(el => { + el.addEventListener('click', () => { + const target = productPane.querySelector('#' + el.dataset.productJump); + thumbsEl.querySelectorAll('.psn-item').forEach(x => x.classList.remove('active')); + el.classList.add('active'); + target?.scrollIntoView({ block: 'start', behavior: 'smooth' }); + }); + }); + + productPane.querySelectorAll('[data-product-media]').forEach(el => { + el.addEventListener('click', () => { + const idx = Number(el.dataset.productMedia); + selectProductMedia(idx); + const item = allMedia[idx]; + Shell.toast('已选中图片', item ? item.title : name); + }); + }); + renderTriState('current'); + selectProductMedia(0); + + productPane.querySelectorAll('[data-product-filter]').forEach(btn => { + btn.addEventListener('click', () => { + productPane.querySelectorAll('[data-product-filter]').forEach(x => x.classList.remove('active')); + btn.classList.add('active'); + applyProductSourceFilter(btn.dataset.productFilter); + }); + }); + + function bindProductActions() { + productPane.querySelectorAll('[data-product-action]').forEach(btn => { + if (btn.dataset.bound === '1') return; + btn.dataset.bound = '1'; + btn.addEventListener('click', e => { + e.stopPropagation(); + const act = btn.dataset.productAction; + if (act === 'open-product') { + location.href = 'product-detail.html?product=' + encodeURIComponent(info.product); + return; + } + if (act === 'upload-more') { + openProductUpload(); + return; + } + if (act === 'new-video') { + location.href = 'projects-new.html?product=' + encodeURIComponent(info.product); + return; + } + if (act === 'go-generate') { + location.href = 'image-optimize.html?product=' + encodeURIComponent(info.product); + return; + } + if (act === 'regen-tri') { + startTriRegenerate(); + return; + } + if (act === 'adopt-tri') { + adoptTriVersion(); + return; + } + Shell.toast('已记录', info.product); + }); + }); + } + bindProductActions(); + } + function _renderHistory() { if (!_versions.length) { historyEl.style.display = 'none'; return; } historyEl.style.display = 'block'; @@ -2258,13 +2750,32 @@ document.querySelectorAll('.asset-card').forEach(card => { historyEl.style.display = 'none'; tipEl.classList.remove('is-loading'); _setAigenLabel('AI 生成三视图', false); + modalEl.classList.remove('product-mode'); + genericPane.hidden = false; + productPane.hidden = true; + leadImg.style.backgroundImage = ''; + leadImg.style.backgroundSize = ''; + leadImg.style.backgroundPosition = ''; + applyBtn.textContent = '保存'; const name = card.dataset.name || '资产'; _curName = name; const used = card.dataset.used || '0'; const source = card.dataset.source || '平台预设'; + const isProductAsset = !!card.dataset.product || card.dataset.kind === '商品'; let tagText = 'AI 素材', intro = '', tags = [], props = [], hasTri = false, isActor = false; + if (isProductAsset) { + titleEl.textContent = name; + modalEl.classList.add('product-mode'); + genericPane.hidden = true; + productPane.hidden = false; + applyBtn.textContent = '完成'; + _renderProductDetail(card, name, source, used); + bg.classList.add('show'); + return; + } + if (card.dataset.gender) { tagText = '人物 · 模特'; isActor = true; hasTri = true; @@ -2309,6 +2820,7 @@ document.querySelectorAll('.asset-card').forEach(card => { titleEl.textContent = name; kindEl.textContent = '/ ' + tagText; + leadImg.style.backgroundImage = ''; leadImg.innerHTML = '' + name + ''; // 立绘 zoom 按钮(单次绑定 · 通过 name 闭包始终读最新 _curName) const _leadZoomBtn = document.getElementById('lib-detail-lead-zoom'); @@ -2385,6 +2897,7 @@ document.querySelectorAll('.asset-card').forEach(card => { bg.classList.add('show'); } + window.LibraryAssetDetailOpen = open; // ── 二次确认弹窗 ── const confirmBg = document.getElementById('lib-confirm-bg'); const confirmCountEl = document.getElementById('lib-confirm-count'); diff --git a/电商AI平台/projects-new.html b/电商AI平台/projects-new.html index a0c9e64..a0c3126 100644 --- a/电商AI平台/projects-new.html +++ b/电商AI平台/projects-new.html @@ -442,8 +442,16 @@ /* ── shared field styles ── */ .field { display: block; margin-bottom: 16px; } .config-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: end; margin-bottom: 16px; } + .config-row.single { grid-template-columns: minmax(0, 1fr); } .config-row .field { margin-bottom: 0; } - .duration-select { cursor: pointer; } + .duration-card-row { grid-template-columns: repeat(4, minmax(0, 1fr)); } + .duration-card { width: 100%; min-height: 108px; text-align: left; font-family: inherit; } + .duration-card .duration-title { font-size: 14px; font-weight: 600; color: var(--accent-black); } + .duration-card .duration-meta { margin-top: 5px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; } + .duration-card .duration-note { margin-top: auto; padding-top: 10px; font-size: 11.5px; color: var(--black-alpha-56); line-height: 1.45; } + .duration-card.selected .duration-title, + .duration-card.selected .duration-meta { color: var(--heat); } + @media (max-width: 1100px) { .duration-card-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 900px) { .config-row { grid-template-columns: 1fr; } } .field-label { display: block; font-size: 12.5px; color: var(--black-alpha-56); font-weight: 500; margin-bottom: 6px; } .field-label .req { color: var(--heat); margin-left: 2px; } @@ -654,10 +662,10 @@ ]; const DURATIONS = [ - { id: '0-10', label: '0-10 秒', shots: [3, 4], tag: '黄金完播', completion: 52, conversion: 1.6 }, - { id: '0-15', label: '0-15 秒', shots: [4, 5], tag: '完播率最佳', completion: 42, conversion: 1.8 }, - { id: '0-30', label: '0-30 秒', shots: [6, 8], tag: '卖点详解', completion: 32, conversion: 2.1 }, - { id: '0-60', label: '0-60 秒', shots: [10, 12], tag: '故事化', completion: 26, conversion: 2.4 }, + { id: '0-10', label: '0-10 秒', shots: [3, 4], tag: '快速种草' }, + { id: '0-15', label: '0-15 秒', shots: [4, 5], tag: '标准短片' }, + { id: '0-30', label: '0-30 秒', shots: [6, 8], tag: '卖点展开' }, + { id: '0-60', label: '0-60 秒', shots: [10, 12], tag: '故事化' }, ]; const STYLES = [ @@ -775,7 +783,7 @@ if (s.id === 'manual') return state.manualScript.trim().length >= 20; return true; } - function canPass3() { return state.projectName.trim().length >= 2; } + function canPass3() { return state.projectName.trim().length >= 2 && !!state.duration; } function canFinish() { return canPass1() && canPass2() && canPass3() && state.agreed && balanceAfter() >= 5; } /* ---------- icons ---------- */ @@ -839,8 +847,8 @@ } function startGenerate() { - const p = getProduct(), d = getDuration(), st = getStyle(); - if (!p || !d || !st || state.projectName.trim().length < 2) return; + const p = getProduct(), d = getDuration(); + if (!p || !d || state.projectName.trim().length < 2) return; // 持久化项目, 让 projects.html 下次加载时自动 prepend 到列表 try { const seconds = (d.id.split('-')[1] || '15'); @@ -1001,11 +1009,11 @@ ============================================================ */ function railConfig() { - const p = getProduct(), du = getDuration(), st = getStyle(); - const cfgReady = !!(du && st); + const p = getProduct(), du = getDuration(); + const cfgReady = !!du && canPass3(); return [ { n: 1, label: '选择商品', desc: p ? p.name : '未选择', done: canPass1() }, - { n: 2, label: '项目配置', desc: cfgReady ? (du.label + ' · ' + st.name) : '时长 · 风格 · 人物', done: cfgReady && canPass3() }, + { n: 2, label: '项目配置', desc: cfgReady ? du.label : '项目名 · 视频时长', done: cfgReady }, ]; } @@ -1221,67 +1229,31 @@ } function renderStep3() { - const personaObj = getPersona(), durObj = getDuration(), styleObj = getStyle(); - let showReco = false, recoDur = null, recoStyle = null; - if (personaObj && state.duration && state.scriptStyle) { - const recoMismatch = personaObj.defaults.duration !== state.duration || personaObj.defaults.style !== state.scriptStyle; - showReco = recoMismatch && !state.recoDismissed; - recoDur = DURATIONS.find(d => d.id === personaObj.defaults.duration); - recoStyle = STYLES.find(s => s.id === personaObj.defaults.style); - } - return `

第 2 步 · 项目配置

-

这些设置会影响 LLM 生成脚本的方向,确认后会进入流水线第 1 步(脚本生成)。

+

基础配置只保留项目名和成片时长。脚本来源、风格和人物设定已移到流水线第 1 步由脚本助手引导。

-
+
-
- - -
- -
- ${STYLES.map(s => `
-

${esc(s.name)}

-
${esc(s.note)}
- ${s.tag ? `[ ${esc(s.tag)} ]` : ''} -
`).join('')} + +
+ ${DURATIONS.map(d => ``).join('')}
-
- -
- ${PERSONAS.map(p => `
-

${esc(p.name)}

-
${esc(p.sub)}
-
${esc(p.metric)}
-
`).join('')} -
- - ${showReco ? `
- ${ICONS.bulb} -
- 抖音同人设 TOP 视频更常用 ${esc(recoDur.label)} + ${esc(recoStyle.name)} - 当前 ${esc(durObj.label)} · ${esc(styleObj.name)} → 推荐换为同人设最优组合 -
- - -
` : ''} -
- ${Object.keys(state.points).length > 0 ? `
@@ -1292,7 +1264,7 @@ } function renderStep4() { - const p = getProduct(), s = getSource(), personaObj = getPersona(), durObj = getDuration(), styleObj = getStyle(); + const p = getProduct(), s = getSource(), durObj = getDuration(); const c = getCost(); const ba = balanceAfter(); const low = ba < 5; @@ -1331,7 +1303,7 @@
// 项目配置
${esc(state.projectName)}
-
${esc(styleObj.name)} · ${esc(personaObj.name)} · ${esc(personaObj.sub)}
+
${esc(durObj.label)} · ${durObj.shots[0]}-${durObj.shots[1]} 镜 · 9:16
卖点:${esc(pointsList)}
@@ -1340,8 +1312,7 @@
// 输出参数
${esc(durObj.label)} · ${durObj.shots[0]}-${durObj.shots[1]} 镜 · 9:16
-
预估完播 ${durObj.completion}% · 预估转化 ${durObj.conversion}%
-
// 数据来源:抖音同品类 TOP 均值
+
// 进入 Stage 1 后由脚本助手确认风格与人物设定
@@ -1516,8 +1487,8 @@
${esc(title)}
镜头
${shots}
-
预估完播
${durObj.completion}%
-
预估转化
${durObj.conversion}%
+
时长
${esc(durObj.label)}
+
比例
9:16
预估成本
¥${c.total.toFixed(2)}
${summaryBlock} @@ -1573,8 +1544,8 @@ renderRail(); const body = $('#wiz-body'); // 单页式: 商品 (step1) + 项目配置 (原 step3, 现 step2),底部「开始」CTA - const p = getProduct(), du = getDuration(), st = getStyle(); - const canStart = !!(p && du && st && state.projectName.trim().length >= 2); + const p = getProduct(), du = getDuration(); + const canStart = !!(p && du && state.projectName.trim().length >= 2); let html = ''; html += '
' + renderStep1() + '
'; html += '
' + renderStep3() + '
';