diff --git a/电商AI平台/assets/mock-media.js b/电商AI平台/assets/mock-media.js new file mode 100644 index 0000000..2f3253c --- /dev/null +++ b/电商AI平台/assets/mock-media.js @@ -0,0 +1,152 @@ +(function () { + const base = 'assets/mock/'; + const media = { + products: { + mask: base + 'product-mask.png', + earbuds: base + 'product-earbuds.png', + noodle: base + 'product-noodle.png', + sunscreen: base + 'product-sunscreen.png', + coffee: base + 'product-coffee.png', + airFryer: base + 'product-air-fryer.png', + yoga: base + 'product-yoga-pants.png', + storage: base + 'product-storage.png' + }, + covers: { + mask: base + 'cover-mask-v3.png', + maskFinal: base + 'cover-mask-final.png', + noodle: base + 'cover-noodle.png', + sunscreen: base + 'cover-sunscreen.png', + coffee: base + 'cover-coffee.png', + earbuds: base + 'cover-earbuds.png', + yoga: base + 'cover-yoga.png', + airFryer: base + 'cover-air-fryer.png' + }, + people: { + linxi: base + 'person-linxi.png', + ajie: base + 'person-ajie.png', + aqiang: base + 'person-aqiang.png', + xiaosu: base + 'person-xiaosu.png' + }, + scenes: { + bedroom: base + 'scene-bedroom.png', + bathroom: base + 'scene-bathroom.png', + living: base + 'scene-living.png', + kitchen: base + 'scene-kitchen.png', + office: base + 'scene-office.png', + cafe: base + 'scene-cafe.png', + street: base + 'scene-night-street.png', + tabletop: base + 'scene-tabletop.png' + } + }; + + function clean(text) { + return String(text || '').replace(/\s+/g, '').toLowerCase(); + } + + function contextText(el) { + const card = el.closest('[data-name], [data-product], [data-project], [data-cat], [data-asset-kind], [data-scene-type]'); + const bits = [ + el.textContent, + card?.dataset.name, + card?.dataset.product, + card?.dataset.project, + card?.dataset.cat, + card?.dataset.assetKind, + card?.dataset.sceneType, + card?.querySelector('.product-name, .card-name, .asset-name, .prod-name, strong')?.textContent + ]; + return clean(bits.filter(Boolean).join(' ')); + } + + function productFor(t) { + if (/蓝牙|耳机|earbud|南卡/.test(t)) return media.products.earbuds; + if (/速食|牛肉面|泡面|面条|noodle/.test(t)) return media.products.noodle; + if (/防晒|sunscreen/.test(t)) return media.products.sunscreen; + if (/咖啡|冻干|coffee/.test(t)) return media.products.coffee; + if (/空气炸锅|airfryer|小熊/.test(t)) return media.products.airFryer; + if (/瑜伽裤|露露|yoga/.test(t)) return media.products.yoga; + if (/收纳|storage|北欧/.test(t)) return media.products.storage; + if (/面膜|补水|玻尿酸|mask|透真/.test(t)) return media.products.mask; + return ''; + } + + function coverFor(t) { + if (/蓝牙|耳机|earbud|南卡/.test(t)) return media.covers.earbuds; + if (/速食|牛肉面|泡面|面条|noodle/.test(t)) return media.covers.noodle; + if (/防晒|sunscreen/.test(t)) return media.covers.sunscreen; + if (/咖啡|冻干|coffee/.test(t)) return media.covers.coffee; + if (/空气炸锅|airfryer|小熊/.test(t)) return media.covers.airFryer; + if (/瑜伽裤|露露|yoga/.test(t)) return media.covers.yoga; + if (/v1|final|已完成|5\/5|成片|敷面膜|化妆台/.test(t)) return media.covers.maskFinal; + if (/面膜|补水|玻尿酸|mask|透真|场1|场2|场3/.test(t)) return media.covers.mask; + return ''; + } + + function personFor(t) { + if (/阿杰|通勤男|男青年/.test(t)) return media.people.ajie; + if (/阿强|健身男|健身/.test(t)) return media.people.aqiang; + if (/小苏|文艺女|短发|阿楠|同事|小七|学生女|闺蜜|妈妈|王姐|豆豆/.test(t)) return media.people.xiaosu; + if (/林夕|主播|都市白领|主角|女性|女生/.test(t)) return media.people.linxi; + if (/小宇|李爷爷|男性|男/.test(t)) return media.people.ajie; + return ''; + } + + function sceneFor(t) { + if (/卧室|床头|bedroom/.test(t)) return media.scenes.bedroom; + if (/浴室|梳妆台|bathroom/.test(t)) return media.scenes.bathroom; + if (/客厅|living/.test(t)) return media.scenes.living; + if (/厨房|中岛|kitchen/.test(t)) return media.scenes.kitchen; + if (/办公室|办公桌|会议室|office|深夜办公/.test(t)) return media.scenes.office; + if (/咖啡店|窗边|cafe/.test(t)) return media.scenes.cafe; + if (/街景|夜|street/.test(t)) return media.scenes.street; + if (/平台|套图|布景|tabletop/.test(t)) return media.scenes.tabletop; + return ''; + } + + function imageFor(el) { + if (el.classList.contains('missing') || el.querySelector('.spinner, .fail-icon')) return ''; + const t = contextText(el); + const card = el.closest('.asset-card, .asset-card-2, .proj-card, .product-card, .video-card'); + if (el.id === 'ed-canvas') return media.covers.maskFinal; + if (el.id === 'sb-main-img' || el.id === 'vd-main-img') return media.covers.mask; + if (el.matches('.video-thumb')) { + if (card?.dataset.videoId === 'v2') return media.products.mask; + if (card?.dataset.videoId === 'v3') return media.covers.maskFinal; + } + if (el.matches('.card-thumb, .proj-thumb, .video-thumb') || card?.classList.contains('video')) return coverFor(t); + if (el.matches('.product-thumb, .prod-thumb, .pl-thumb')) return productFor(t); + if (el.matches('.m-thumb')) return personFor(t); + if (el.matches('.thumb-2, .asset-thumb')) { + return personFor(t) || sceneFor(t) || productFor(t) || coverFor(t); + } + if (el.matches('.sb-scene-thumb, .sb-history-thumb, .vd-history-thumb')) return coverFor(t) || media.covers.mask; + return productFor(t) || personFor(t) || sceneFor(t) || coverFor(t); + } + + function applyOne(el) { + if (!el || el.dataset.mockMediaApplied === '1') return; + const src = imageFor(el); + if (!src) return; + el.dataset.mockMediaApplied = '1'; + el.classList.add('has-mock-media'); + el.style.setProperty('--mock-media-url', `url("${src}")`); + el.style.backgroundImage = `url("${src}")`; + } + + function apply() { + document.querySelectorAll('.placeholder, #ed-canvas').forEach(applyOne); + } + + function boot() { + apply(); + const mo = new MutationObserver(() => apply()); + mo.observe(document.body, { childList: true, subtree: true }); + window.MockMedia = { apply, media }; + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot, { once: true }); + } else { + boot(); + } +})(); diff --git a/电商AI平台/assets/mock/cover-air-fryer.png b/电商AI平台/assets/mock/cover-air-fryer.png new file mode 100644 index 0000000..ba8ba94 Binary files /dev/null and b/电商AI平台/assets/mock/cover-air-fryer.png differ diff --git a/电商AI平台/assets/mock/cover-coffee.png b/电商AI平台/assets/mock/cover-coffee.png new file mode 100644 index 0000000..c21d895 Binary files /dev/null and b/电商AI平台/assets/mock/cover-coffee.png differ diff --git a/电商AI平台/assets/mock/cover-earbuds.png b/电商AI平台/assets/mock/cover-earbuds.png new file mode 100644 index 0000000..3c11af7 Binary files /dev/null and b/电商AI平台/assets/mock/cover-earbuds.png differ diff --git a/电商AI平台/assets/mock/cover-mask-final.png b/电商AI平台/assets/mock/cover-mask-final.png new file mode 100644 index 0000000..e44cf5b Binary files /dev/null and b/电商AI平台/assets/mock/cover-mask-final.png differ diff --git a/电商AI平台/assets/mock/cover-mask-v3.png b/电商AI平台/assets/mock/cover-mask-v3.png new file mode 100644 index 0000000..f309950 Binary files /dev/null and b/电商AI平台/assets/mock/cover-mask-v3.png differ diff --git a/电商AI平台/assets/mock/cover-noodle.png b/电商AI平台/assets/mock/cover-noodle.png new file mode 100644 index 0000000..b57055f Binary files /dev/null and b/电商AI平台/assets/mock/cover-noodle.png differ diff --git a/电商AI平台/assets/mock/cover-sunscreen.png b/电商AI平台/assets/mock/cover-sunscreen.png new file mode 100644 index 0000000..43e9d6c Binary files /dev/null and b/电商AI平台/assets/mock/cover-sunscreen.png differ diff --git a/电商AI平台/assets/mock/cover-yoga.png b/电商AI平台/assets/mock/cover-yoga.png new file mode 100644 index 0000000..1e7ee75 Binary files /dev/null and b/电商AI平台/assets/mock/cover-yoga.png differ diff --git a/电商AI平台/assets/mock/person-ajie.png b/电商AI平台/assets/mock/person-ajie.png new file mode 100644 index 0000000..8350809 Binary files /dev/null and b/电商AI平台/assets/mock/person-ajie.png differ diff --git a/电商AI平台/assets/mock/person-aqiang.png b/电商AI平台/assets/mock/person-aqiang.png new file mode 100644 index 0000000..10d84af Binary files /dev/null and b/电商AI平台/assets/mock/person-aqiang.png differ diff --git a/电商AI平台/assets/mock/person-linxi.png b/电商AI平台/assets/mock/person-linxi.png new file mode 100644 index 0000000..6b1a056 Binary files /dev/null and b/电商AI平台/assets/mock/person-linxi.png differ diff --git a/电商AI平台/assets/mock/person-xiaosu.png b/电商AI平台/assets/mock/person-xiaosu.png new file mode 100644 index 0000000..d27adfd Binary files /dev/null and b/电商AI平台/assets/mock/person-xiaosu.png differ diff --git a/电商AI平台/assets/mock/product-air-fryer.png b/电商AI平台/assets/mock/product-air-fryer.png new file mode 100644 index 0000000..2676fa5 Binary files /dev/null and b/电商AI平台/assets/mock/product-air-fryer.png differ diff --git a/电商AI平台/assets/mock/product-coffee.png b/电商AI平台/assets/mock/product-coffee.png new file mode 100644 index 0000000..8ea2166 Binary files /dev/null and b/电商AI平台/assets/mock/product-coffee.png differ diff --git a/电商AI平台/assets/mock/product-earbuds.png b/电商AI平台/assets/mock/product-earbuds.png new file mode 100644 index 0000000..16f4cb0 Binary files /dev/null and b/电商AI平台/assets/mock/product-earbuds.png differ diff --git a/电商AI平台/assets/mock/product-mask.png b/电商AI平台/assets/mock/product-mask.png new file mode 100644 index 0000000..66fb12a Binary files /dev/null and b/电商AI平台/assets/mock/product-mask.png differ diff --git a/电商AI平台/assets/mock/product-noodle.png b/电商AI平台/assets/mock/product-noodle.png new file mode 100644 index 0000000..742130f Binary files /dev/null and b/电商AI平台/assets/mock/product-noodle.png differ diff --git a/电商AI平台/assets/mock/product-storage.png b/电商AI平台/assets/mock/product-storage.png new file mode 100644 index 0000000..c97c18c Binary files /dev/null and b/电商AI平台/assets/mock/product-storage.png differ diff --git a/电商AI平台/assets/mock/product-sunscreen.png b/电商AI平台/assets/mock/product-sunscreen.png new file mode 100644 index 0000000..fa3af5e Binary files /dev/null and b/电商AI平台/assets/mock/product-sunscreen.png differ diff --git a/电商AI平台/assets/mock/product-yoga-pants.png b/电商AI平台/assets/mock/product-yoga-pants.png new file mode 100644 index 0000000..14550bd Binary files /dev/null and b/电商AI平台/assets/mock/product-yoga-pants.png differ diff --git a/电商AI平台/assets/mock/scene-bathroom.png b/电商AI平台/assets/mock/scene-bathroom.png new file mode 100644 index 0000000..db3a459 Binary files /dev/null and b/电商AI平台/assets/mock/scene-bathroom.png differ diff --git a/电商AI平台/assets/mock/scene-bedroom.png b/电商AI平台/assets/mock/scene-bedroom.png new file mode 100644 index 0000000..cd4c4df Binary files /dev/null and b/电商AI平台/assets/mock/scene-bedroom.png differ diff --git a/电商AI平台/assets/mock/scene-cafe.png b/电商AI平台/assets/mock/scene-cafe.png new file mode 100644 index 0000000..ab8e5dc Binary files /dev/null and b/电商AI平台/assets/mock/scene-cafe.png differ diff --git a/电商AI平台/assets/mock/scene-kitchen.png b/电商AI平台/assets/mock/scene-kitchen.png new file mode 100644 index 0000000..755d007 Binary files /dev/null and b/电商AI平台/assets/mock/scene-kitchen.png differ diff --git a/电商AI平台/assets/mock/scene-living.png b/电商AI平台/assets/mock/scene-living.png new file mode 100644 index 0000000..8c32db2 Binary files /dev/null and b/电商AI平台/assets/mock/scene-living.png differ diff --git a/电商AI平台/assets/mock/scene-night-street.png b/电商AI平台/assets/mock/scene-night-street.png new file mode 100644 index 0000000..fe96358 Binary files /dev/null and b/电商AI平台/assets/mock/scene-night-street.png differ diff --git a/电商AI平台/assets/mock/scene-office.png b/电商AI平台/assets/mock/scene-office.png new file mode 100644 index 0000000..1857731 Binary files /dev/null and b/电商AI平台/assets/mock/scene-office.png differ diff --git a/电商AI平台/assets/mock/scene-tabletop.png b/电商AI平台/assets/mock/scene-tabletop.png new file mode 100644 index 0000000..790a739 Binary files /dev/null and b/电商AI平台/assets/mock/scene-tabletop.png differ diff --git a/电商AI平台/assets/restraint.css b/电商AI平台/assets/restraint.css index 10ab13f..b6edd96 100644 --- a/电商AI平台/assets/restraint.css +++ b/电商AI平台/assets/restraint.css @@ -758,7 +758,7 @@ main { position: relative; background: var(--background-base); min-width: 0; } background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); - box-shadow: 0 8px 24px rgba(0,0,0,.08); + box-shadow: var(--shadow-floating); padding: 4px; z-index: 50; display: none; @@ -781,6 +781,271 @@ main { position: relative; background: var(--background-base); min-width: 0; } .chip-menu .mi.selected .mi-check { opacity: 1; } .chip-menu .mi-sep { height: 1px; background: var(--border-faint); margin: 4px 6px; } +/* ─── Dropdown unification · 下拉框 / 菜单视觉收口 ─── */ +select.select, +select.v-select, +select.nm-select, +select.duration-select, +.filter-bar select { + appearance: none !important; + height: 36px !important; + padding: 0 32px 0 14px !important; + background-color: var(--surface) !important; + background-image: url("data:image/svg+xml;utf8,") !important; + background-repeat: no-repeat !important; + background-position: right 12px center !important; + border: 1px solid var(--black-alpha-12) !important; + border-radius: var(--r-md) !important; + color: var(--accent-black) !important; + font-family: inherit !important; + font-size: 13.5px !important; + transition: border-color var(--t-base), box-shadow var(--t-base), background var(--t-base) !important; +} +select.select:hover, +select.v-select:hover, +select.nm-select:hover, +select.duration-select:hover, +.filter-bar select:hover { + border-color: var(--black-alpha-24) !important; +} +select.select:focus, +select.v-select:focus, +select.nm-select:focus, +select.duration-select:focus, +.filter-bar select:focus { + border-color: var(--heat-40) !important; + box-shadow: inset 0 0 0 1px var(--heat-40) !important; +} + +.pd-toolbar .filter, +.pp-toolbar .pp-chip, +.mp-main-h .tb-chip, +.pc-main-h .tb-chip { + height: 36px !important; + padding: 0 14px !important; + gap: 6px !important; + background: var(--surface) !important; + border: 1px solid var(--border-faint) !important; + border-radius: var(--r-md) !important; + color: var(--black-alpha-56) !important; + font-size: 13px !important; + font-weight: 500 !important; + transition: background var(--t-base), border-color var(--t-base), color var(--t-base) !important; +} +.pd-toolbar .filter:hover, +.pp-toolbar .pp-chip:hover, +.mp-main-h .tb-chip:hover, +.pc-main-h .tb-chip:hover { + background: var(--black-alpha-4) !important; + border-color: var(--black-alpha-24) !important; + color: var(--accent-black) !important; +} +.pd-toolbar .filter.open, +.pd-toolbar .filter.filtered, +.pp-toolbar .pp-chip.active, +.mp-main-h .tb-chip.active, +.pc-main-h .tb-chip.active { + background: var(--heat-12) !important; + border-color: var(--heat-40) !important; + color: var(--heat) !important; +} + +:where(.chip-menu, .filter-pop, .pp-menu, .tb-menu, .io-param-menu, .move-menu, .cell-more-menu, .msg-more-menu, .batch-more-menu) { + background: var(--surface) !important; + border: 1px solid var(--border-faint) !important; + border-radius: var(--r-md) !important; + box-shadow: var(--shadow-floating) !important; + padding: 4px !important; +} +:where( + .chip-menu .mi, + .filter-pop button, + .pp-menu .mi, + .tb-menu-item, + .io-param-menu .mi, + .move-menu .mv-item, + .cell-more-menu button, + .msg-more-menu button, + .batch-more-menu button +) { + min-height: 32px !important; + padding: 0 10px !important; + background: transparent !important; + border: 0 !important; + border-radius: var(--r-sm) !important; + color: var(--accent-black) !important; + display: flex !important; + align-items: center !important; + gap: 8px !important; + font-family: inherit !important; + font-size: 13px !important; + font-weight: 400 !important; + text-align: left !important; + cursor: pointer !important; + transition: background var(--t-base), color var(--t-base) !important; +} +:where( + .chip-menu .mi, + .filter-pop button, + .pp-menu .mi, + .tb-menu-item, + .io-param-menu .mi, + .move-menu .mv-item, + .cell-more-menu button, + .msg-more-menu button, + .batch-more-menu button +):hover { + background: var(--black-alpha-4) !important; + color: var(--accent-black) !important; +} +:where( + .chip-menu .mi.selected, + .filter-pop button.selected, + .pp-menu .mi.selected, + .tb-menu-item.active, + .io-param-menu .mi.selected +) { + background: var(--heat-12) !important; + color: var(--heat) !important; + font-weight: 500 !important; +} +:where(.cell-more-menu button.danger:hover, .msg-more-menu button.danger:hover, .batch-more-menu button.danger:hover) { + background: var(--crimson-bg) !important; + color: var(--accent-crimson) !important; +} +:where(.tb-menu-empty) { + color: var(--black-alpha-48) !important; + font-family: var(--font-mono) !important; + font-size: 11.5px !important; + letter-spacing: .02em !important; +} + +.rs-select { + position: relative; + display: inline-flex; + min-width: 126px; + vertical-align: middle; +} +.rs-select.rs-select-fill { width: 100%; } +.rs-select.rs-select-filter { min-width: 126px; } +.rs-select > select[data-rs-select-bound="1"] { + position: absolute !important; + inset: 0 auto auto 0 !important; + width: 1px !important; + height: 1px !important; + opacity: 0 !important; + pointer-events: none !important; +} +.rs-select-btn { + width: 100%; + height: 36px; + padding: 0 12px 0 14px; + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 12px; + background: var(--surface); + border: 1px solid var(--black-alpha-12); + border-radius: var(--r-md); + color: var(--accent-black); + font-family: inherit; + font-size: 13.5px; + cursor: pointer; + transition: background var(--t-base), border-color var(--t-base), box-shadow var(--t-base), color var(--t-base); +} +.rs-select-btn:hover { + background: var(--black-alpha-4); + border-color: var(--black-alpha-24); +} +.rs-select.open .rs-select-btn, +.rs-select-btn:focus-visible { + border-color: var(--heat-40); + box-shadow: inset 0 0 0 1px var(--heat-40); +} +.rs-select-btn[disabled] { + background: var(--black-alpha-5); + color: var(--black-alpha-32); + cursor: not-allowed; +} +.rs-select-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.rs-select-btn svg { + width: 12px; + height: 12px; + color: var(--black-alpha-56); + flex-shrink: 0; + transition: transform var(--t-base), color var(--t-base); +} +.rs-select.open .rs-select-btn svg { + transform: rotate(180deg); + color: var(--heat); +} +.rs-select-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + min-width: 100%; + width: max-content; + max-width: min(260px, calc(100vw - 24px)); + max-height: 320px; + overflow-y: auto; + display: none; + z-index: 1600; + background: var(--surface); + border: 1px solid var(--border-faint); + border-radius: var(--r-md); + box-shadow: var(--shadow-floating); + padding: 4px; +} +.rs-select-menu.align-right { + left: auto; + right: 0; +} +.rs-select.open .rs-select-menu { display: block; } +.rs-select-option { + width: 100%; + min-height: 32px; + padding: 0 10px; + display: flex; + align-items: center; + gap: 8px; + background: transparent; + border: 0; + border-radius: var(--r-sm); + color: var(--accent-black); + font-family: inherit; + font-size: 13px; + text-align: left; + white-space: nowrap; + cursor: pointer; + transition: background var(--t-base), color var(--t-base); +} +.rs-select-option:hover, +.rs-select-option.is-active { + background: var(--black-alpha-4); +} +.rs-select-option.selected { + background: var(--heat-12); + color: var(--heat); + font-weight: 500; +} +.rs-select-option[disabled] { + color: var(--black-alpha-32); + cursor: not-allowed; +} +.rs-select-option .mi-check { + width: 13px; + height: 13px; + color: var(--heat); + opacity: 0; + flex-shrink: 0; +} +.rs-select-option.selected .mi-check { opacity: 1; } + /* ─── Clear-filters btn · 共享组件 ─── */ .clear-filters { height: 36px; padding: 0 12px; @@ -1272,6 +1537,25 @@ table.t tbody tr:hover { background: var(--black-alpha-4); } color: var(--black-alpha-56); font-weight: 500; } +.placeholder.has-mock-media { + background-color: var(--background-lighter); + background-image: var(--mock-media-url); + background-position: center; + background-size: cover; + background-repeat: no-repeat; + padding: 0; +} +.placeholder.has-mock-media .ph-frame { + opacity: 0; + pointer-events: none; +} +.placeholder.has-mock-media.mock-label .ph-frame { + position: absolute; + left: 8px; + bottom: 8px; + max-width: calc(100% - 16px); + opacity: 1; +} /* ─── Toast ─── */ .toast { diff --git a/电商AI平台/assets/shell.js b/电商AI平台/assets/shell.js index db0817b..4462644 100644 --- a/电商AI平台/assets/shell.js +++ b/电商AI平台/assets/shell.js @@ -292,6 +292,144 @@ window.Shell = { document.getElementById('global-search')?.focus(); } }); + + this.enhanceSelects(document); + }, + + enhanceSelects(root = document) { + const scope = root || document; + const nodes = []; + if (scope.matches?.('select')) nodes.push(scope); + scope.querySelectorAll?.('select:not([data-rs-select-bound])').forEach(el => nodes.push(el)); + const selects = [...new Set(nodes)].filter(select => + select.tagName === 'SELECT' && + !select.multiple && + Number(select.size || 0) <= 1 && + !select.closest('.rs-select') && + select.dataset.nativeSelect !== 'true' + ); + if (!selects.length) { + this._bindCustomSelectInfrastructure(); + return; + } + const esc = (s) => String(s ?? '').replace(/[&<>"']/g, c => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' + })[c]); + const checkSvg = ShellIcon('check', { className: 'mi-check', size: 13 }); + const caretSvg = ShellIcon('chevronDown', { size: 12 }); + + selects.forEach(select => { + const wrap = document.createElement('span'); + wrap.className = 'rs-select'; + if (select.classList.contains('v-edit')) wrap.classList.add('v-edit'); + if (select.hidden) wrap.hidden = true; + if ( + select.classList.contains('select') || + select.classList.contains('v-select') || + select.classList.contains('nm-select') || + select.closest('.field') || + select.closest('.form-row') + ) wrap.classList.add('rs-select-fill'); + if (select.closest('.filter-bar')) wrap.classList.add('rs-select-filter'); + + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'rs-select-btn'; + btn.setAttribute('aria-haspopup', 'listbox'); + btn.setAttribute('aria-expanded', 'false'); + + const menu = document.createElement('div'); + menu.className = 'rs-select-menu'; + menu.setAttribute('role', 'listbox'); + + select.parentNode.insertBefore(wrap, select); + wrap.appendChild(select); + wrap.appendChild(btn); + wrap.appendChild(menu); + select.dataset.rsSelectBound = '1'; + select.tabIndex = -1; + + const sync = () => { + const selected = select.selectedOptions[0] || select.options[select.selectedIndex] || select.options[0]; + btn.disabled = select.disabled; + btn.innerHTML = `${esc(selected?.textContent || '')}${caretSvg}`; + menu.innerHTML = [...select.options].map((opt, i) => ` + + `).join(''); + }; + + btn.addEventListener('click', e => { + e.stopPropagation(); + if (select.disabled) return; + const willOpen = !wrap.classList.contains('open'); + this.closeCustomSelects(wrap); + wrap.classList.toggle('open', willOpen); + btn.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); + if (willOpen) { + menu.classList.remove('align-right'); + const rect = menu.getBoundingClientRect(); + if (rect.right > window.innerWidth - 12) menu.classList.add('align-right'); + } + }); + btn.addEventListener('keydown', e => { + if (e.key !== 'Enter' && e.key !== ' ' && e.key !== 'ArrowDown') return; + e.preventDefault(); + btn.click(); + }); + menu.addEventListener('click', e => { + const item = e.target.closest('.rs-select-option'); + if (!item || item.disabled) return; + const idx = Number(item.dataset.index); + if (!Number.isNaN(idx) && select.selectedIndex !== idx) { + select.selectedIndex = idx; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + sync(); + this.closeCustomSelects(); + btn.focus(); + }); + select.addEventListener('change', sync); + select._rsSelectSync = sync; + sync(); + }); + + this._bindCustomSelectInfrastructure(); + }, + + refreshCustomSelects(root = document) { + root.querySelectorAll?.('select[data-rs-select-bound="1"]').forEach(select => { + if (typeof select._rsSelectSync === 'function') select._rsSelectSync(); + }); + }, + + closeCustomSelects(except) { + document.querySelectorAll('.rs-select.open').forEach(wrap => { + if (except && wrap === except) return; + wrap.classList.remove('open'); + wrap.querySelector('.rs-select-btn')?.setAttribute('aria-expanded', 'false'); + }); + }, + + _bindCustomSelectInfrastructure() { + if (this._rsSelectInfrastructureBound) return; + this._rsSelectInfrastructureBound = true; + document.addEventListener('click', e => { + if (!e.target.closest('.rs-select')) this.closeCustomSelects(); + requestAnimationFrame(() => this.refreshCustomSelects(document)); + }); + document.addEventListener('keydown', e => { + if (e.key === 'Escape') this.closeCustomSelects(); + }); + this._rsSelectObserver = new MutationObserver(records => { + if (!records.some(r => [...r.addedNodes].some(n => + n.nodeType === 1 && (n.matches?.('select') || n.querySelector?.('select')) + ))) return; + requestAnimationFrame(() => this.enhanceSelects(document)); + }); + if (document.body) this._rsSelectObserver.observe(document.body, { childList: true, subtree: true }); }, // [DEPRECATED] 新建商品弹窗 · 已废弃,创建商品改为直跳 product-create.html @@ -612,3 +750,12 @@ window.Shell = { this.unlockScroll(); } }; + +(function loadMockMedia() { + if (window.__AIRSHELF_DISABLE_MOCK_MEDIA__) return; + if (document.querySelector('script[data-mock-media]')) return; + const script = document.createElement('script'); + script.src = 'assets/mock-media.js?v=2026052703'; + script.dataset.mockMedia = '1'; + document.head.appendChild(script); +})(); diff --git a/电商AI平台/pipeline.html b/电商AI平台/pipeline.html index a82c5af..4abb136 100644 --- a/电商AI平台/pipeline.html +++ b/电商AI平台/pipeline.html @@ -164,7 +164,7 @@ /* 镜头脚本 | 拖拽分隔条 | 脚本助手 三列布局 · 分隔条可拖动 */ .content--fh-flat .stage[data-stage-pane="1"].active > .stage-script { gap: 0; - grid-template-columns: minmax(0, 1fr) 6px var(--chat-w, 360px); + grid-template-columns: minmax(0, 1fr) 6px var(--chat-w, 520px); } /* 拖拽 gutter · 默认 1px 灰线,hover/拖中加重 · 鼠标 col-resize */ .stage-script-gutter { @@ -2518,12 +2518,12 @@ document.getElementById('page-title').textContent = PROJECT_TITLE + ' · 流水 topbar.appendChild(anchor); // position: absolute · 自动锚定 topbar 中部 })(); -/* ─── Stage 1 拖拽分隔条 · 控制脚本助手宽度 · clamp 在 [280, 600] ─── */ +/* ─── Stage 1 拖拽分隔条 · 控制脚本助手宽度 · clamp 在 [380, 680] ─── */ (function _setupStageScriptGutter() { const gutter = document.getElementById('stage-script-gutter'); const grid = document.querySelector('.stage-script'); if (!gutter || !grid) return; - const MIN = 320, MAX = 600; + const MIN = 380, MAX = 680; let dragging = false; gutter.addEventListener('mousedown', (e) => { dragging = true; diff --git a/电商AI平台/product-detail.html b/电商AI平台/product-detail.html index 9f7202b..0c7d0d7 100644 --- a/电商AI平台/product-detail.html +++ b/电商AI平台/product-detail.html @@ -1446,12 +1446,18 @@ const pill = e.target.closest('.asset-card .meta .pill[data-status]'); if (!pill) return; e.stopPropagation(); + if (pill.closest('.asset-card[data-tri-version]')) { + window.ProductDetailFilters?.applyAssets?.(); + Shell.toast('三视图状态由采用版本决定', '请在三视图面板选择「采用此版本」'); + return; + } const cur = pill.dataset.status; const next = order[(order.indexOf(cur) + 1) % order.length]; pill.dataset.status = next; pill.classList.remove('pass', 'fail', 'archive'); pill.classList.add(next); pill.textContent = labels[next]; + window.ProductDetailFilters?.applyAssets?.(); Shell.toast('状态已更新', labels[next]); }); })(); @@ -1721,6 +1727,16 @@ if (more) more.style.display = ''; } } + + window.ProductDetailFilters = { + apply(pane = 'assets') { + const paneEl = document.querySelector(`.tab-pane[data-pane="${pane}"]`); + if (paneEl) applyFilters(paneEl); + }, + applyAssets() { + this.apply('assets'); + } + }; })(); // 编辑商品信息 · 在卡片内 inline 切换 view ↔ edit @@ -1899,6 +1915,10 @@ return (document.getElementById('pd-name')?.textContent || '商品').trim(); } + function refreshAssetFilters() { + window.ProductDetailFilters?.applyAssets?.(); + } + function open() { pop.classList.add('show'); btn.classList.add('is-open'); @@ -1983,6 +2003,7 @@ pill.setAttribute('data-status', isAdopted ? 'pass' : 'fail'); pill.setAttribute('title', isAdopted ? '当前采用版本' : '未被采用'); }); + refreshAssetFilters(); } function appendLibraryCard(ver) { @@ -2007,6 +2028,7 @@ const n = m ? Number(m[1]) + 1 : 1; ct.textContent = `(${n})`; } + refreshAssetFilters(); } // 切换主图预览(不动采用状态、不动素材库)