View all
  • Free Shipping On Order 99+

    Free Shipping On Order 99+

    Shop now

Free Shipping On Order 99+

Free Shipping On Order 99+

function getTotalHeightIncludingMargin(element) { if (!element) return 0; const style = window.getComputedStyle(element); const marginTop = parseFloat(style.marginTop); const marginBottom = parseFloat(style.marginBottom); return element.offsetHeight + marginTop + marginBottom; } function updateAnnouncementContainerHeight() { const socialLinksHeight = getTotalHeightIncludingMargin(socialLinks); const announcementHeight = getTotalHeightIncludingMargin(announcement); const height = Math.max(socialLinksHeight, announcementHeight); announcementContainer.style.height = height + 'px'; } function resetAnnouncementContainerHeight() { announcementContainer.style.height = 'auto'; } const socialLinks = document.getElementById('socialLinks'); const announcement = document.querySelector('.pc-announcement'); const announcementContainer = document.querySelector('.announcement-container'); const resizeObserver = new ResizeObserver(() => { if (window.matchMedia("(min-width: 960px)").matches) { updateAnnouncementContainerHeight(); }else { resetAnnouncementContainerHeight(); } }); if (socialLinks) { resizeObserver.observe(socialLinks); } if (announcement) { resizeObserver.observe(announcement); }
const updateHeaderMetrics = () => { const header = document.getElementById('header'); const rect = header.getBoundingClientRect(); const headerHeight = rect.bottom; document.documentElement.style.setProperty('--header-height', `${headerHeight}px`); }; window.addEventListener('DOMContentLoaded', updateHeaderMetrics); window.addEventListener('resize', updateHeaderMetrics); const header = document.getElementById('header'); const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { const rect = entry.target.getBoundingClientRect(); const headerHeight = rect.bottom; document.documentElement.style.setProperty('--header-height', `${headerHeight}px`); } }); resizeObserver.observe(header);
function setSearchUrl(searchValue) { return Promise.resolve({ url: '/search?q=' + searchValue }); } exportFunction('setSearchUrl', setSearchUrl);

moongor

Search
search Search
search Search
search Search
No results for search
const templateName = SHOPLAZZA?.meta?.page?.template_name || ''; const SEARCH_URL = '/search'; const TAG = 'spz-custom-smart-search-location'; const SEARCH_CONTAINER_CLASS = 'app-smart-product-search-container'; const THEME_NAME = window.SHOPLAZZA.theme.merchant_theme_name.replace(/ /g, ''); const BREAKPOINT = 960; const DELAY = 300; const DEFAULT_SEARCH_STYLE_CONFIG = { styleType: 'imageText', borderRadius: 4, marginEnabled: false, margin: { mobile: { left: 0, right: 0, linked: true }, pc: { left: 0, right: 0, linked: true }, }, showSearchButton: true, searchButtonType: 'text', iconEnabled: true, iconType: 'system', customIcon: '', textEnabled: false, fontStyle: 'normal', fontBold: false, fontSize: 14, fontItalic: false, searchText: 'Search', colorType: 'theme', iconColor: '#202020', textColor: '#202020', buttonTextColor: '#FFFFFF', buttonIconColor: '#FFFFFF', buttonColor: '#202020', searchBorderColor: '#202020', searchBgColor: '#FFFFFF', inputIconTextColor: '#6D7175', }; const DEFAULT_CLICK_SEARCH_STYLE_CONFIG = { buttonType: 'text', iconType: 'system', customIcon: '', fontStyle: 'normal', fontBold: false, fontSize: 14, fontItalic: false, searchText: 'Search', borderRadius: 4, colorType: 'theme', iconColor: '#FFFFFF', textColor: '#FFFFFF', buttonColor: '#202020', searchBorderColor: '#202020', searchBgColor: '#FFFFFF', inputIconTextColor: '#6D7175', hotSearchBgStartColor: '#FFE5E6', hotSearchBgEndColor: '#FFFCFC', }; // --- 工具函数 --- function parseHeaderStyle(headerStyleStr) { const hasHeaderStyle = headerStyleStr && headerStyleStr !== '' && headerStyleStr !== '{}'; if (!hasHeaderStyle) { return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }, }; } try { const parsed = typeof headerStyleStr === 'string' ? JSON.parse(headerStyleStr) : headerStyleStr; return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG, ...(parsed.searchStyleConfig || {}) }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG, ...(parsed.clickSearchStyleConfig || {}) }, }; } catch (e) { console.error('parseHeaderStyle error:', e); return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }, }; } } function matchTheme(target) { return THEME_NAME.toLocaleLowerCase().includes(target.toLocaleLowerCase()); } function resolveThemeValue(themeMap, defaultValue) { let result = defaultValue; for (const key of Object.keys(themeMap)) { if (matchTheme(key)) result = themeMap[key]; } return result; } function joinSelectors(selectorList) { return [...new Set(selectorList)].join(','); } function isDesktop() { // 编辑器环境检测:编辑器可能使用 CSS transform 模拟移动端,此时 matchMedia 不准确 // 优先使用 document.documentElement.clientWidth 检测实际可视区域宽度 const viewportWidth = document.documentElement.clientWidth || window.innerWidth; return viewportWidth >= BREAKPOINT; } // --- 元素查找 Mixin --- const ElementFinderMixin = { getBlockWrap() { return this.element.closest('.app-smart-product-search-wrap') || document.querySelector('.app-smart-product-search-wrap'); }, getBlockContainer() { return this.element.closest('.' + SEARCH_CONTAINER_CLASS) || document.querySelector('.' + SEARCH_CONTAINER_CLASS); }, resolveBlockElement(selector, fallbackId) { const wrap = this.getBlockWrap(); const el = wrap?.querySelector(selector) || document.getElementById(fallbackId); return el ? SPZ.whenApiDefined(el) : Promise.resolve(null); }, getSmartSearchEl() { return this.resolveBlockElement('ljs-search', 'app-smart-search-899'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-899'); }, }; // --- 主题配置 --- const HEADER_SELECTOR = resolveThemeValue({ eva: 'header .header_grid_layout', geek: '.header-mobile-inner-container', onePage: 'header .header', wind: 'header #header-nav', nova: 'header .header', hero: 'header .header__nav', flash: '#shoplaza-section-header>div>div', lifestyle: '.header__wrapper', reformia: 'header#header', }, 'header'); const SEARCH_ICON_CLASS = resolveThemeValue({ flash: 'app-smart-icon-search-large-flash', hero: 'app-smart-icon-search-large-hero', geek: 'app-smart-icon-search-large-geek', nova: 'app-smart-icon-search-large-nova', }, 'app-smart-icon-search-large-default'); const PLUGIN_RELOCATION_CONFIG = resolveThemeValue({ reformia: { pc: '.header-layout .header__actions', mobile: '.header-layout .header__actions', }, }, null); // --- 布局 Mixin --- const MobileLayoutMixin = { relocatePlugin() { if (!PLUGIN_RELOCATION_CONFIG) return; const targetSelector = isDesktop() ? PLUGIN_RELOCATION_CONFIG.pc : PLUGIN_RELOCATION_CONFIG.mobile; if (!targetSelector) return; if (this._relocateTimer) { clearInterval(this._relocateTimer); } const attemptRelocate = () => { const container = this.element.closest('.' + SEARCH_CONTAINER_CLASS) || document.querySelector('#app-smart-product-search-container-899'); if (!container || !document.body.contains(container)) return false; const target = document.querySelector(targetSelector); if (!target) return false; if (target.contains(container)) return true; target.insertBefore(container, target.firstChild); return true; }; if (attemptRelocate()) return; let attempts = 0; this._relocateTimer = setInterval(() => { attempts++; if (attemptRelocate() || attempts >= 20) { clearInterval(this._relocateTimer); this._relocateTimer = null; } }, 500); }, applySearchIconClass() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.classList.add(SEARCH_ICON_CLASS); }); }, adjustLifestyleIcon() { if (!matchTheme('lifestyle') || this.searchItemType === 'input' || isDesktop()) return; if (window.__smartSearchLifestyleIconMoved__) { this._lifestyleIconMoved = true; return; } const container = this.getBlockContainer(); if (!container) return; const alreadyMoved = !!document.querySelector( '.header__wrapper .container .row.header>div>.app-smart-product-search-container' ); if (alreadyMoved) { this._lifestyleIconMoved = true; window.__smartSearchLifestyleIconMoved__ = true; return; } const headerDivs = document.querySelectorAll('.header__wrapper .container .row.header>div'); if (!headerDivs.length) return; const lastDiv = headerDivs[headerDivs.length - 1]; lastDiv.appendChild(container); this._lifestyleIconMoved = true; window.__smartSearchLifestyleIconMoved__ = true; }, initInputMode() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.style.display = 'none'; }); const searchWrap = this.getBlockWrap(); const pcContainer = this.getBlockContainer(); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (!this._originalSearchWrapParent && searchWrap && searchWrap.parentElement) { this._originalSearchWrapParent = searchWrap.parentElement; } if (isDesktop()) { if (mobileContainer) mobileContainer.style.display = 'none'; if (searchWrap && this._originalSearchWrapParent) { if (mobileContainer && mobileContainer.contains(searchWrap)) { this._originalSearchWrapParent.appendChild(searchWrap); } } // PC 端:显示所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'block'; }); return; } if (templateName === 'search') { this._skipMobileInit = true; return; } // 移动端:隐藏所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'none'; }); this.ensureMobileSearchContainer(); const mobileContainerAfterEnsure = document.querySelector('.smart-search-mobile-container'); if (!mobileContainerAfterEnsure) return; const existingWrap = mobileContainerAfterEnsure.querySelector('.app-smart-product-search-wrap'); if (existingWrap && existingWrap !== searchWrap) { return; } if (searchWrap && !mobileContainerAfterEnsure.contains(searchWrap)) { mobileContainerAfterEnsure.appendChild(searchWrap); } mobileContainerAfterEnsure.style.display = ''; }, ensureMobileSearchContainer() { if (document.querySelector('.smart-search-mobile-container')) return; const header = document.querySelector(HEADER_SELECTOR); if (!header) return; const container = document.createElement('div'); container.classList.add('smart-search-mobile-container'); container.classList.add('smart-search-mobile-container-' + THEME_NAME.toLocaleLowerCase()); header.appendChild(container); }, initIconMode() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.style.display = 'flex'; }); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (mobileContainer) mobileContainer.style.display = 'none'; // 显示所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = ''; }); }, hasMobilePluginParent() { return !['geek', 'flash', 'boost', 'reformia'].includes(THEME_NAME.toLocaleLowerCase()); }, showMobileSmartSearch() { if (this._mobileSearchShown) return; const PLUGIN_PARENT_SELECTORS = { nova: '.header__mobile #header__plugin-container', hero: '.header__icons .tw-flex.tw-justify-end.tw-items-center.tw-space-x-7', onePage: '.header__mobile #header__plugin-container', wind: '#header-icons .flex.justify-end.items-center', eva: '#header__icons .plugin_content', }; const parentEl = document.querySelector( joinSelectors(Object.values(PLUGIN_PARENT_SELECTORS)) ); if (!parentEl) return; const hasHiddenClass = parentEl.classList.contains('md:hidden') || parentEl.classList.contains('md:tw-hidden'); if (hasHiddenClass) { Array.from(parentEl.children).forEach((child) => { if (!this.isSmartSearchElement(child)) { child.style.display = 'none'; } }); parentEl.classList.remove('md:hidden', 'md:tw-hidden'); } else { const smartSearchEl = Array.from(parentEl.children).find( (child) => this.isSmartSearchElement(child) ); if (smartSearchEl) { smartSearchEl.style.display = 'block'; } } this._mobileSearchShown = true; }, isSmartSearchElement(el) { return ( el.classList.contains(SEARCH_CONTAINER_CLASS) || el.querySelectorAll(`.${SEARCH_CONTAINER_CLASS}`).length > 0 ); }, addMobileSmartSearch() { if (this._mobileSearchAdded) return; const HEADER_ICONS_SELECTORS = { geek: '#header-mobile-container .flex.items-center.justify-end.flex-shrink-0', flash: '#header-layout .header__icons', boost: '.header__mobile-bottom .tw-flex.tw-items-center.tw-justify-end.tw-flex-1', reformia: '.header-layout .header__actions', }; const SMART_SEARCH_ANCESTORS = [ '#header-menu-mobile #menu-drawer', '#menu-drawer .plugin__header-content', '.header__drawer', '.header-content .logo-wrap', '.header_hamburger_sidebar-container', ]; const iconsEl = document.querySelector( joinSelectors(Object.values(HEADER_ICONS_SELECTORS)) ); const searchWrapSelector = joinSelectors( SMART_SEARCH_ANCESTORS.map(a => `${a} .${SEARCH_CONTAINER_CLASS}`) ); const searchWrapEl = document.querySelector(searchWrapSelector); if (!iconsEl || !searchWrapEl) return; iconsEl.insertAdjacentElement('afterbegin', searchWrapEl); this._mobileSearchAdded = true; }, initMobileSmartSearch() { if (this._lifestyleIconMoved) return; if (this.hasMobilePluginParent()) { this.showMobileSmartSearch(); } else { this.addMobileSmartSearch(); } }, }; const StyleApplicatorMixin = { _getMarginConfig(config) { if (!config.marginEnabled || !config.margin) { return { left: 0, right: 0 }; } const device = isDesktop() ? 'pc' : 'mobile'; return config.margin[device] || { left: 0, right: 0 }; }, _applySearchInlineStyles(searchWrap, config) { const isCustom = config.colorType === 'custom'; const find = (sel) => searchWrap.querySelector(sel); const borderRadius = `${config.borderRadius}px`; const fontSize = `${config.fontSize}px`; const fontWeight = config.fontBold ? 'bold' : 'normal'; const fontStyle = config.fontItalic ? 'italic' : 'normal'; const inputContainer = find('.smart-search-outside-input-container'); if (inputContainer) { inputContainer.style.setProperty('border-radius', borderRadius, 'important'); if (isCustom) { inputContainer.style.setProperty('border-color', config.searchBorderColor, 'important'); inputContainer.style.setProperty('background-color', config.searchBgColor, 'important'); } else { inputContainer.style.removeProperty('border-color'); inputContainer.style.removeProperty('background-color'); } } const outsideButtons = searchWrap.querySelectorAll('.smart-search-outside-input-button'); outsideButtons.forEach((btn) => { if (isCustom) { btn.style.setProperty('background-color', config.buttonColor, 'important'); } else { btn.style.removeProperty('background-color'); } }); const btnSystemIcons = searchWrap.querySelectorAll('.smart-search-button-system-icon'); btnSystemIcons.forEach((icon) => { if (isCustom) { icon.style.setProperty('color', config.buttonIconColor, 'important'); } else { icon.style.removeProperty('color'); } }); const btnTexts = searchWrap.querySelectorAll('.smart-search-button-text'); btnTexts.forEach((txt) => { txt.style.setProperty('font-size', fontSize, 'important'); txt.style.setProperty('font-weight', fontWeight, 'important'); txt.style.setProperty('font-style', fontStyle, 'important'); if (isCustom) { txt.style.setProperty('color', config.buttonTextColor, 'important'); } else { txt.style.removeProperty('color'); } }); const inputIcons = searchWrap.querySelectorAll('.smart-search-outside-input-icon, .smart-search-outside-input-icon svg'); inputIcons.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.inputIconTextColor, 'important'); el.style.setProperty('fill', config.inputIconTextColor, 'important'); } else { el.style.removeProperty('color'); el.style.removeProperty('fill'); } }); const placeholderTexts = searchWrap.querySelectorAll('.smart-search-outside-input-placeholder-text'); placeholderTexts.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.inputIconTextColor, 'important'); } else { el.style.removeProperty('color'); } }); const systemIcons = searchWrap.querySelectorAll('.smart-search-system-icon, .smart-search-system-icon svg'); systemIcons.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.iconColor, 'important'); el.style.setProperty('fill', config.iconColor, 'important'); } else { el.style.removeProperty('color'); el.style.removeProperty('fill'); } }); const iconText = find('.smart-search-icon-text'); if (iconText) { iconText.style.setProperty('font-size', fontSize, 'important'); iconText.style.setProperty('font-weight', fontWeight, 'important'); iconText.style.setProperty('font-style', fontStyle, 'important'); if (isCustom) { iconText.style.setProperty('color', config.textColor, 'important'); } else { iconText.style.removeProperty('color'); } } }, _applyClickSearchInlineStyles(sidebar, config) { const isCustom = config.colorType === 'custom'; const find = (sel) => sidebar.querySelector(sel) || document.querySelector(sel); const searchForm = find('.smart-search-form'); if (searchForm) { searchForm.style.setProperty('border-radius', `${config.borderRadius}px`, 'important'); searchForm.style.setProperty('border-width', '1px', 'important'); searchForm.style.setProperty('border-style', 'solid', 'important'); searchForm.style.setProperty('overflow', 'hidden'); } const submitBtn = find('.smart-search-submit-btn'); const buttonText = find('.smart-search-sidebar-button-text'); if (buttonText) { buttonText.style.setProperty('font-size', `${config.fontSize}px`, 'important'); buttonText.style.setProperty('font-weight', config.fontBold ? 'bold' : 'normal', 'important'); buttonText.style.setProperty('font-style', config.fontItalic ? 'italic' : 'normal', 'important'); } if (submitBtn) { const hotWordsCarousel = sidebar.querySelector('.hot-words-carousel'); if (hotWordsCarousel) { const btnWidth = submitBtn.offsetWidth || 66; hotWordsCarousel.style.setProperty('right', `${btnWidth + 8}px`, 'important'); } } if (isCustom) { if (searchForm) { searchForm.style.setProperty('border-color', config.searchBorderColor, 'important'); } const inputContent = find('.smart-search-input-content'); if (inputContent) { inputContent.style.setProperty('background', config.searchBgColor, 'important'); inputContent.style.setProperty('border-color', 'transparent', 'important'); } if (submitBtn) { submitBtn.style.setProperty('background-color', config.buttonColor, 'important'); submitBtn.style.setProperty('color', config.textColor, 'important'); } const systemIcon = find('.smart-search-sidebar-button-system-icon'); if (systemIcon) { systemIcon.style.setProperty('color', config.iconColor, 'important'); const svg = systemIcon.querySelector('svg'); if (svg) svg.style.setProperty('fill', config.iconColor, 'important'); } if (buttonText) { buttonText.style.setProperty('color', config.textColor, 'important'); } const insideIcon = find('.smart-search-inside-system-icon'); if (insideIcon) { insideIcon.style.setProperty('color', config.inputIconTextColor, 'important'); const svg = insideIcon.querySelector('svg'); if (svg) svg.style.setProperty('fill', config.inputIconTextColor, 'important'); } const searchInput = find('.smart-search-input'); if (searchInput) { searchInput.style.setProperty('color', config.inputIconTextColor, 'important'); } } this._injectClickSearchScopedStyle(config); }, _injectClickSearchScopedStyle(config) { const isCustom = config.colorType === 'custom'; const styleId = 'smart-search-click-scoped-style'; let style = document.getElementById(styleId); if (!style) { style = document.createElement('style'); style.id = styleId; document.head.appendChild(style); } let css = ''; if (isCustom) { css = ` .hot-search { background: linear-gradient(180deg, ${config.hotSearchBgStartColor} 0%, ${config.hotSearchBgEndColor} 100%) !important; } .smart-search-input::placeholder { color: ${config.inputIconTextColor} !important; opacity: 0.6; } .hot-words-carousel-word { color: ${config.inputIconTextColor} !important; opacity: 0.6; } `; } style.textContent = css; }, applySearchStyleConfig(retryCount = 0) { const config = this.searchStyleConfig; const searchWrap = this.getBlockWrap(); if (!searchWrap) return; // 设置锁标记,防止 resize 期间重复调用 this._isApplyingStyle = true; // 重构后:静态内容在 ljs-render 外部,DOM 元素始终存在 const iconSearchLarge = searchWrap.querySelector('.app-smart-icon-search-large'); const isSearchBox = config.styleType === 'searchBox'; // 检查轮播是否已渲染,如果没有则短暂等待后重试 const placeholder = searchWrap.querySelector('.smart-search-outside-input-placeholder'); const carousel = searchWrap.querySelector('.app-smart-search-outside-carousel'); // 轮播组件可能需要额外时间渲染,等待后重试 if (placeholder && !carousel && retryCount < 10) { this._isApplyingStyle = false; setTimeout(() => this.applySearchStyleConfig(retryCount + 1), 50); return; } // 轮播已渲染,隐藏默认占位文本 if (placeholder && carousel) { placeholder.classList.add('has-carousel'); } this._applySearchInlineStyles(searchWrap, config); const searchBtn = searchWrap.querySelector('.app-smart-search-btn'); const outsideInputContainer = searchWrap.querySelector('.smart-search-outside-input-container'); const buttonType = config.searchButtonType || config.buttonType; if (searchBtn) { const marginConfig = this._getMarginConfig(config); searchBtn.style.marginLeft = `${marginConfig.left}px`; searchBtn.style.marginRight = `${marginConfig.right}px`; // 使用 data 属性控制样式类型显示(searchBox 或 imageText) searchBtn.dataset.styleType = isSearchBox ? 'searchBox' : 'imageText'; } // 搜索按钮显示/隐藏控制 const outsideButtons = searchWrap.querySelectorAll('.smart-search-outside-input-button'); outsideButtons.forEach((btn) => { btn.style.display = config.showSearchButton ? '' : 'none'; this._applyButtonContentDisplay(btn, config, { customIconSelector: '.smart-search-button-custom-icon', systemIconSelector: '.smart-search-button-system-icon', textSelector: '.smart-search-button-text', }); }); // 图文样式配置 - 使用 data 属性控制显示 if (iconSearchLarge) { const customIcon = iconSearchLarge.querySelector('.smart-search-custom-icon'); const iconText = iconSearchLarge.querySelector('.smart-search-icon-text'); const mobile = !isDesktop(); // 设置 data 属性,让 CSS 控制显示 iconSearchLarge.dataset.iconEnabled = config.iconEnabled ? 'true' : 'false'; iconSearchLarge.dataset.iconType = config.iconType || 'system'; if (mobile) { // 移动端:只显示图标,不显示文本 iconSearchLarge.dataset.textEnabled = 'false'; } else { iconSearchLarge.dataset.textEnabled = config.textEnabled ? 'true' : 'false'; } // 设置自定义图标 src(如果有) if (config.iconType === 'custom' && config.customIcon && customIcon) { customIcon.src = config.customIcon; } // 设置文本内容 if (iconText && config.textEnabled) { iconText.textContent = config.searchText || 'Search'; } } if (searchBtn) { searchBtn.classList.add('style-ready'); } // 解除锁标记 this._isApplyingStyle = false; }, _applyButtonContentDisplay(btn, config, selectors) { const { customIconSelector, systemIconSelector, textSelector } = selectors; const customIcon = btn.querySelector(customIconSelector); const systemIcon = btn.querySelector(systemIconSelector); const textSpan = btn.querySelector(textSelector); const buttonType = config.searchButtonType || config.buttonType; // 使用 data 属性驱动 CSS 显示,确保降级和一致性 btn.dataset.buttonType = buttonType || 'text'; if (buttonType === 'icon') { // 设置图标类型:custom 或 system(默认) const iconType = (config.iconType === 'custom' && config.customIcon) ? 'custom' : 'system'; btn.dataset.iconType = iconType; // 如果是自定义图标,设置 src if (iconType === 'custom' && customIcon) { customIcon.src = config.customIcon; } } else { // text 类型:移除 iconType 属性,更新文本内容 delete btn.dataset.iconType; if (textSpan) { textSpan.textContent = config.searchText || 'Search'; } } }, applyClickSearchStyleConfig(retryCount = 0) { const config = this.clickSearchStyleConfig; const sidebar = this._getSidebarOverlay ? this._getSidebarOverlay() : document.querySelector('.smart-search-sidebar-overlay'); if (!sidebar) return; const findElement = (selector) => sidebar.querySelector(selector) || document.querySelector(selector); const submitBtn = findElement('.smart-search-submit-btn'); if (!submitBtn && retryCount < 10) { setTimeout(() => this.applyClickSearchStyleConfig(retryCount + 1), 100); return; } if (this._sidebarObserver) this._sidebarObserver.disconnect(); this._applyClickSearchInlineStyles(sidebar, config); if (submitBtn) { this._applyButtonContentDisplay(submitBtn, config, { customIconSelector: '.smart-search-sidebar-button-custom-icon', systemIconSelector: '.smart-search-sidebar-button-system-icon', textSelector: '.smart-search-sidebar-button-text', }); } if (this._sidebarObserver) { this._sidebarObserver.observe(sidebar, { childList: true, subtree: true }); } }, }; // --- 侧边栏管理 Mixin(预加载模式)--- const SidebarManagerMixin = { // 侧边栏状态: idle -> preloading -> ready -> open -> idle _sidebarState: 'idle', _sidebarReady: false, _contentPreloaded: false, _contentPreloadPromise: null, _getSidebarOverlay() { const wrap = this.getBlockWrap(); if (wrap) { const overlay = wrap.querySelector('.smart-search-sidebar-overlay'); if (overlay) return overlay; } return document.querySelector('.smart-search-sidebar-overlay'); }, _getSidebarPanel() { const wrap = this.getBlockWrap(); if (wrap) { const panel = wrap.querySelector('.smart-search-sidebar'); if (panel) return panel; } return document.querySelector('.smart-search-sidebar'); }, _findVisibleSearchEntry() { const wrap = this.getBlockWrap(); if (wrap) { const content = wrap.querySelector('[id^="app-smart-product-search-content-"]'); if (content) return content; } return document.querySelector('[id^="app-smart-product-search-content-"]'); }, _alignSidebarToOutsideInput() { if (!isDesktop()) return; const panel = this._getSidebarPanel(); if (!panel) return; const refEl = this._findVisibleSearchEntry(); let topPos = 80; let rightPos = 20; if (refEl) { const rect = refEl.getBoundingClientRect(); const viewportWidth = document.documentElement.clientWidth; topPos = Math.max(0, rect.top); rightPos = Math.max(0, viewportWidth - rect.right); } panel.style.setProperty('top', topPos + 'px', 'important'); panel.style.setProperty('right', rightPos + 'px', 'important'); }, preloadSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const currentState = overlay.getAttribute('data-state'); if (this._sidebarReady || this._sidebarState === 'preloading' || this._sidebarState === 'open') return; if (currentState === 'open' || currentState === 'preloading') return; this._sidebarState = 'preloading'; overlay.setAttribute('data-state', 'preloading'); const configReady = new Promise((resolve) => { if (this._configLoaded) { resolve(); return; } this.getOutsideItemEl().then((outsideItem) => { if (outsideItem) { const apiData = outsideItem.getData() || {}; const { clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; } resolve(); }).catch(() => resolve()); }); // 先渲染内容(创建 DOM),再应用样式(依赖 DOM 存在) configReady.then(() => { const currentState = overlay.getAttribute('data-state'); if (currentState === 'open') return; this._contentPreloaded = false; this._contentPreloadPromise = this.initSidebarContent().then(() => { this.applyClickSearchStyleConfig(); this._contentPreloaded = true; }).catch(() => { this._contentPreloaded = false; this._contentPreloadPromise = null; }); this._sidebarReady = true; this._sidebarState = 'ready'; overlay.setAttribute('data-state', 'ready'); }); }, // 打开侧边栏 openSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; this._sidebarOpenWidth = window.innerWidth; this._sidebarOpenedAsDesktop = isDesktop(); this._savedScrollbarWidth = parseFloat(getComputedStyle(document.documentElement).marginRight) || 0; const configReady = new Promise((resolve) => { if (this._configLoaded) { resolve(); return; } this.getOutsideItemEl().then((outsideItem) => { if (outsideItem) { const apiData = outsideItem.getData() || {}; const { clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; } resolve(); }).catch(() => resolve()); }); configReady.then(() => { this.applyClickSearchStyleConfig(); this._sidebarReady = true; this._showSidebar(overlay); }); document.body.style.overflow = 'hidden'; if (!this._resizeHandler) { this._resizeHandler = () => this._alignSidebarToOutsideInput(); window.addEventListener('resize', this._resizeHandler); } this._setupSidebarObserver(overlay); }, _showSidebar(overlay) { const panel = this._getSidebarPanel(); if (!panel) return; this._sidebarState = 'open'; overlay.setAttribute('data-state', 'open'); const _isDesktop = isDesktop(); const ANIM_DURATION = 280; const _setupPanelLayout = () => { const smartSearchWrap = panel.querySelector('.smart-search-wrap'); if (smartSearchWrap) { smartSearchWrap.style.cssText = _isDesktop ? 'display: block !important; width: 100% !important;' : 'display: flex !important; flex-direction: column !important; width: 100% !important; flex: 1 !important; min-height: 0 !important;'; } const pageContent = panel.querySelector('.page-content'); if (pageContent) { pageContent.style.cssText = _isDesktop ? 'display: flex !important; flex-direction: column !important; width: 100% !important;' : 'display: flex !important; flex-direction: column !important; width: 100% !important; flex: 1 !important; min-height: 0 !important;'; } }; const _applyPanelPosition = () => { if (_isDesktop) { const refEl = this._findVisibleSearchEntry(); let topPos = 80; let rightPos = 20; if (refEl) { const rect = refEl.getBoundingClientRect(); const viewportWidth = document.documentElement.clientWidth; topPos = Math.max(0, rect.top); rightPos = Math.max(0, viewportWidth - rect.right); } panel.style.cssText = ` display: block !important; position: fixed !important; top: ${topPos}px !important; right: ${rightPos}px !important; left: auto !important; bottom: auto !important; width: 520px !important; max-width: 520px !important; background: #fff !important; visibility: visible !important; opacity: 1 !important; z-index: 10000 !important; padding: 16px !important; border-radius: 6px !important; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15) !important; pointer-events: auto !important; `; } else { panel.style.cssText = ` display: flex !important; flex-direction: column !important; position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; max-width: 100vw !important; background: #fff !important; visibility: visible !important; opacity: 1 !important; z-index: 10000 !important; padding: 16px !important; border-radius: 0 !important; box-shadow: none !important; pointer-events: auto !important; transform: translateX(100%) !important; `; } }; const revealSidebar = () => { _applyPanelPosition(); _setupPanelLayout(); this.applyClickSearchStyleConfig(); overlay.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; z-index: 9999 !important; visibility: visible !important; pointer-events: auto !important; display: block !important; `; this.checkHistoryOverflow(); if (!_isDesktop) { const contentWrap = panel.querySelector('.smart-search-wrap'); console.log('contentWrap'); if (contentWrap) { contentWrap.style.setProperty('opacity', '0', 'important'); } requestAnimationFrame(() => { panel.style.setProperty('transition', `transform ${ANIM_DURATION}ms cubic-bezier(0.16, 1, 0.3, 1)`, 'important'); panel.style.setProperty('transform', 'translateX(0)', 'important'); }); const fontsReady = (document.fonts && document.fonts.ready) ? document.fonts.ready : Promise.resolve(); const stableDelay = new Promise(r => setTimeout(r, ANIM_DURATION)); Promise.all([fontsReady, stableDelay]).then(() => { requestAnimationFrame(() => { if (contentWrap) { contentWrap.style.setProperty('opacity', '1', 'important'); } }); }); } const input = overlay.querySelector('.smart-search-input'); if (input) { setTimeout(() => input.focus(), _isDesktop ? 50 : ANIM_DURATION); } this._syncInsideCarousel(overlay); }; if (this._contentPreloaded) { revealSidebar(); return; } overlay.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; z-index: 9999 !important; visibility: hidden !important; pointer-events: none !important; display: block !important; opacity: 0 !important; `; _applyPanelPosition(); _setupPanelLayout(); const contentReady = this._contentPreloadPromise || this.initSidebarContent(); contentReady.then(() => { requestAnimationFrame(() => revealSidebar()); }).catch(() => { revealSidebar(); }); }, _setupSidebarObserver(overlay) { if (this._sidebarObserver) { this._sidebarObserver.disconnect(); } this._sidebarObserver = new MutationObserver(() => { if (this._applyStyleDebounceTimer) { clearTimeout(this._applyStyleDebounceTimer); } this._applyStyleDebounceTimer = setTimeout(() => { const submitBtn = overlay.querySelector('.smart-search-submit-btn'); if (submitBtn) { this.applyClickSearchStyleConfig(); } this.checkHistoryOverflow(); }, 50); }); this._sidebarObserver.observe(overlay, { childList: true, subtree: true }); }, _syncOutsideCarousel() { if (this.insideCarouselIndex === this.outsideCarouselIndex) return; this.outsideCarouselIndex = this.insideCarouselIndex; const outsideEl = document.querySelector('ljs-carousel[id^="app-smart-search-outside-carousel-"]'); if (!outsideEl) return; SPZ.whenApiDefined(outsideEl).then((api) => { try { api.goToSlide(String(this.outsideCarouselIndex)); } catch(e) {} }); }, closeSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const overlayState = overlay.getAttribute('data-state'); if (this._sidebarState === 'closing') return; if (this._sidebarState === 'idle' && overlayState !== 'open') return; this._syncOutsideCarousel(); this._sidebarState = 'closing'; const panel = this._getSidebarPanel(); const _wasDesktop = this._sidebarOpenedAsDesktop !== undefined ? this._sidebarOpenedAsDesktop : isDesktop(); const ANIM_DURATION = 250; const _cleanup = () => { this._sidebarState = 'idle'; this._sidebarReady = false; this._contentPreloaded = false; this._contentPreloadPromise = null; this._sidebarOpenedAsDesktop = undefined; overlay.setAttribute('data-state', 'idle'); overlay.style.cssText = ''; if (panel) { panel.style.cssText = ''; const smartSearchWrap = panel.querySelector('.smart-search-wrap'); if (smartSearchWrap) smartSearchWrap.style.cssText = ''; const pageContent = panel.querySelector('.page-content'); if (pageContent) pageContent.style.cssText = ''; const input = panel.querySelector('.smart-search-input'); if (input) { input.value = ''; input.removeAttribute('has-value'); } panel.removeAttribute('has-value'); panel.removeAttribute('data-empty'); panel.removeAttribute('loading'); const loadingEl = panel.querySelector('.smart-search-loading'); if (loadingEl) { loadingEl.removeAttribute('show'); loadingEl.setAttribute('hide', ''); } document.querySelectorAll('.hot-words-carousel-inner-container').forEach(el => { el.style.display = 'block'; }); } document.body.style.overflow = ''; const scrollbarWidth = this._savedScrollbarWidth || 0; if (scrollbarWidth > 0) { const html = document.documentElement; html.style.setProperty('overflow', 'hidden', 'important'); html.style.setProperty('margin-right', scrollbarWidth + 'px', 'important'); setTimeout(() => { html.style.removeProperty('overflow'); html.style.removeProperty('margin-right'); }, 50); } this._savedScrollbarWidth = 0; if (this._sidebarObserver) { this._sidebarObserver.disconnect(); this._sidebarObserver = null; } if (this._applyStyleDebounceTimer) { clearTimeout(this._applyStyleDebounceTimer); this._applyStyleDebounceTimer = null; } if (this._resizeHandler) { window.removeEventListener('resize', this._resizeHandler); this._resizeHandler = null; } this._historyExpanded = false; }; if (panel && !_wasDesktop) { panel.style.setProperty('transition', `transform ${ANIM_DURATION}ms cubic-bezier(0.5, 0, 0.7, 0.4)`, 'important'); panel.style.setProperty('transform', 'translateX(100%)', 'important'); setTimeout(_cleanup, ANIM_DURATION); } else { _cleanup(); } const sectionPrefix = 'shoplaza-section'; const announcement = document.getElementById(sectionPrefix + '-announcement'); const header = document.getElementById(sectionPrefix + '-header'); if (announcement) announcement.classList.remove('header_mask_open'); if (header) header.classList.remove('header_mask_open'); }, _restartCarouselAutoplay(carouselEl) { carouselEl.removeAttribute('autoplay'); carouselEl.setAttribute('pause', ''); setTimeout(() => { carouselEl.setAttribute('autoplay', ''); carouselEl.removeAttribute('pause'); }, 50); }, _syncInsideCarousel(overlay) { const targetIndex = this.outsideCarouselIndex || 0; const doSync = () => { const carouselEl = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (!carouselEl) return; SPZ.whenApiDefined(carouselEl).then((carouselApi) => { try { carouselApi.goToSlide(String(targetIndex)); } catch(e) {} this._restartCarouselAutoplay(carouselEl); }); }; const renderEl = overlay.querySelector('ljs-render[id^="hot-words-carousel-"]'); if (renderEl) { const existingCarousel = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (existingCarousel && existingCarousel.hasAttribute('dom-mounted')) { doSync(); } else { const observer = new MutationObserver(() => { const el = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (el && el.hasAttribute('dom-mounted')) { observer.disconnect(); doSync(); } }); observer.observe(renderEl, { childList: true, subtree: true, attributes: true }); setTimeout(() => { observer.disconnect(); doSync(); }, 3000); } } else { doSync(); } }, // 兼容旧的 onSidebarOpen/Close 方法 onSidebarOpen() { this.openSidebar(); }, onSidebarClose() { this.closeSidebar(); }, }; // --- 搜索历史折叠/展开 Mixin --- const HISTORY_COLLAPSED_ROWS = 6; const HISTORY_EXPAND_THRESHOLD = 10; const HistoryOverflowMixin = { _findHistoryList(root) { const sources = [root, document]; for (const src of sources) { const list = src.querySelector('.recently-history-list'); if (list) return list; const els = src.querySelectorAll('*'); for (const el of els) { if (el.shadowRoot) { const l = el.shadowRoot.querySelector('.recently-history-list'); if (l) return l; } } } return null; }, _getRowHeight(list) { const item = list.querySelector('.recently-history-item'); if (!item || item.getBoundingClientRect().height === 0) return 0; const rowGap = parseFloat(getComputedStyle(list).rowGap) || 0; return item.getBoundingClientRect().height + rowGap; }, _findToggleBtn(list) { const parent = list.closest('.recently-history-content'); return parent ? parent.querySelector('.history-toggle-btn') : null; }, _applyMaxHeight(list) { const item = list.querySelector('.recently-history-item'); if (!item || item.getBoundingClientRect().height === 0) return; const itemHeight = item.getBoundingClientRect().height; const rowGap = parseFloat(getComputedStyle(list).rowGap) || 0; const paddingTop = parseFloat(getComputedStyle(list).paddingTop) || 0; var maxHeight = Math.ceil(itemHeight * HISTORY_COLLAPSED_ROWS + rowGap * (HISTORY_COLLAPSED_ROWS - 1)) + paddingTop; if (isDesktop()) maxHeight--; list.style.setProperty('max-height', maxHeight + 'px'); }, checkHistoryOverflow() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const list = this._findHistoryList(overlay); if (!list) return; const items = list.querySelectorAll('.recently-history-item'); if (items.length === 0) return; this._applyMaxHeight(list); const needsCollapse = items.length > HISTORY_EXPAND_THRESHOLD; if (needsCollapse && !this._historyExpanded) { list.classList.add('history-collapsed'); } else { list.classList.remove('history-collapsed'); } const toggleBtn = this._findToggleBtn(list); if (toggleBtn) { if (needsCollapse && !this._historyExpanded) { toggleBtn.classList.remove('hidden'); } else { toggleBtn.classList.add('hidden'); } } }, expandHistory() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const list = this._findHistoryList(overlay); if (!list) return; this._historyExpanded = true; list.classList.remove('history-collapsed'); this._applyMaxHeight(list); const toggleBtn = this._findToggleBtn(list); if (toggleBtn) toggleBtn.classList.add('hidden'); }, }; const HotKeywordsMixin = { generateHotKeywordList(data) { const searchKeywords = data?.hotKeywordList || []; const isShowHotKeyword = data?.isShowHotKeyword || false; this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const hotwords = outsideItem.getData()?.search_keywords || []; const enrichedKeywords = this.enrichKeywords(searchKeywords, hotwords); this.renderHotKeywords(enrichedKeywords, isShowHotKeyword); }); }, enrichKeywords(keywords, hotwords) { return keywords.map((item) => { item.url_obj = item.url_obj || {}; const hotwordItem = hotwords.find(h => h.word === item.word); if (hotwordItem) { item.icon = hotwordItem.icon || ''; } if (!item.urlObj || !item.urlObj.url) { item.urlObj = { ...item.url_obj, url: item.url_obj.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj.url, }; } return item; }); }, renderHotKeywords(keywords, isShowHotKeyword) { document.querySelectorAll('.app-hot-keyword-render-child').forEach((el) => { SPZ.whenApiDefined(el).then((hotWordsChild) => { hotWordsChild.render({ list: keywords, isShowHotKeyword }); }); }); }, normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable) { if (findKeywordEnable === false) return []; if (findKeywords && findKeywords.length > 0) { return findKeywords.map(keyword => ({ word: keyword, icon: '', pic: '', type: 'find_keyword', url_obj: { type: 'search', url: `${SEARCH_URL}?q=${keyword}`, }, })); } return searchKeywords || []; }, normalizeKeywordUrl(item) { if (!item) return null; if (item.url_obj) { item.url_obj.url = item.url_obj.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj.url; } return item; }, onTapHotWord(type) { const index = type === 'inside' ? this.insideCarouselIndex : this.outsideCarouselIndex; this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData(); const findKeywords = apiData?.find_keywords || []; const searchKeywords = apiData?.search_keywords || []; const findKeywordEnable = apiData?.find_keyword_enable !== false; const keywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable); const currentItem = this.normalizeKeywordUrl(keywords[index] || null); if (currentItem) { this.handleHotKeyword({ args: { word: currentItem.word, query_type: currentItem.type, url: currentItem.url_obj?.url, } }); } else { this.executeSearch([''], 1); } }); }, getOutsideCarouselConfig() { return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return { outsideCarouselIndex: this.outsideCarouselIndex }; const apiData = outsideItem.getData(); const findKeywords = apiData?.find_keywords || []; const searchKeywords = apiData?.search_keywords || []; const findKeywordEnable = apiData?.find_keyword_enable !== false; const carouselKeywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable); return { ...apiData, search_keywords: carouselKeywords, outsideCarouselIndex: this.outsideCarouselIndex, }; }); }, }; const HISTORY_CACHE_KEY = 'smart_search_history'; const HISTORY_MAX_LEN = 30; const HOT_SEARCH_LEN = 6; const SMART_SEARCH_THINK_URL = '/api/search/suggestion'; const SearchControllerMixin = { // 搜索历史缓存 _historyCache: null, _searchData: null, _hotList: [], _curFindKeyword: '', // 初始化搜索历史 initHistoryCache() { if (this._historyCache) return; try { const cached = localStorage.getItem(HISTORY_CACHE_KEY); this._historyCache = cached ? JSON.parse(cached) : []; } catch (e) { this._historyCache = []; } }, // 获取历史列表 getHistoryList() { try { const cached = localStorage.getItem(HISTORY_CACHE_KEY); const historyList = cached ? JSON.parse(cached) : []; return historyList.slice().reverse(); } catch (e) { return []; } }, // 添加历史记录 addHistory(keyword) { if (!keyword || !keyword.trim()) return; this.initHistoryCache(); const index = this._historyCache.indexOf(keyword); if (index > -1) { this._historyCache.splice(index, 1); } this._historyCache.push(keyword); if (this._historyCache.length > HISTORY_MAX_LEN) { this._historyCache.shift(); } try { localStorage.setItem(HISTORY_CACHE_KEY, JSON.stringify(this._historyCache)); } catch (e) {} }, // 清除历史 clearHistory() { this._historyCache = []; try { localStorage.removeItem(HISTORY_CACHE_KEY); } catch (e) {} }, // 渲染表单区域 renderSearchForm() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData() || {}; const formData = { isOpenAutoThink: apiData.auto_think_enable || false, isOpenFindKeyword: apiData.find_keyword_enable || false, findKeywordList: apiData.find_keywords || [], }; const formRender = panel.querySelector('[role="form"]'); if (formRender) { return SPZ.whenApiDefined(formRender).then((api) => { api.render(formData); }); } }); }, // 渲染历史区域 renderSearchHistory() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; const historyList = this.getHistoryList(); const historyData = { isShowHistory: (apiData.user_history_enable !== false) && historyList.length > 0, historyList: historyList, }; const historyRender = panel.querySelector('[role="history"]'); if (historyRender) { return SPZ.whenApiDefined(historyRender).then((api) => { api.render(historyData); }); } }); }, // 渲染联想结果 renderThinkResult(thinkResult) { const panel = this._getSidebarPanel(); if (!panel) return; const thinkRender = panel.querySelector('[role="thinkresult"]'); if (thinkRender) { SPZ.whenApiDefined(thinkRender).then((api) => { api.render({ thinkResult }); }); } }, // 获取联想结果 fetchThinkResult(keyword) { if (!keyword || !keyword.trim()) { this.setThinkResultStatus(false, false); return Promise.resolve([]); } this.setThinkResultStatus(true, false); this.setLoadingStatus(true); return fetch(`${SMART_SEARCH_THINK_URL}?surface=autocomplete&keyword=${encodeURIComponent(keyword)}`) .then(res => res.json()) .then(res => { this.setLoadingStatus(false); const items = res.items || []; this.setThinkResultStatus(true, items.length === 0); const lowerKeyword = keyword.toLowerCase(); const thinkResult = items.map(item => ({ ...item, highlightHtml: item.word.replace( new RegExp(lowerKeyword, 'gi'), `${lowerKeyword}` ), })); this.renderThinkResult(thinkResult); return thinkResult; }) .catch(() => { this.setLoadingStatus(false); this.setThinkResultStatus(true, true); return []; }); }, // 设置联想结果状态 setThinkResultStatus(hasValue, isEmpty) { const panel = this._getSidebarPanel(); if (!panel) return; if (hasValue) { panel.setAttribute('has-value', ''); } else { panel.removeAttribute('has-value'); } if (isEmpty) { panel.setAttribute('data-empty', ''); } else { panel.removeAttribute('data-empty'); } }, // 设置 loading 状态 setLoadingStatus(loading) { const panel = this._getSidebarPanel(); if (!panel) return; if (loading) { panel.setAttribute('loading', ''); } else { panel.removeAttribute('loading'); } const loadingEl = panel.querySelector('.smart-search-loading'); if (loadingEl) { if (loading) { loadingEl.removeAttribute('hide'); loadingEl.setAttribute('show', ''); } else { loadingEl.removeAttribute('show'); loadingEl.setAttribute('hide', ''); } } }, // 处理表单输入 handleFormInput(invocation) { const keyword = invocation?.args?.keyword ?? invocation?.keyword ?? ''; this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; if (!apiData.auto_think_enable) return; this.fetchThinkResult(keyword); }); }, // 处理搜索提交 handleSearchSubmit(value) { const searchStr = Array.isArray(value) ? value[0] : value; if (!searchStr || !searchStr.trim()) { window.location.href = SEARCH_URL; return; } this.addHistory(searchStr); this.trackSearch(searchStr, 'user_input'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(searchStr)}`; }, // 处理历史点击 handleHistory(invocation) { const value = invocation?.args?.value ?? ''; if (!value) return; this.addHistory(value); this.trackSearch(value, 'user_history'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(value)}`; }, // 处理热词点击 handleHotKeyword(invocation) { const word = invocation?.args?.word ?? ''; const queryType = invocation?.args?.query_type ?? 'user_keyword'; const url = invocation?.args?.url ?? ''; if (!word) return; this.addHistory(word); this.trackSearch(word, queryType); if (url && !url.includes(SEARCH_URL)) { window.location.href = url; } else { window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(word)}`; } }, // 处理联想结果点击 handleThinkResult(invocation) { const word = invocation?.args?.word ?? ''; if (!word) return; this.addHistory(word); this.trackSearch(word, 'auto_think'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(word)}`; }, // 处理清除历史 handleClearHistory() { this.clearHistory(); this.renderSearchHistory(); }, // 处理刷新热词 handleRefreshHot() { this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; const searchKeywords = apiData.search_keywords || []; if (searchKeywords.length <= HOT_SEARCH_LEN) return; // 直接调用渲染方法(会使用 _hotList 的分页逻辑) this.renderHotKeywordDirect(); }); }, // 埋点 trackSearch(query, queryType) { const trackQueryType = { 'user_input': 1, 'user_history': 2, 'user_keyword': 3, 'smart_keyword': 4, 'auto_think': 5, 'find_keyword': 6, }; if (window.sa) { window.sa.track('search_request', { event_info: JSON.stringify({ query, query_type: queryType }), function_name: 'smart_search', }); window.sa.registerAID(`smart_search.${trackQueryType[queryType] || 1}.${query}`); } }, // 初始化侧边栏内容渲染(返回 Promise,所有内容渲染完成后 resolve) initSidebarContent() { return Promise.all([ this.renderSearchForm(), this.renderSearchHistory(), this.renderHotKeywordDirect(), ]); }, // 直接渲染热搜词 renderHotKeywordDirect() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData() || {}; const searchKeywords = apiData.search_keywords || []; const hotKeywordList = this.getHotKeywordList(searchKeywords); const hotKeywordData = { isShowHotKeyword: searchKeywords.length > 0, list: hotKeywordList, }; const hotKeywordRender = panel.querySelector('[role="hotkeyword"].app-hot-keyword-render-child'); if (hotKeywordRender) { return SPZ.whenApiDefined(hotKeywordRender).then((api) => { api.render(hotKeywordData); }); } }); }, // 获取热搜词列表(带分页逻辑) getHotKeywordList(searchKeywords) { const enrichedList = searchKeywords.map(item => ({ ...item, urlObj: { ...item.url_obj, url: item.url_obj?.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj?.url, }, })); // 用于刷新功能的分页逻辑 if (!this._hotListIndex) { this._hotListIndex = 0; } const startIndex = this._hotListIndex; const endIndex = startIndex + HOT_SEARCH_LEN; const result = enrichedList.slice(startIndex, endIndex); // 更新索引,循环使用 this._hotListIndex = endIndex >= enrichedList.length ? 0 : endIndex; return result.length > 0 ? result : enrichedList.slice(0, HOT_SEARCH_LEN); }, }; // --- 主组件 --- class SpzCustomSmartSearchLocation extends SPZ.BaseElement { constructor(element) { super(element); this.outsideCarouselIndex = 0; this.insideCarouselIndex = 0; this.searchItemType = 'icon'; this._originalSearchWrapParent = null; this._skipMobileInit = false; this.searchStyleConfig = { ...DEFAULT_SEARCH_STYLE_CONFIG }; this.clickSearchStyleConfig = { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.bindResizeListener(); this.registerActions(); // 设置超时降级:如果 API 在 5 秒内没有返回,显示默认样式 this._styleFallbackTimer = setTimeout(() => { if (!this._configLoaded) { this._applyStyleFallback(); } }, 5000); } mountCallback(){ this.safeInit(); } // API 超时/报错时的降级处理 _applyStyleFallback() { const searchWrap = this.getBlockWrap(); if (!searchWrap) return; const searchBtn = searchWrap.querySelector('.app-smart-search-btn'); if (searchBtn && !searchBtn.classList.contains('style-ready')) { searchBtn.classList.add('style-fallback'); } } // 清除降级定时器 _clearFallbackTimer() { if (this._styleFallbackTimer) { clearTimeout(this._styleFallbackTimer); this._styleFallbackTimer = null; } } unmountCallback(){ this.unbindResizeListener(); this.unregisterActions(); } // --- 初始化 --- safeInit() { this.relocatePlugin(); this.applySearchIconClass(); this.adjustLifestyleIcon(); if (!isDesktop() && !this._skipMobileInit && !this._mobileInitDone) { this.initMobileSmartSearch(); this._mobileInitDone = true; } } init() { this.safeInit(); if (this.searchItemType === 'input') { this.initInputMode(); return; } this.initIconMode(); } // --- Action 注册 --- registerActions() { this.registerAction('onSearchInputChange', (invocation) => { this.onSearchInputChange(invocation.args.keyword); }); this.registerAction('onSearchFormSubmit', (invocation) => { this.onSearchFormSubmit(invocation.args.event); }); this.registerAction('onOutsideCarouselIndexChange', (invocation) => { this.outsideCarouselIndex = invocation.args.index || 0; }); this.registerAction('onInsideCarouselIndexChange', (invocation) => { this.insideCarouselIndex = invocation.args.index || 0; }); this.registerAction('getSearchItemType', () => { this.fetchAndApplySearchItemType(); }); this.registerAction('generateHotKeywordList', (invocation) => { this.generateHotKeywordList(invocation.args?.data?.data); }); this.registerAction('onTapHotWord', (invocation) => { this.onTapHotWord(invocation.args.type); }); this.registerAction('onSidebarOpen', () => { this.onSidebarOpen(); }); this.registerAction('onSidebarClose', () => { this.onSidebarClose(); }); this.registerAction('openSidebar', () => { this.openSidebar(); }); this.registerAction('closeSidebar', () => { this.closeSidebar(); }); this.registerAction('expandHistory', () => { this.expandHistory(); }); // 搜索控制器 actions this.registerAction('handleFormInput', (invocation) => { this.handleFormInput(invocation); }); this.registerAction('handleHistory', (invocation) => { this.handleHistory(invocation); }); this.registerAction('handleHotKeyword', (invocation) => { this.handleHotKeyword(invocation); }); this.registerAction('handleThinkResult', (invocation) => { this.handleThinkResult(invocation); }); this.registerAction('handleClearHistory', () => { this.handleClearHistory(); }); this.registerAction('handleRefreshHot', () => { this.handleRefreshHot(); }); } // --- 搜索输入 & 提交 --- onSearchInputChange(keyword) { const hasValue = keyword && keyword.length > 0; const display = hasValue ? 'none' : 'block'; // 控制热词轮播显示 document.querySelectorAll('.hot-words-carousel-inner-container').forEach(el => { el.style.display = display; }); // 设置 input 元素的 has-value 属性(控制清除按钮和热词轮播的 CSS 样式) const panel = this._getSidebarPanel(); if (panel) { const input = panel.querySelector('.smart-search-input'); if (input) { if (hasValue) { input.setAttribute('has-value', ''); } else { input.removeAttribute('has-value'); } } } } onSearchFormSubmit(event) { const keywordArray = event.q || []; const keyword = keywordArray[0]; if (keyword !== null && keyword.length) { this.executeSearch(keywordArray, 1); } else { this.onTapHotWord('inside'); } } executeSearch(value, retryCount) { const searchStr = Array.isArray(value) ? value[0] : value; this.handleSearchSubmit(searchStr); } // --- 搜索项类型 --- fetchAndApplySearchItemType() { this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) { // API 获取失败,应用降级样式 this._applyStyleFallback(); return; } // 清除降级定时器 this._clearFallbackTimer(); const apiData = outsideItem.getData() || {}; const hasHeaderStyle = apiData.header_style && apiData.header_style !== '' && apiData.header_style !== '{}'; const { searchStyleConfig, clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.searchStyleConfig = searchStyleConfig; this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; if (hasHeaderStyle) { this.searchItemType = searchStyleConfig.styleType === 'searchBox' ? 'input' : 'icon'; } else { const type = apiData.search_item_type; if (type) { this.searchItemType = type; } else { this.searchItemType = 'icon'; } this.searchStyleConfig.styleType = this.searchItemType === 'input' ? 'searchBox' : 'imageText'; } this.applySearchStyleConfig(); this.init(); // 接口数据加载完成后,预加载侧边栏 this.preloadSidebar(); }).catch(() => { // API 报错,应用降级样式 this._applyStyleFallback(); }); } // --- 窗口监听 --- bindResizeListener() { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = SPZCore.Types.debounce( this.win, () => { const viewportWidth = document.documentElement.clientWidth || window.innerWidth; // 防止在 ljs-render 渲染过程中触发重复操作 if (this._isApplyingStyle) return; const widthChanged = !this._sidebarOpenWidth || window.innerWidth !== this._sidebarOpenWidth; const sidebarVisible = this._sidebarState === 'open' || this._sidebarState === 'ready' || this._sidebarState === 'preloading'; const overlay = this._getSidebarOverlay && this._getSidebarOverlay(); const overlayOpen = overlay && overlay.getAttribute('data-state') === 'open'; if (sidebarVisible || overlayOpen) { if (!widthChanged) return; this.closeSidebar(); } this.fetchAndApplySearchItemType(); }, DELAY ); window.addEventListener('resize', window.smartSearchResizeCallback); } unbindResizeListener() { if (window.smartSearchResizeCallback) { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = null; } if (this._relocateTimer) { clearInterval(this._relocateTimer); this._relocateTimer = null; } } unregisterActions() { const actionNames = [ 'onSearchInputChange', 'onSearchFormSubmit', 'onOutsideCarouselIndexChange', 'onInsideCarouselIndexChange', 'getSearchItemType', 'generateHotKeywordList', 'onTapHotWord', 'expandHistory', ]; actionNames.forEach((name) => { this.registerAction(name, () => {}); }); } } Object.assign(SpzCustomSmartSearchLocation.prototype, ElementFinderMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, StyleApplicatorMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, SidebarManagerMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, HistoryOverflowMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, HotKeywordsMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, MobileLayoutMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, SearchControllerMixin); SPZ.defineElement(TAG, SpzCustomSmartSearchLocation); class SpzCustomSmartSearchToast extends SPZ.BaseElement { constructor(element) { super(element); this.toastDom = null; this.toastTimeout = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback(){ this.init(); } init(){ const toast = document.createElement('div'); toast.id = 'spz-custom-smart-search-toast-899'; toast.className = 'spz-custom-smart-search-toast'; document.body.appendChild(toast); this.toastDom = toast; this.registerAction('showToast',(invocation)=>{ this.showToast(invocation.args); }); this.registerAction('hideToast',(invocation)=>{ this.hideToast(invocation.args); }); } showToast({ message, duration = 2000 }){ if( !this.toastDom ) return; this.toastDom.innerHTML = message; this.toastDom.classList.add('smart-search-toast-show'); clearTimeout(this.toastTimeout); this.toastTimeout = setTimeout(() => { this.hideToast(); }, duration); } hideToast(){ if( !this.toastDom ) return; this.toastDom.classList.remove('smart-search-toast-show'); } } SPZ.defineElement('spz-custom-smart-search-toast', SpzCustomSmartSearchToast); class SpzCustomSmartSearchCookie extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.registerAction('getCookie',(invocation)=>{ this.getCookie(invocation.args); }); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } getCookie(key) { let cookieMap = {} document.cookie.split(';').map(item=>{ let [key, value] = item.trim().split('=') cookieMap[key] = value }) return cookieMap[key] || ''; } } SPZ.defineElement('spz-custom-smart-search-cookie', SpzCustomSmartSearchCookie); const default_function_name = 'smart_search'; const default_plugin_name = 'smart_search'; const default_module_type = 'smart_search'; const default_module = 'apps'; const default_business_type = 'product_plugin'; const default_event_developer = 'ray'; class SpzCustomSmartSearchTrack extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.registerAction('track', (invocation) => { const { trackType, trackData } = invocation.args; this.track({trackType, trackData}); }); } track({trackType, trackData}) { const { function_name, plugin_name, module_type, module, business_type, event_developer, event_type, event_desc, trackEventInfo, ...otherTrackData } = trackData; window.sa.track(trackType, { function_name: function_name || default_function_name, plugin_name: plugin_name || default_plugin_name, module_type: module_type || default_module_type, module: module || default_module, business_type: business_type || default_business_type, event_developer: event_developer || default_event_developer, event_type: event_type, event_desc: event_desc, ...otherTrackData, event_info: JSON.stringify({ ...(trackEventInfo || {}), }), }); } } SPZ.defineElement('spz-custom-smart-search-track', SpzCustomSmartSearchTrack);
function setSearchUrl(searchValue) { return Promise.resolve({ url: '/search?q=' + searchValue }); } exportFunction('setSearchUrl', setSearchUrl); function setSearchUrl(searchValue) { return Promise.resolve({ url: '/search?q=' + searchValue }); } exportFunction('setSearchUrl', setSearchUrl);
Account
Log in Create an account
Cart
  • New In
    New In
    • COLLECTION
      COLLECTION
      • TRENDY FASHION
      • CASUAL CHIC
      • LANTERN SLEEVE
    • CATEGORY
      CATEGORY
      • NEW IN DRESSES
      • NEW IN TOPS
      • NEW IN BOTTOMS
  • Hot Sale
    Hot Sale
    • DRESSES
    • Strap Series
  • Dresses
    Dresses
    • SHOP BY STYLE
      SHOP BY STYLE
      • COTTON
      • DENIM
      • EMBROIDERY
      • LINEN
      • KNIT
    • SHOP BY LENGTH
      SHOP BY LENGTH
      • Mini Dresses
      • Midi Dresses
      • Maxi Dresses
    • SHOP BY PATTERN
      SHOP BY PATTERN
      • FLORAL
      • SOLID COLOR
      • PLAID
      • PATCHWORK
      • POLKA DOT
      • STRIPED
  • Tops
    Tops
    • T-shirts
    • Blouses&Shirts
    • Sweatshirts
    • Vests
    • Outwears
  • Bottoms
    Bottoms
    • Skirts
    • Shorts
    • Pants
    • Jeans
  • Co-ords&Jumpsuits
    Co-ords&Jumpsuits
    • Co-ords
    • Jumpsuits
  • ACCS&SHOES
    ACCS&SHOES
    • Jewelry
      Jewelry
      • Earrings
      • Necklaces
      • Bracelets
      • Body Chain
      • Rings
    • Accessories
      Accessories
      • Bags
      • Scarfs&Hats
      • Warmers
      • Decoration
      • Blankets
    • Shoes
      Shoes
      • Thick Bottom Shoes
      • Boots
      • Household Slipper
      • Headwears
  • Log in Create an account
    Search
    search Search
    search Search
    search Search
    No results for search
    const templateName = SHOPLAZZA?.meta?.page?.template_name || ''; const SEARCH_URL = '/search'; const TAG = 'spz-custom-smart-search-location'; const SEARCH_CONTAINER_CLASS = 'app-smart-product-search-container'; const THEME_NAME = window.SHOPLAZZA.theme.merchant_theme_name.replace(/ /g, ''); const BREAKPOINT = 960; const DELAY = 300; const DEFAULT_SEARCH_STYLE_CONFIG = { styleType: 'imageText', borderRadius: 4, marginEnabled: false, margin: { mobile: { left: 0, right: 0, linked: true }, pc: { left: 0, right: 0, linked: true }, }, showSearchButton: true, searchButtonType: 'text', iconEnabled: true, iconType: 'system', customIcon: '', textEnabled: false, fontStyle: 'normal', fontBold: false, fontSize: 14, fontItalic: false, searchText: 'Search', colorType: 'theme', iconColor: '#202020', textColor: '#202020', buttonTextColor: '#FFFFFF', buttonIconColor: '#FFFFFF', buttonColor: '#202020', searchBorderColor: '#202020', searchBgColor: '#FFFFFF', inputIconTextColor: '#6D7175', }; const DEFAULT_CLICK_SEARCH_STYLE_CONFIG = { buttonType: 'text', iconType: 'system', customIcon: '', fontStyle: 'normal', fontBold: false, fontSize: 14, fontItalic: false, searchText: 'Search', borderRadius: 4, colorType: 'theme', iconColor: '#FFFFFF', textColor: '#FFFFFF', buttonColor: '#202020', searchBorderColor: '#202020', searchBgColor: '#FFFFFF', inputIconTextColor: '#6D7175', hotSearchBgStartColor: '#FFE5E6', hotSearchBgEndColor: '#FFFCFC', }; // --- 工具函数 --- function parseHeaderStyle(headerStyleStr) { const hasHeaderStyle = headerStyleStr && headerStyleStr !== '' && headerStyleStr !== '{}'; if (!hasHeaderStyle) { return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }, }; } try { const parsed = typeof headerStyleStr === 'string' ? JSON.parse(headerStyleStr) : headerStyleStr; return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG, ...(parsed.searchStyleConfig || {}) }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG, ...(parsed.clickSearchStyleConfig || {}) }, }; } catch (e) { console.error('parseHeaderStyle error:', e); return { searchStyleConfig: { ...DEFAULT_SEARCH_STYLE_CONFIG }, clickSearchStyleConfig: { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }, }; } } function matchTheme(target) { return THEME_NAME.toLocaleLowerCase().includes(target.toLocaleLowerCase()); } function resolveThemeValue(themeMap, defaultValue) { let result = defaultValue; for (const key of Object.keys(themeMap)) { if (matchTheme(key)) result = themeMap[key]; } return result; } function joinSelectors(selectorList) { return [...new Set(selectorList)].join(','); } function isDesktop() { // 编辑器环境检测:编辑器可能使用 CSS transform 模拟移动端,此时 matchMedia 不准确 // 优先使用 document.documentElement.clientWidth 检测实际可视区域宽度 const viewportWidth = document.documentElement.clientWidth || window.innerWidth; return viewportWidth >= BREAKPOINT; } // --- 元素查找 Mixin --- const ElementFinderMixin = { getBlockWrap() { return this.element.closest('.app-smart-product-search-wrap') || document.querySelector('.app-smart-product-search-wrap'); }, getBlockContainer() { return this.element.closest('.' + SEARCH_CONTAINER_CLASS) || document.querySelector('.' + SEARCH_CONTAINER_CLASS); }, resolveBlockElement(selector, fallbackId) { const wrap = this.getBlockWrap(); const el = wrap?.querySelector(selector) || document.getElementById(fallbackId); return el ? SPZ.whenApiDefined(el) : Promise.resolve(null); }, getSmartSearchEl() { return this.resolveBlockElement('ljs-search', 'app-smart-search-974'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-974'); }, }; // --- 主题配置 --- const HEADER_SELECTOR = resolveThemeValue({ eva: 'header .header_grid_layout', geek: '.header-mobile-inner-container', onePage: 'header .header', wind: 'header #header-nav', nova: 'header .header', hero: 'header .header__nav', flash: '#shoplaza-section-header>div>div', lifestyle: '.header__wrapper', reformia: 'header#header', }, 'header'); const SEARCH_ICON_CLASS = resolveThemeValue({ flash: 'app-smart-icon-search-large-flash', hero: 'app-smart-icon-search-large-hero', geek: 'app-smart-icon-search-large-geek', nova: 'app-smart-icon-search-large-nova', }, 'app-smart-icon-search-large-default'); const PLUGIN_RELOCATION_CONFIG = resolveThemeValue({ reformia: { pc: '.header-layout .header__actions', mobile: '.header-layout .header__actions', }, }, null); // --- 布局 Mixin --- const MobileLayoutMixin = { relocatePlugin() { if (!PLUGIN_RELOCATION_CONFIG) return; const targetSelector = isDesktop() ? PLUGIN_RELOCATION_CONFIG.pc : PLUGIN_RELOCATION_CONFIG.mobile; if (!targetSelector) return; if (this._relocateTimer) { clearInterval(this._relocateTimer); } const attemptRelocate = () => { const container = this.element.closest('.' + SEARCH_CONTAINER_CLASS) || document.querySelector('#app-smart-product-search-container-974'); if (!container || !document.body.contains(container)) return false; const target = document.querySelector(targetSelector); if (!target) return false; if (target.contains(container)) return true; target.insertBefore(container, target.firstChild); return true; }; if (attemptRelocate()) return; let attempts = 0; this._relocateTimer = setInterval(() => { attempts++; if (attemptRelocate() || attempts >= 20) { clearInterval(this._relocateTimer); this._relocateTimer = null; } }, 500); }, applySearchIconClass() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.classList.add(SEARCH_ICON_CLASS); }); }, adjustLifestyleIcon() { if (!matchTheme('lifestyle') || this.searchItemType === 'input' || isDesktop()) return; if (window.__smartSearchLifestyleIconMoved__) { this._lifestyleIconMoved = true; return; } const container = this.getBlockContainer(); if (!container) return; const alreadyMoved = !!document.querySelector( '.header__wrapper .container .row.header>div>.app-smart-product-search-container' ); if (alreadyMoved) { this._lifestyleIconMoved = true; window.__smartSearchLifestyleIconMoved__ = true; return; } const headerDivs = document.querySelectorAll('.header__wrapper .container .row.header>div'); if (!headerDivs.length) return; const lastDiv = headerDivs[headerDivs.length - 1]; lastDiv.appendChild(container); this._lifestyleIconMoved = true; window.__smartSearchLifestyleIconMoved__ = true; }, initInputMode() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.style.display = 'none'; }); const searchWrap = this.getBlockWrap(); const pcContainer = this.getBlockContainer(); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (!this._originalSearchWrapParent && searchWrap && searchWrap.parentElement) { this._originalSearchWrapParent = searchWrap.parentElement; } if (isDesktop()) { if (mobileContainer) mobileContainer.style.display = 'none'; if (searchWrap && this._originalSearchWrapParent) { if (mobileContainer && mobileContainer.contains(searchWrap)) { this._originalSearchWrapParent.appendChild(searchWrap); } } // PC 端:显示所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'block'; }); return; } if (templateName === 'search') { this._skipMobileInit = true; return; } // 移动端:隐藏所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'none'; }); this.ensureMobileSearchContainer(); const mobileContainerAfterEnsure = document.querySelector('.smart-search-mobile-container'); if (!mobileContainerAfterEnsure) return; const existingWrap = mobileContainerAfterEnsure.querySelector('.app-smart-product-search-wrap'); if (existingWrap && existingWrap !== searchWrap) { return; } if (searchWrap && !mobileContainerAfterEnsure.contains(searchWrap)) { mobileContainerAfterEnsure.appendChild(searchWrap); } mobileContainerAfterEnsure.style.display = ''; }, ensureMobileSearchContainer() { if (document.querySelector('.smart-search-mobile-container')) return; const header = document.querySelector(HEADER_SELECTOR); if (!header) return; const container = document.createElement('div'); container.classList.add('smart-search-mobile-container'); container.classList.add('smart-search-mobile-container-' + THEME_NAME.toLocaleLowerCase()); header.appendChild(container); }, initIconMode() { document.querySelectorAll('.app-smart-icon-search-large').forEach(el => { el.style.display = 'flex'; }); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (mobileContainer) mobileContainer.style.display = 'none'; // 显示所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = ''; }); }, hasMobilePluginParent() { return !['geek', 'flash', 'boost', 'reformia'].includes(THEME_NAME.toLocaleLowerCase()); }, showMobileSmartSearch() { if (this._mobileSearchShown) return; const PLUGIN_PARENT_SELECTORS = { nova: '.header__mobile #header__plugin-container', hero: '.header__icons .tw-flex.tw-justify-end.tw-items-center.tw-space-x-7', onePage: '.header__mobile #header__plugin-container', wind: '#header-icons .flex.justify-end.items-center', eva: '#header__icons .plugin_content', }; const parentEl = document.querySelector( joinSelectors(Object.values(PLUGIN_PARENT_SELECTORS)) ); if (!parentEl) return; const hasHiddenClass = parentEl.classList.contains('md:hidden') || parentEl.classList.contains('md:tw-hidden'); if (hasHiddenClass) { Array.from(parentEl.children).forEach((child) => { if (!this.isSmartSearchElement(child)) { child.style.display = 'none'; } }); parentEl.classList.remove('md:hidden', 'md:tw-hidden'); } else { const smartSearchEl = Array.from(parentEl.children).find( (child) => this.isSmartSearchElement(child) ); if (smartSearchEl) { smartSearchEl.style.display = 'block'; } } this._mobileSearchShown = true; }, isSmartSearchElement(el) { return ( el.classList.contains(SEARCH_CONTAINER_CLASS) || el.querySelectorAll(`.${SEARCH_CONTAINER_CLASS}`).length > 0 ); }, addMobileSmartSearch() { if (this._mobileSearchAdded) return; const HEADER_ICONS_SELECTORS = { geek: '#header-mobile-container .flex.items-center.justify-end.flex-shrink-0', flash: '#header-layout .header__icons', boost: '.header__mobile-bottom .tw-flex.tw-items-center.tw-justify-end.tw-flex-1', reformia: '.header-layout .header__actions', }; const SMART_SEARCH_ANCESTORS = [ '#header-menu-mobile #menu-drawer', '#menu-drawer .plugin__header-content', '.header__drawer', '.header-content .logo-wrap', '.header_hamburger_sidebar-container', ]; const iconsEl = document.querySelector( joinSelectors(Object.values(HEADER_ICONS_SELECTORS)) ); const searchWrapSelector = joinSelectors( SMART_SEARCH_ANCESTORS.map(a => `${a} .${SEARCH_CONTAINER_CLASS}`) ); const searchWrapEl = document.querySelector(searchWrapSelector); if (!iconsEl || !searchWrapEl) return; iconsEl.insertAdjacentElement('afterbegin', searchWrapEl); this._mobileSearchAdded = true; }, initMobileSmartSearch() { if (this._lifestyleIconMoved) return; if (this.hasMobilePluginParent()) { this.showMobileSmartSearch(); } else { this.addMobileSmartSearch(); } }, }; const StyleApplicatorMixin = { _getMarginConfig(config) { if (!config.marginEnabled || !config.margin) { return { left: 0, right: 0 }; } const device = isDesktop() ? 'pc' : 'mobile'; return config.margin[device] || { left: 0, right: 0 }; }, _applySearchInlineStyles(searchWrap, config) { const isCustom = config.colorType === 'custom'; const find = (sel) => searchWrap.querySelector(sel); const borderRadius = `${config.borderRadius}px`; const fontSize = `${config.fontSize}px`; const fontWeight = config.fontBold ? 'bold' : 'normal'; const fontStyle = config.fontItalic ? 'italic' : 'normal'; const inputContainer = find('.smart-search-outside-input-container'); if (inputContainer) { inputContainer.style.setProperty('border-radius', borderRadius, 'important'); if (isCustom) { inputContainer.style.setProperty('border-color', config.searchBorderColor, 'important'); inputContainer.style.setProperty('background-color', config.searchBgColor, 'important'); } else { inputContainer.style.removeProperty('border-color'); inputContainer.style.removeProperty('background-color'); } } const outsideButtons = searchWrap.querySelectorAll('.smart-search-outside-input-button'); outsideButtons.forEach((btn) => { if (isCustom) { btn.style.setProperty('background-color', config.buttonColor, 'important'); } else { btn.style.removeProperty('background-color'); } }); const btnSystemIcons = searchWrap.querySelectorAll('.smart-search-button-system-icon'); btnSystemIcons.forEach((icon) => { if (isCustom) { icon.style.setProperty('color', config.buttonIconColor, 'important'); } else { icon.style.removeProperty('color'); } }); const btnTexts = searchWrap.querySelectorAll('.smart-search-button-text'); btnTexts.forEach((txt) => { txt.style.setProperty('font-size', fontSize, 'important'); txt.style.setProperty('font-weight', fontWeight, 'important'); txt.style.setProperty('font-style', fontStyle, 'important'); if (isCustom) { txt.style.setProperty('color', config.buttonTextColor, 'important'); } else { txt.style.removeProperty('color'); } }); const inputIcons = searchWrap.querySelectorAll('.smart-search-outside-input-icon, .smart-search-outside-input-icon svg'); inputIcons.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.inputIconTextColor, 'important'); el.style.setProperty('fill', config.inputIconTextColor, 'important'); } else { el.style.removeProperty('color'); el.style.removeProperty('fill'); } }); const placeholderTexts = searchWrap.querySelectorAll('.smart-search-outside-input-placeholder-text'); placeholderTexts.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.inputIconTextColor, 'important'); } else { el.style.removeProperty('color'); } }); const systemIcons = searchWrap.querySelectorAll('.smart-search-system-icon, .smart-search-system-icon svg'); systemIcons.forEach((el) => { if (isCustom) { el.style.setProperty('color', config.iconColor, 'important'); el.style.setProperty('fill', config.iconColor, 'important'); } else { el.style.removeProperty('color'); el.style.removeProperty('fill'); } }); const iconText = find('.smart-search-icon-text'); if (iconText) { iconText.style.setProperty('font-size', fontSize, 'important'); iconText.style.setProperty('font-weight', fontWeight, 'important'); iconText.style.setProperty('font-style', fontStyle, 'important'); if (isCustom) { iconText.style.setProperty('color', config.textColor, 'important'); } else { iconText.style.removeProperty('color'); } } }, _applyClickSearchInlineStyles(sidebar, config) { const isCustom = config.colorType === 'custom'; const find = (sel) => sidebar.querySelector(sel) || document.querySelector(sel); const searchForm = find('.smart-search-form'); if (searchForm) { searchForm.style.setProperty('border-radius', `${config.borderRadius}px`, 'important'); searchForm.style.setProperty('border-width', '1px', 'important'); searchForm.style.setProperty('border-style', 'solid', 'important'); searchForm.style.setProperty('overflow', 'hidden'); } const submitBtn = find('.smart-search-submit-btn'); const buttonText = find('.smart-search-sidebar-button-text'); if (buttonText) { buttonText.style.setProperty('font-size', `${config.fontSize}px`, 'important'); buttonText.style.setProperty('font-weight', config.fontBold ? 'bold' : 'normal', 'important'); buttonText.style.setProperty('font-style', config.fontItalic ? 'italic' : 'normal', 'important'); } if (submitBtn) { const hotWordsCarousel = sidebar.querySelector('.hot-words-carousel'); if (hotWordsCarousel) { const btnWidth = submitBtn.offsetWidth || 66; hotWordsCarousel.style.setProperty('right', `${btnWidth + 8}px`, 'important'); } } if (isCustom) { if (searchForm) { searchForm.style.setProperty('border-color', config.searchBorderColor, 'important'); } const inputContent = find('.smart-search-input-content'); if (inputContent) { inputContent.style.setProperty('background', config.searchBgColor, 'important'); inputContent.style.setProperty('border-color', 'transparent', 'important'); } if (submitBtn) { submitBtn.style.setProperty('background-color', config.buttonColor, 'important'); submitBtn.style.setProperty('color', config.textColor, 'important'); } const systemIcon = find('.smart-search-sidebar-button-system-icon'); if (systemIcon) { systemIcon.style.setProperty('color', config.iconColor, 'important'); const svg = systemIcon.querySelector('svg'); if (svg) svg.style.setProperty('fill', config.iconColor, 'important'); } if (buttonText) { buttonText.style.setProperty('color', config.textColor, 'important'); } const insideIcon = find('.smart-search-inside-system-icon'); if (insideIcon) { insideIcon.style.setProperty('color', config.inputIconTextColor, 'important'); const svg = insideIcon.querySelector('svg'); if (svg) svg.style.setProperty('fill', config.inputIconTextColor, 'important'); } const searchInput = find('.smart-search-input'); if (searchInput) { searchInput.style.setProperty('color', config.inputIconTextColor, 'important'); } } this._injectClickSearchScopedStyle(config); }, _injectClickSearchScopedStyle(config) { const isCustom = config.colorType === 'custom'; const styleId = 'smart-search-click-scoped-style'; let style = document.getElementById(styleId); if (!style) { style = document.createElement('style'); style.id = styleId; document.head.appendChild(style); } let css = ''; if (isCustom) { css = ` .hot-search { background: linear-gradient(180deg, ${config.hotSearchBgStartColor} 0%, ${config.hotSearchBgEndColor} 100%) !important; } .smart-search-input::placeholder { color: ${config.inputIconTextColor} !important; opacity: 0.6; } .hot-words-carousel-word { color: ${config.inputIconTextColor} !important; opacity: 0.6; } `; } style.textContent = css; }, applySearchStyleConfig(retryCount = 0) { const config = this.searchStyleConfig; const searchWrap = this.getBlockWrap(); if (!searchWrap) return; // 设置锁标记,防止 resize 期间重复调用 this._isApplyingStyle = true; // 重构后:静态内容在 ljs-render 外部,DOM 元素始终存在 const iconSearchLarge = searchWrap.querySelector('.app-smart-icon-search-large'); const isSearchBox = config.styleType === 'searchBox'; // 检查轮播是否已渲染,如果没有则短暂等待后重试 const placeholder = searchWrap.querySelector('.smart-search-outside-input-placeholder'); const carousel = searchWrap.querySelector('.app-smart-search-outside-carousel'); // 轮播组件可能需要额外时间渲染,等待后重试 if (placeholder && !carousel && retryCount < 10) { this._isApplyingStyle = false; setTimeout(() => this.applySearchStyleConfig(retryCount + 1), 50); return; } // 轮播已渲染,隐藏默认占位文本 if (placeholder && carousel) { placeholder.classList.add('has-carousel'); } this._applySearchInlineStyles(searchWrap, config); const searchBtn = searchWrap.querySelector('.app-smart-search-btn'); const outsideInputContainer = searchWrap.querySelector('.smart-search-outside-input-container'); const buttonType = config.searchButtonType || config.buttonType; if (searchBtn) { const marginConfig = this._getMarginConfig(config); searchBtn.style.marginLeft = `${marginConfig.left}px`; searchBtn.style.marginRight = `${marginConfig.right}px`; // 使用 data 属性控制样式类型显示(searchBox 或 imageText) searchBtn.dataset.styleType = isSearchBox ? 'searchBox' : 'imageText'; } // 搜索按钮显示/隐藏控制 const outsideButtons = searchWrap.querySelectorAll('.smart-search-outside-input-button'); outsideButtons.forEach((btn) => { btn.style.display = config.showSearchButton ? '' : 'none'; this._applyButtonContentDisplay(btn, config, { customIconSelector: '.smart-search-button-custom-icon', systemIconSelector: '.smart-search-button-system-icon', textSelector: '.smart-search-button-text', }); }); // 图文样式配置 - 使用 data 属性控制显示 if (iconSearchLarge) { const customIcon = iconSearchLarge.querySelector('.smart-search-custom-icon'); const iconText = iconSearchLarge.querySelector('.smart-search-icon-text'); const mobile = !isDesktop(); // 设置 data 属性,让 CSS 控制显示 iconSearchLarge.dataset.iconEnabled = config.iconEnabled ? 'true' : 'false'; iconSearchLarge.dataset.iconType = config.iconType || 'system'; if (mobile) { // 移动端:只显示图标,不显示文本 iconSearchLarge.dataset.textEnabled = 'false'; } else { iconSearchLarge.dataset.textEnabled = config.textEnabled ? 'true' : 'false'; } // 设置自定义图标 src(如果有) if (config.iconType === 'custom' && config.customIcon && customIcon) { customIcon.src = config.customIcon; } // 设置文本内容 if (iconText && config.textEnabled) { iconText.textContent = config.searchText || 'Search'; } } if (searchBtn) { searchBtn.classList.add('style-ready'); } // 解除锁标记 this._isApplyingStyle = false; }, _applyButtonContentDisplay(btn, config, selectors) { const { customIconSelector, systemIconSelector, textSelector } = selectors; const customIcon = btn.querySelector(customIconSelector); const systemIcon = btn.querySelector(systemIconSelector); const textSpan = btn.querySelector(textSelector); const buttonType = config.searchButtonType || config.buttonType; // 使用 data 属性驱动 CSS 显示,确保降级和一致性 btn.dataset.buttonType = buttonType || 'text'; if (buttonType === 'icon') { // 设置图标类型:custom 或 system(默认) const iconType = (config.iconType === 'custom' && config.customIcon) ? 'custom' : 'system'; btn.dataset.iconType = iconType; // 如果是自定义图标,设置 src if (iconType === 'custom' && customIcon) { customIcon.src = config.customIcon; } } else { // text 类型:移除 iconType 属性,更新文本内容 delete btn.dataset.iconType; if (textSpan) { textSpan.textContent = config.searchText || 'Search'; } } }, applyClickSearchStyleConfig(retryCount = 0) { const config = this.clickSearchStyleConfig; const sidebar = this._getSidebarOverlay ? this._getSidebarOverlay() : document.querySelector('.smart-search-sidebar-overlay'); if (!sidebar) return; const findElement = (selector) => sidebar.querySelector(selector) || document.querySelector(selector); const submitBtn = findElement('.smart-search-submit-btn'); if (!submitBtn && retryCount < 10) { setTimeout(() => this.applyClickSearchStyleConfig(retryCount + 1), 100); return; } if (this._sidebarObserver) this._sidebarObserver.disconnect(); this._applyClickSearchInlineStyles(sidebar, config); if (submitBtn) { this._applyButtonContentDisplay(submitBtn, config, { customIconSelector: '.smart-search-sidebar-button-custom-icon', systemIconSelector: '.smart-search-sidebar-button-system-icon', textSelector: '.smart-search-sidebar-button-text', }); } if (this._sidebarObserver) { this._sidebarObserver.observe(sidebar, { childList: true, subtree: true }); } }, }; // --- 侧边栏管理 Mixin(预加载模式)--- const SidebarManagerMixin = { // 侧边栏状态: idle -> preloading -> ready -> open -> idle _sidebarState: 'idle', _sidebarReady: false, _contentPreloaded: false, _contentPreloadPromise: null, _getSidebarOverlay() { const wrap = this.getBlockWrap(); if (wrap) { const overlay = wrap.querySelector('.smart-search-sidebar-overlay'); if (overlay) return overlay; } return document.querySelector('.smart-search-sidebar-overlay'); }, _getSidebarPanel() { const wrap = this.getBlockWrap(); if (wrap) { const panel = wrap.querySelector('.smart-search-sidebar'); if (panel) return panel; } return document.querySelector('.smart-search-sidebar'); }, _findVisibleSearchEntry() { const wrap = this.getBlockWrap(); if (wrap) { const content = wrap.querySelector('[id^="app-smart-product-search-content-"]'); if (content) return content; } return document.querySelector('[id^="app-smart-product-search-content-"]'); }, _alignSidebarToOutsideInput() { if (!isDesktop()) return; const panel = this._getSidebarPanel(); if (!panel) return; const refEl = this._findVisibleSearchEntry(); let topPos = 80; let rightPos = 20; if (refEl) { const rect = refEl.getBoundingClientRect(); const viewportWidth = document.documentElement.clientWidth; topPos = Math.max(0, rect.top); rightPos = Math.max(0, viewportWidth - rect.right); } panel.style.setProperty('top', topPos + 'px', 'important'); panel.style.setProperty('right', rightPos + 'px', 'important'); }, preloadSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const currentState = overlay.getAttribute('data-state'); if (this._sidebarReady || this._sidebarState === 'preloading' || this._sidebarState === 'open') return; if (currentState === 'open' || currentState === 'preloading') return; this._sidebarState = 'preloading'; overlay.setAttribute('data-state', 'preloading'); const configReady = new Promise((resolve) => { if (this._configLoaded) { resolve(); return; } this.getOutsideItemEl().then((outsideItem) => { if (outsideItem) { const apiData = outsideItem.getData() || {}; const { clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; } resolve(); }).catch(() => resolve()); }); // 先渲染内容(创建 DOM),再应用样式(依赖 DOM 存在) configReady.then(() => { const currentState = overlay.getAttribute('data-state'); if (currentState === 'open') return; this._contentPreloaded = false; this._contentPreloadPromise = this.initSidebarContent().then(() => { this.applyClickSearchStyleConfig(); this._contentPreloaded = true; }).catch(() => { this._contentPreloaded = false; this._contentPreloadPromise = null; }); this._sidebarReady = true; this._sidebarState = 'ready'; overlay.setAttribute('data-state', 'ready'); }); }, // 打开侧边栏 openSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; this._sidebarOpenWidth = window.innerWidth; this._sidebarOpenedAsDesktop = isDesktop(); this._savedScrollbarWidth = parseFloat(getComputedStyle(document.documentElement).marginRight) || 0; const configReady = new Promise((resolve) => { if (this._configLoaded) { resolve(); return; } this.getOutsideItemEl().then((outsideItem) => { if (outsideItem) { const apiData = outsideItem.getData() || {}; const { clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; } resolve(); }).catch(() => resolve()); }); configReady.then(() => { this.applyClickSearchStyleConfig(); this._sidebarReady = true; this._showSidebar(overlay); }); document.body.style.overflow = 'hidden'; if (!this._resizeHandler) { this._resizeHandler = () => this._alignSidebarToOutsideInput(); window.addEventListener('resize', this._resizeHandler); } this._setupSidebarObserver(overlay); }, _showSidebar(overlay) { const panel = this._getSidebarPanel(); if (!panel) return; this._sidebarState = 'open'; overlay.setAttribute('data-state', 'open'); const _isDesktop = isDesktop(); const ANIM_DURATION = 280; const _setupPanelLayout = () => { const smartSearchWrap = panel.querySelector('.smart-search-wrap'); if (smartSearchWrap) { smartSearchWrap.style.cssText = _isDesktop ? 'display: block !important; width: 100% !important;' : 'display: flex !important; flex-direction: column !important; width: 100% !important; flex: 1 !important; min-height: 0 !important;'; } const pageContent = panel.querySelector('.page-content'); if (pageContent) { pageContent.style.cssText = _isDesktop ? 'display: flex !important; flex-direction: column !important; width: 100% !important;' : 'display: flex !important; flex-direction: column !important; width: 100% !important; flex: 1 !important; min-height: 0 !important;'; } }; const _applyPanelPosition = () => { if (_isDesktop) { const refEl = this._findVisibleSearchEntry(); let topPos = 80; let rightPos = 20; if (refEl) { const rect = refEl.getBoundingClientRect(); const viewportWidth = document.documentElement.clientWidth; topPos = Math.max(0, rect.top); rightPos = Math.max(0, viewportWidth - rect.right); } panel.style.cssText = ` display: block !important; position: fixed !important; top: ${topPos}px !important; right: ${rightPos}px !important; left: auto !important; bottom: auto !important; width: 520px !important; max-width: 520px !important; background: #fff !important; visibility: visible !important; opacity: 1 !important; z-index: 10000 !important; padding: 16px !important; border-radius: 6px !important; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15) !important; pointer-events: auto !important; `; } else { panel.style.cssText = ` display: flex !important; flex-direction: column !important; position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; max-width: 100vw !important; background: #fff !important; visibility: visible !important; opacity: 1 !important; z-index: 10000 !important; padding: 16px !important; border-radius: 0 !important; box-shadow: none !important; pointer-events: auto !important; transform: translateX(100%) !important; `; } }; const revealSidebar = () => { _applyPanelPosition(); _setupPanelLayout(); this.applyClickSearchStyleConfig(); overlay.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; z-index: 9999 !important; visibility: visible !important; pointer-events: auto !important; display: block !important; `; this.checkHistoryOverflow(); if (!_isDesktop) { const contentWrap = panel.querySelector('.smart-search-wrap'); console.log('contentWrap'); if (contentWrap) { contentWrap.style.setProperty('opacity', '0', 'important'); } requestAnimationFrame(() => { panel.style.setProperty('transition', `transform ${ANIM_DURATION}ms cubic-bezier(0.16, 1, 0.3, 1)`, 'important'); panel.style.setProperty('transform', 'translateX(0)', 'important'); }); const fontsReady = (document.fonts && document.fonts.ready) ? document.fonts.ready : Promise.resolve(); const stableDelay = new Promise(r => setTimeout(r, ANIM_DURATION)); Promise.all([fontsReady, stableDelay]).then(() => { requestAnimationFrame(() => { if (contentWrap) { contentWrap.style.setProperty('opacity', '1', 'important'); } }); }); } const input = overlay.querySelector('.smart-search-input'); if (input) { setTimeout(() => input.focus(), _isDesktop ? 50 : ANIM_DURATION); } this._syncInsideCarousel(overlay); }; if (this._contentPreloaded) { revealSidebar(); return; } overlay.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; z-index: 9999 !important; visibility: hidden !important; pointer-events: none !important; display: block !important; opacity: 0 !important; `; _applyPanelPosition(); _setupPanelLayout(); const contentReady = this._contentPreloadPromise || this.initSidebarContent(); contentReady.then(() => { requestAnimationFrame(() => revealSidebar()); }).catch(() => { revealSidebar(); }); }, _setupSidebarObserver(overlay) { if (this._sidebarObserver) { this._sidebarObserver.disconnect(); } this._sidebarObserver = new MutationObserver(() => { if (this._applyStyleDebounceTimer) { clearTimeout(this._applyStyleDebounceTimer); } this._applyStyleDebounceTimer = setTimeout(() => { const submitBtn = overlay.querySelector('.smart-search-submit-btn'); if (submitBtn) { this.applyClickSearchStyleConfig(); } this.checkHistoryOverflow(); }, 50); }); this._sidebarObserver.observe(overlay, { childList: true, subtree: true }); }, _syncOutsideCarousel() { if (this.insideCarouselIndex === this.outsideCarouselIndex) return; this.outsideCarouselIndex = this.insideCarouselIndex; const outsideEl = document.querySelector('ljs-carousel[id^="app-smart-search-outside-carousel-"]'); if (!outsideEl) return; SPZ.whenApiDefined(outsideEl).then((api) => { try { api.goToSlide(String(this.outsideCarouselIndex)); } catch(e) {} }); }, closeSidebar() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const overlayState = overlay.getAttribute('data-state'); if (this._sidebarState === 'closing') return; if (this._sidebarState === 'idle' && overlayState !== 'open') return; this._syncOutsideCarousel(); this._sidebarState = 'closing'; const panel = this._getSidebarPanel(); const _wasDesktop = this._sidebarOpenedAsDesktop !== undefined ? this._sidebarOpenedAsDesktop : isDesktop(); const ANIM_DURATION = 250; const _cleanup = () => { this._sidebarState = 'idle'; this._sidebarReady = false; this._contentPreloaded = false; this._contentPreloadPromise = null; this._sidebarOpenedAsDesktop = undefined; overlay.setAttribute('data-state', 'idle'); overlay.style.cssText = ''; if (panel) { panel.style.cssText = ''; const smartSearchWrap = panel.querySelector('.smart-search-wrap'); if (smartSearchWrap) smartSearchWrap.style.cssText = ''; const pageContent = panel.querySelector('.page-content'); if (pageContent) pageContent.style.cssText = ''; const input = panel.querySelector('.smart-search-input'); if (input) { input.value = ''; input.removeAttribute('has-value'); } panel.removeAttribute('has-value'); panel.removeAttribute('data-empty'); panel.removeAttribute('loading'); const loadingEl = panel.querySelector('.smart-search-loading'); if (loadingEl) { loadingEl.removeAttribute('show'); loadingEl.setAttribute('hide', ''); } document.querySelectorAll('.hot-words-carousel-inner-container').forEach(el => { el.style.display = 'block'; }); } document.body.style.overflow = ''; const scrollbarWidth = this._savedScrollbarWidth || 0; if (scrollbarWidth > 0) { const html = document.documentElement; html.style.setProperty('overflow', 'hidden', 'important'); html.style.setProperty('margin-right', scrollbarWidth + 'px', 'important'); setTimeout(() => { html.style.removeProperty('overflow'); html.style.removeProperty('margin-right'); }, 50); } this._savedScrollbarWidth = 0; if (this._sidebarObserver) { this._sidebarObserver.disconnect(); this._sidebarObserver = null; } if (this._applyStyleDebounceTimer) { clearTimeout(this._applyStyleDebounceTimer); this._applyStyleDebounceTimer = null; } if (this._resizeHandler) { window.removeEventListener('resize', this._resizeHandler); this._resizeHandler = null; } this._historyExpanded = false; }; if (panel && !_wasDesktop) { panel.style.setProperty('transition', `transform ${ANIM_DURATION}ms cubic-bezier(0.5, 0, 0.7, 0.4)`, 'important'); panel.style.setProperty('transform', 'translateX(100%)', 'important'); setTimeout(_cleanup, ANIM_DURATION); } else { _cleanup(); } const sectionPrefix = 'shoplaza-section'; const announcement = document.getElementById(sectionPrefix + '-announcement'); const header = document.getElementById(sectionPrefix + '-header'); if (announcement) announcement.classList.remove('header_mask_open'); if (header) header.classList.remove('header_mask_open'); }, _restartCarouselAutoplay(carouselEl) { carouselEl.removeAttribute('autoplay'); carouselEl.setAttribute('pause', ''); setTimeout(() => { carouselEl.setAttribute('autoplay', ''); carouselEl.removeAttribute('pause'); }, 50); }, _syncInsideCarousel(overlay) { const targetIndex = this.outsideCarouselIndex || 0; const doSync = () => { const carouselEl = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (!carouselEl) return; SPZ.whenApiDefined(carouselEl).then((carouselApi) => { try { carouselApi.goToSlide(String(targetIndex)); } catch(e) {} this._restartCarouselAutoplay(carouselEl); }); }; const renderEl = overlay.querySelector('ljs-render[id^="hot-words-carousel-"]'); if (renderEl) { const existingCarousel = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (existingCarousel && existingCarousel.hasAttribute('dom-mounted')) { doSync(); } else { const observer = new MutationObserver(() => { const el = overlay.querySelector('ljs-carousel[id^="inside-hot-words-carousel-"]'); if (el && el.hasAttribute('dom-mounted')) { observer.disconnect(); doSync(); } }); observer.observe(renderEl, { childList: true, subtree: true, attributes: true }); setTimeout(() => { observer.disconnect(); doSync(); }, 3000); } } else { doSync(); } }, // 兼容旧的 onSidebarOpen/Close 方法 onSidebarOpen() { this.openSidebar(); }, onSidebarClose() { this.closeSidebar(); }, }; // --- 搜索历史折叠/展开 Mixin --- const HISTORY_COLLAPSED_ROWS = 6; const HISTORY_EXPAND_THRESHOLD = 10; const HistoryOverflowMixin = { _findHistoryList(root) { const sources = [root, document]; for (const src of sources) { const list = src.querySelector('.recently-history-list'); if (list) return list; const els = src.querySelectorAll('*'); for (const el of els) { if (el.shadowRoot) { const l = el.shadowRoot.querySelector('.recently-history-list'); if (l) return l; } } } return null; }, _getRowHeight(list) { const item = list.querySelector('.recently-history-item'); if (!item || item.getBoundingClientRect().height === 0) return 0; const rowGap = parseFloat(getComputedStyle(list).rowGap) || 0; return item.getBoundingClientRect().height + rowGap; }, _findToggleBtn(list) { const parent = list.closest('.recently-history-content'); return parent ? parent.querySelector('.history-toggle-btn') : null; }, _applyMaxHeight(list) { const item = list.querySelector('.recently-history-item'); if (!item || item.getBoundingClientRect().height === 0) return; const itemHeight = item.getBoundingClientRect().height; const rowGap = parseFloat(getComputedStyle(list).rowGap) || 0; const paddingTop = parseFloat(getComputedStyle(list).paddingTop) || 0; var maxHeight = Math.ceil(itemHeight * HISTORY_COLLAPSED_ROWS + rowGap * (HISTORY_COLLAPSED_ROWS - 1)) + paddingTop; if (isDesktop()) maxHeight--; list.style.setProperty('max-height', maxHeight + 'px'); }, checkHistoryOverflow() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const list = this._findHistoryList(overlay); if (!list) return; const items = list.querySelectorAll('.recently-history-item'); if (items.length === 0) return; this._applyMaxHeight(list); const needsCollapse = items.length > HISTORY_EXPAND_THRESHOLD; if (needsCollapse && !this._historyExpanded) { list.classList.add('history-collapsed'); } else { list.classList.remove('history-collapsed'); } const toggleBtn = this._findToggleBtn(list); if (toggleBtn) { if (needsCollapse && !this._historyExpanded) { toggleBtn.classList.remove('hidden'); } else { toggleBtn.classList.add('hidden'); } } }, expandHistory() { const overlay = this._getSidebarOverlay(); if (!overlay) return; const list = this._findHistoryList(overlay); if (!list) return; this._historyExpanded = true; list.classList.remove('history-collapsed'); this._applyMaxHeight(list); const toggleBtn = this._findToggleBtn(list); if (toggleBtn) toggleBtn.classList.add('hidden'); }, }; const HotKeywordsMixin = { generateHotKeywordList(data) { const searchKeywords = data?.hotKeywordList || []; const isShowHotKeyword = data?.isShowHotKeyword || false; this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const hotwords = outsideItem.getData()?.search_keywords || []; const enrichedKeywords = this.enrichKeywords(searchKeywords, hotwords); this.renderHotKeywords(enrichedKeywords, isShowHotKeyword); }); }, enrichKeywords(keywords, hotwords) { return keywords.map((item) => { item.url_obj = item.url_obj || {}; const hotwordItem = hotwords.find(h => h.word === item.word); if (hotwordItem) { item.icon = hotwordItem.icon || ''; } if (!item.urlObj || !item.urlObj.url) { item.urlObj = { ...item.url_obj, url: item.url_obj.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj.url, }; } return item; }); }, renderHotKeywords(keywords, isShowHotKeyword) { document.querySelectorAll('.app-hot-keyword-render-child').forEach((el) => { SPZ.whenApiDefined(el).then((hotWordsChild) => { hotWordsChild.render({ list: keywords, isShowHotKeyword }); }); }); }, normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable) { if (findKeywordEnable === false) return []; if (findKeywords && findKeywords.length > 0) { return findKeywords.map(keyword => ({ word: keyword, icon: '', pic: '', type: 'find_keyword', url_obj: { type: 'search', url: `${SEARCH_URL}?q=${keyword}`, }, })); } return searchKeywords || []; }, normalizeKeywordUrl(item) { if (!item) return null; if (item.url_obj) { item.url_obj.url = item.url_obj.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj.url; } return item; }, onTapHotWord(type) { const index = type === 'inside' ? this.insideCarouselIndex : this.outsideCarouselIndex; this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData(); const findKeywords = apiData?.find_keywords || []; const searchKeywords = apiData?.search_keywords || []; const findKeywordEnable = apiData?.find_keyword_enable !== false; const keywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable); const currentItem = this.normalizeKeywordUrl(keywords[index] || null); if (currentItem) { this.handleHotKeyword({ args: { word: currentItem.word, query_type: currentItem.type, url: currentItem.url_obj?.url, } }); } else { this.executeSearch([''], 1); } }); }, getOutsideCarouselConfig() { return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return { outsideCarouselIndex: this.outsideCarouselIndex }; const apiData = outsideItem.getData(); const findKeywords = apiData?.find_keywords || []; const searchKeywords = apiData?.search_keywords || []; const findKeywordEnable = apiData?.find_keyword_enable !== false; const carouselKeywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords, findKeywordEnable); return { ...apiData, search_keywords: carouselKeywords, outsideCarouselIndex: this.outsideCarouselIndex, }; }); }, }; const HISTORY_CACHE_KEY = 'smart_search_history'; const HISTORY_MAX_LEN = 30; const HOT_SEARCH_LEN = 6; const SMART_SEARCH_THINK_URL = '/api/search/suggestion'; const SearchControllerMixin = { // 搜索历史缓存 _historyCache: null, _searchData: null, _hotList: [], _curFindKeyword: '', // 初始化搜索历史 initHistoryCache() { if (this._historyCache) return; try { const cached = localStorage.getItem(HISTORY_CACHE_KEY); this._historyCache = cached ? JSON.parse(cached) : []; } catch (e) { this._historyCache = []; } }, // 获取历史列表 getHistoryList() { try { const cached = localStorage.getItem(HISTORY_CACHE_KEY); const historyList = cached ? JSON.parse(cached) : []; return historyList.slice().reverse(); } catch (e) { return []; } }, // 添加历史记录 addHistory(keyword) { if (!keyword || !keyword.trim()) return; this.initHistoryCache(); const index = this._historyCache.indexOf(keyword); if (index > -1) { this._historyCache.splice(index, 1); } this._historyCache.push(keyword); if (this._historyCache.length > HISTORY_MAX_LEN) { this._historyCache.shift(); } try { localStorage.setItem(HISTORY_CACHE_KEY, JSON.stringify(this._historyCache)); } catch (e) {} }, // 清除历史 clearHistory() { this._historyCache = []; try { localStorage.removeItem(HISTORY_CACHE_KEY); } catch (e) {} }, // 渲染表单区域 renderSearchForm() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData() || {}; const formData = { isOpenAutoThink: apiData.auto_think_enable || false, isOpenFindKeyword: apiData.find_keyword_enable || false, findKeywordList: apiData.find_keywords || [], }; const formRender = panel.querySelector('[role="form"]'); if (formRender) { return SPZ.whenApiDefined(formRender).then((api) => { api.render(formData); }); } }); }, // 渲染历史区域 renderSearchHistory() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; const historyList = this.getHistoryList(); const historyData = { isShowHistory: (apiData.user_history_enable !== false) && historyList.length > 0, historyList: historyList, }; const historyRender = panel.querySelector('[role="history"]'); if (historyRender) { return SPZ.whenApiDefined(historyRender).then((api) => { api.render(historyData); }); } }); }, // 渲染联想结果 renderThinkResult(thinkResult) { const panel = this._getSidebarPanel(); if (!panel) return; const thinkRender = panel.querySelector('[role="thinkresult"]'); if (thinkRender) { SPZ.whenApiDefined(thinkRender).then((api) => { api.render({ thinkResult }); }); } }, // 获取联想结果 fetchThinkResult(keyword) { if (!keyword || !keyword.trim()) { this.setThinkResultStatus(false, false); return Promise.resolve([]); } this.setThinkResultStatus(true, false); this.setLoadingStatus(true); return fetch(`${SMART_SEARCH_THINK_URL}?surface=autocomplete&keyword=${encodeURIComponent(keyword)}`) .then(res => res.json()) .then(res => { this.setLoadingStatus(false); const items = res.items || []; this.setThinkResultStatus(true, items.length === 0); const lowerKeyword = keyword.toLowerCase(); const thinkResult = items.map(item => ({ ...item, highlightHtml: item.word.replace( new RegExp(lowerKeyword, 'gi'), `${lowerKeyword}` ), })); this.renderThinkResult(thinkResult); return thinkResult; }) .catch(() => { this.setLoadingStatus(false); this.setThinkResultStatus(true, true); return []; }); }, // 设置联想结果状态 setThinkResultStatus(hasValue, isEmpty) { const panel = this._getSidebarPanel(); if (!panel) return; if (hasValue) { panel.setAttribute('has-value', ''); } else { panel.removeAttribute('has-value'); } if (isEmpty) { panel.setAttribute('data-empty', ''); } else { panel.removeAttribute('data-empty'); } }, // 设置 loading 状态 setLoadingStatus(loading) { const panel = this._getSidebarPanel(); if (!panel) return; if (loading) { panel.setAttribute('loading', ''); } else { panel.removeAttribute('loading'); } const loadingEl = panel.querySelector('.smart-search-loading'); if (loadingEl) { if (loading) { loadingEl.removeAttribute('hide'); loadingEl.setAttribute('show', ''); } else { loadingEl.removeAttribute('show'); loadingEl.setAttribute('hide', ''); } } }, // 处理表单输入 handleFormInput(invocation) { const keyword = invocation?.args?.keyword ?? invocation?.keyword ?? ''; this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; if (!apiData.auto_think_enable) return; this.fetchThinkResult(keyword); }); }, // 处理搜索提交 handleSearchSubmit(value) { const searchStr = Array.isArray(value) ? value[0] : value; if (!searchStr || !searchStr.trim()) { window.location.href = SEARCH_URL; return; } this.addHistory(searchStr); this.trackSearch(searchStr, 'user_input'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(searchStr)}`; }, // 处理历史点击 handleHistory(invocation) { const value = invocation?.args?.value ?? ''; if (!value) return; this.addHistory(value); this.trackSearch(value, 'user_history'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(value)}`; }, // 处理热词点击 handleHotKeyword(invocation) { const word = invocation?.args?.word ?? ''; const queryType = invocation?.args?.query_type ?? 'user_keyword'; const url = invocation?.args?.url ?? ''; if (!word) return; this.addHistory(word); this.trackSearch(word, queryType); if (url && !url.includes(SEARCH_URL)) { window.location.href = url; } else { window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(word)}`; } }, // 处理联想结果点击 handleThinkResult(invocation) { const word = invocation?.args?.word ?? ''; if (!word) return; this.addHistory(word); this.trackSearch(word, 'auto_think'); window.location.href = `${SEARCH_URL}?q=${encodeURIComponent(word)}`; }, // 处理清除历史 handleClearHistory() { this.clearHistory(); this.renderSearchHistory(); }, // 处理刷新热词 handleRefreshHot() { this.getOutsideItemEl().then((outsideItem) => { const apiData = outsideItem?.getData() || {}; const searchKeywords = apiData.search_keywords || []; if (searchKeywords.length <= HOT_SEARCH_LEN) return; // 直接调用渲染方法(会使用 _hotList 的分页逻辑) this.renderHotKeywordDirect(); }); }, // 埋点 trackSearch(query, queryType) { const trackQueryType = { 'user_input': 1, 'user_history': 2, 'user_keyword': 3, 'smart_keyword': 4, 'auto_think': 5, 'find_keyword': 6, }; if (window.sa) { window.sa.track('search_request', { event_info: JSON.stringify({ query, query_type: queryType }), function_name: 'smart_search', }); window.sa.registerAID(`smart_search.${trackQueryType[queryType] || 1}.${query}`); } }, // 初始化侧边栏内容渲染(返回 Promise,所有内容渲染完成后 resolve) initSidebarContent() { return Promise.all([ this.renderSearchForm(), this.renderSearchHistory(), this.renderHotKeywordDirect(), ]); }, // 直接渲染热搜词 renderHotKeywordDirect() { const panel = this._getSidebarPanel(); if (!panel) return Promise.resolve(); return this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) return; const apiData = outsideItem.getData() || {}; const searchKeywords = apiData.search_keywords || []; const hotKeywordList = this.getHotKeywordList(searchKeywords); const hotKeywordData = { isShowHotKeyword: searchKeywords.length > 0, list: hotKeywordList, }; const hotKeywordRender = panel.querySelector('[role="hotkeyword"].app-hot-keyword-render-child'); if (hotKeywordRender) { return SPZ.whenApiDefined(hotKeywordRender).then((api) => { api.render(hotKeywordData); }); } }); }, // 获取热搜词列表(带分页逻辑) getHotKeywordList(searchKeywords) { const enrichedList = searchKeywords.map(item => ({ ...item, urlObj: { ...item.url_obj, url: item.url_obj?.type === 'search' ? `${SEARCH_URL}?q=${item.word}` : item.url_obj?.url, }, })); // 用于刷新功能的分页逻辑 if (!this._hotListIndex) { this._hotListIndex = 0; } const startIndex = this._hotListIndex; const endIndex = startIndex + HOT_SEARCH_LEN; const result = enrichedList.slice(startIndex, endIndex); // 更新索引,循环使用 this._hotListIndex = endIndex >= enrichedList.length ? 0 : endIndex; return result.length > 0 ? result : enrichedList.slice(0, HOT_SEARCH_LEN); }, }; // --- 主组件 --- class SpzCustomSmartSearchLocation extends SPZ.BaseElement { constructor(element) { super(element); this.outsideCarouselIndex = 0; this.insideCarouselIndex = 0; this.searchItemType = 'icon'; this._originalSearchWrapParent = null; this._skipMobileInit = false; this.searchStyleConfig = { ...DEFAULT_SEARCH_STYLE_CONFIG }; this.clickSearchStyleConfig = { ...DEFAULT_CLICK_SEARCH_STYLE_CONFIG }; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.bindResizeListener(); this.registerActions(); // 设置超时降级:如果 API 在 5 秒内没有返回,显示默认样式 this._styleFallbackTimer = setTimeout(() => { if (!this._configLoaded) { this._applyStyleFallback(); } }, 5000); } mountCallback(){ this.safeInit(); } // API 超时/报错时的降级处理 _applyStyleFallback() { const searchWrap = this.getBlockWrap(); if (!searchWrap) return; const searchBtn = searchWrap.querySelector('.app-smart-search-btn'); if (searchBtn && !searchBtn.classList.contains('style-ready')) { searchBtn.classList.add('style-fallback'); } } // 清除降级定时器 _clearFallbackTimer() { if (this._styleFallbackTimer) { clearTimeout(this._styleFallbackTimer); this._styleFallbackTimer = null; } } unmountCallback(){ this.unbindResizeListener(); this.unregisterActions(); } // --- 初始化 --- safeInit() { this.relocatePlugin(); this.applySearchIconClass(); this.adjustLifestyleIcon(); if (!isDesktop() && !this._skipMobileInit && !this._mobileInitDone) { this.initMobileSmartSearch(); this._mobileInitDone = true; } } init() { this.safeInit(); if (this.searchItemType === 'input') { this.initInputMode(); return; } this.initIconMode(); } // --- Action 注册 --- registerActions() { this.registerAction('onSearchInputChange', (invocation) => { this.onSearchInputChange(invocation.args.keyword); }); this.registerAction('onSearchFormSubmit', (invocation) => { this.onSearchFormSubmit(invocation.args.event); }); this.registerAction('onOutsideCarouselIndexChange', (invocation) => { this.outsideCarouselIndex = invocation.args.index || 0; }); this.registerAction('onInsideCarouselIndexChange', (invocation) => { this.insideCarouselIndex = invocation.args.index || 0; }); this.registerAction('getSearchItemType', () => { this.fetchAndApplySearchItemType(); }); this.registerAction('generateHotKeywordList', (invocation) => { this.generateHotKeywordList(invocation.args?.data?.data); }); this.registerAction('onTapHotWord', (invocation) => { this.onTapHotWord(invocation.args.type); }); this.registerAction('onSidebarOpen', () => { this.onSidebarOpen(); }); this.registerAction('onSidebarClose', () => { this.onSidebarClose(); }); this.registerAction('openSidebar', () => { this.openSidebar(); }); this.registerAction('closeSidebar', () => { this.closeSidebar(); }); this.registerAction('expandHistory', () => { this.expandHistory(); }); // 搜索控制器 actions this.registerAction('handleFormInput', (invocation) => { this.handleFormInput(invocation); }); this.registerAction('handleHistory', (invocation) => { this.handleHistory(invocation); }); this.registerAction('handleHotKeyword', (invocation) => { this.handleHotKeyword(invocation); }); this.registerAction('handleThinkResult', (invocation) => { this.handleThinkResult(invocation); }); this.registerAction('handleClearHistory', () => { this.handleClearHistory(); }); this.registerAction('handleRefreshHot', () => { this.handleRefreshHot(); }); } // --- 搜索输入 & 提交 --- onSearchInputChange(keyword) { const hasValue = keyword && keyword.length > 0; const display = hasValue ? 'none' : 'block'; // 控制热词轮播显示 document.querySelectorAll('.hot-words-carousel-inner-container').forEach(el => { el.style.display = display; }); // 设置 input 元素的 has-value 属性(控制清除按钮和热词轮播的 CSS 样式) const panel = this._getSidebarPanel(); if (panel) { const input = panel.querySelector('.smart-search-input'); if (input) { if (hasValue) { input.setAttribute('has-value', ''); } else { input.removeAttribute('has-value'); } } } } onSearchFormSubmit(event) { const keywordArray = event.q || []; const keyword = keywordArray[0]; if (keyword !== null && keyword.length) { this.executeSearch(keywordArray, 1); } else { this.onTapHotWord('inside'); } } executeSearch(value, retryCount) { const searchStr = Array.isArray(value) ? value[0] : value; this.handleSearchSubmit(searchStr); } // --- 搜索项类型 --- fetchAndApplySearchItemType() { this.getOutsideItemEl().then((outsideItem) => { if (!outsideItem) { // API 获取失败,应用降级样式 this._applyStyleFallback(); return; } // 清除降级定时器 this._clearFallbackTimer(); const apiData = outsideItem.getData() || {}; const hasHeaderStyle = apiData.header_style && apiData.header_style !== '' && apiData.header_style !== '{}'; const { searchStyleConfig, clickSearchStyleConfig } = parseHeaderStyle(apiData.header_style); this.searchStyleConfig = searchStyleConfig; this.clickSearchStyleConfig = clickSearchStyleConfig; this._configLoaded = true; if (hasHeaderStyle) { this.searchItemType = searchStyleConfig.styleType === 'searchBox' ? 'input' : 'icon'; } else { const type = apiData.search_item_type; if (type) { this.searchItemType = type; } else { this.searchItemType = 'icon'; } this.searchStyleConfig.styleType = this.searchItemType === 'input' ? 'searchBox' : 'imageText'; } this.applySearchStyleConfig(); this.init(); // 接口数据加载完成后,预加载侧边栏 this.preloadSidebar(); }).catch(() => { // API 报错,应用降级样式 this._applyStyleFallback(); }); } // --- 窗口监听 --- bindResizeListener() { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = SPZCore.Types.debounce( this.win, () => { const viewportWidth = document.documentElement.clientWidth || window.innerWidth; // 防止在 ljs-render 渲染过程中触发重复操作 if (this._isApplyingStyle) return; const widthChanged = !this._sidebarOpenWidth || window.innerWidth !== this._sidebarOpenWidth; const sidebarVisible = this._sidebarState === 'open' || this._sidebarState === 'ready' || this._sidebarState === 'preloading'; const overlay = this._getSidebarOverlay && this._getSidebarOverlay(); const overlayOpen = overlay && overlay.getAttribute('data-state') === 'open'; if (sidebarVisible || overlayOpen) { if (!widthChanged) return; this.closeSidebar(); } this.fetchAndApplySearchItemType(); }, DELAY ); window.addEventListener('resize', window.smartSearchResizeCallback); } unbindResizeListener() { if (window.smartSearchResizeCallback) { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = null; } if (this._relocateTimer) { clearInterval(this._relocateTimer); this._relocateTimer = null; } } unregisterActions() { const actionNames = [ 'onSearchInputChange', 'onSearchFormSubmit', 'onOutsideCarouselIndexChange', 'onInsideCarouselIndexChange', 'getSearchItemType', 'generateHotKeywordList', 'onTapHotWord', 'expandHistory', ]; actionNames.forEach((name) => { this.registerAction(name, () => {}); }); } } Object.assign(SpzCustomSmartSearchLocation.prototype, ElementFinderMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, StyleApplicatorMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, SidebarManagerMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, HistoryOverflowMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, HotKeywordsMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, MobileLayoutMixin); Object.assign(SpzCustomSmartSearchLocation.prototype, SearchControllerMixin); SPZ.defineElement(TAG, SpzCustomSmartSearchLocation); class SpzCustomSmartSearchToast extends SPZ.BaseElement { constructor(element) { super(element); this.toastDom = null; this.toastTimeout = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback(){ this.init(); } init(){ const toast = document.createElement('div'); toast.id = 'spz-custom-smart-search-toast-974'; toast.className = 'spz-custom-smart-search-toast'; document.body.appendChild(toast); this.toastDom = toast; this.registerAction('showToast',(invocation)=>{ this.showToast(invocation.args); }); this.registerAction('hideToast',(invocation)=>{ this.hideToast(invocation.args); }); } showToast({ message, duration = 2000 }){ if( !this.toastDom ) return; this.toastDom.innerHTML = message; this.toastDom.classList.add('smart-search-toast-show'); clearTimeout(this.toastTimeout); this.toastTimeout = setTimeout(() => { this.hideToast(); }, duration); } hideToast(){ if( !this.toastDom ) return; this.toastDom.classList.remove('smart-search-toast-show'); } } SPZ.defineElement('spz-custom-smart-search-toast', SpzCustomSmartSearchToast); class SpzCustomSmartSearchCookie extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.registerAction('getCookie',(invocation)=>{ this.getCookie(invocation.args); }); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } getCookie(key) { let cookieMap = {} document.cookie.split(';').map(item=>{ let [key, value] = item.trim().split('=') cookieMap[key] = value }) return cookieMap[key] || ''; } } SPZ.defineElement('spz-custom-smart-search-cookie', SpzCustomSmartSearchCookie); const default_function_name = 'smart_search'; const default_plugin_name = 'smart_search'; const default_module_type = 'smart_search'; const default_module = 'apps'; const default_business_type = 'product_plugin'; const default_event_developer = 'ray'; class SpzCustomSmartSearchTrack extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.registerAction('track', (invocation) => { const { trackType, trackData } = invocation.args; this.track({trackType, trackData}); }); } track({trackType, trackData}) { const { function_name, plugin_name, module_type, module, business_type, event_developer, event_type, event_desc, trackEventInfo, ...otherTrackData } = trackData; window.sa.track(trackType, { function_name: function_name || default_function_name, plugin_name: plugin_name || default_plugin_name, module_type: module_type || default_module_type, module: module || default_module, business_type: business_type || default_business_type, event_developer: event_developer || default_event_developer, event_type: event_type, event_desc: event_desc, ...otherTrackData, event_info: JSON.stringify({ ...(trackEventInfo || {}), }), }); } } SPZ.defineElement('spz-custom-smart-search-track', SpzCustomSmartSearchTrack);
  • New In
    COLLECTION
    • TRENDY FASHION
    • CASUAL CHIC
    • LANTERN SLEEVE
    CATEGORY
    • NEW IN DRESSES
    • NEW IN TOPS
    • NEW IN BOTTOMS
    Niche Design Plaid High-Waisted Skirt
    $74.74
  • Hot Sale
    • DRESSES
    • Strap Series
    Pleated Bishop Sleeve Patchwork Turtleneck Loose Sweater
    $43.76
  • Dresses
    SHOP BY STYLE
    • COTTON
    • DENIM
    • EMBROIDERY
    • LINEN
    • KNIT
    SHOP BY LENGTH
    • Mini Dresses
    • Midi Dresses
    • Maxi Dresses
    SHOP BY PATTERN
    • FLORAL
    • SOLID COLOR
    • PLAID
    • PATCHWORK
    • POLKA DOT
    • STRIPED
    +1
    Casual Cotton Linen Strapless Dress
    $32.80
  • Tops
    • T-shirts
    • Blouses&Shirts
    • Sweatshirts
    • Vests
    • Outwears
    Vintage Leopard Printed Bat Wing Shirt
    $42.98
  • Bottoms
    • Skirts
    • Shorts
    • Pants
    • Jeans
    Leisure High Waisted Water-drill Jeans
    $124.93
  • Co-ords&Jumpsuits
    • Co-ords
    • Jumpsuits
    SALE
    Personalized Single Strappy Wide Leg Jumpsuit
    $54.61
    $59.98
    - 9%
    - 9%
  • ACCS&SHOES
    Jewelry
    • Earrings
    • Necklaces
    • Bracelets
    • Body Chain
    • Rings
    Accessories
    • Bags
    • Scarfs&Hats
    • Warmers
    • Decoration
    • Blankets
    Shoes
    • Thick Bottom Shoes
    • Boots
    • Household Slipper
    • Headwears
    Large Capacity PU Leather Tote Bag
    $75.82
  • More links
    function setSearchUrl(searchValue) { return Promise.resolve({ url: '/search?q=' + searchValue }); } exportFunction('setSearchUrl', setSearchUrl);

    New Arrivals

    View all
    • Niche Design Plaid High-Waisted Skirt
      $74.74
    • Elegant Pleated Elastic-Waist Skirt
      $81.60
    • Vacation Style Splicing High-Waisted Skirt
      $81.60
    • Retro Mesh Patchwork High-Waisted Skirt
      $65.14
    • Vintage Dyed Printed Wide-Leg Jumpsuit
      $74.74
    • +2
      Casual Cotton Linen Strapless Dress
      $32.80
    • Versatile Jacquard Split Short-Sleeve T-Shirt
      $51.43
    • Simple Solid Color Short-Sleeve T-Shirt
      $23.89
    • Casual Pleated Zipper Top
      $59.83
    • +3
      Minimalist Solid Color Batwing-Sleeve Top
      $27.31

    BEST SELLERS

    let section_id = '1708570229557'; window.reviewSettings = {}; window.reviewSettings[section_id] = { "sub_title": "Subscribe to get special offers, free giveaways, and once-in-a-lifetime deals.", "star_least": "5", "only_featured": false, "with_photo": true, "review_insufficient": null, "minimum_comment_num": 5, "fill_strategy": null, "layout": "grid", "image_size": "100", "wall_mobile_num": 2, "wall_pc_num": 4, "limit": 12, "show_product": true, "hide_review_section": true, "title": "Customer Reviews", "accent_color": null, "color_title": "#000000", "text_color": "#000000", "card_wrap_color": null, "background_color": "#ffffff" };
    const TAG = 'spz-custom-revue-util'; const DEFAULT_DELAY_TIME = 100; class SpzCustomRevueUtil extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = SPZServices.templatesForDoc(); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } static deferredMount() { return false; } mountCallback() { } debounceRender(el, thisEl, containerStr) { return this.smoothRender_(el, thisEl, containerStr).then(() => this.attemptToFit_(thisEl)); } smoothRender_(newEl, thisEl, containerStr) { const that = this; that.appendAsUnvisibleContainer_(newEl, thisEl); const components = newEl.querySelectorAll('[layout]'); return Promise.race([ Promise.all( Array.prototype.map.call(components, (e) => SPZ.whenDefined(e).then(() => e.whenBuilt()) ) ), SPZServices.timerFor(that.win).promise(DEFAULT_DELAY_TIME), ]).then(() => { return containerStr !== 'form_' ? thisEl.mutateElement(() => that.quickReplace(thisEl, newEl)) : thisEl.mutateElement(() => that.quickReplaceForm(thisEl, newEl)); }); } quickReplace(thisEl, newEl) { thisEl.container_ && this.toggleVisible_(thisEl.container_); this.toggleVisible_(newEl, true); thisEl.container_ && SPZCore.Dom.removeElement(thisEl.container_); thisEl.container_ = newEl; }; quickReplaceForm(thisEl, newEl) { thisEl.form_ && this.toggleVisible_(thisEl.form_); this.toggleVisible_(newEl, true); const children = thisEl.form_.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.toggleVisible_(thisEl.form_, true); thisEl.form_.appendChild(newEl); }; appendAsUnvisibleContainer_(el, thisEl) { this.toggleVisible_(el); thisEl.element.appendChild(el); } attemptToFit_(thisEl) { const fitFunc = () => { thisEl.mutateElement(this.setElementHeight_.bind(thisEl)); }; const container = thisEl.container_ || thisEl.form_; if (container) { const children = container.querySelectorAll('*:not(template)'); const spzChildren = Array.prototype.filter .call(children, SPZUtils.isSpzElement) .filter((e) => !(e.isMount && e.isMount())); spzChildren .map((e) => SPZ.whenDefined(e).then(() => e.whenMounted())) .forEach((p) => p.then(() => fitFunc())); } return fitFunc(); } setElementHeight_() { const targetHeight = (this.container_ || this.form_)?./*OK*/ scrollHeight; const height = this.element./*OK*/ offsetHeight; if (height !== targetHeight) { SPZCore.Dom.setStyles(this.element, { height: `${targetHeight}px`, }); } } toggleVisible_(el, visible = false) { if (!visible) { el.classList.add('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': -100000, 'opacity': 0, }); } else { el.classList.remove('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': 'auto', 'opacity': 1, }); } } setMinWidth_() { const targetWidth = this.container_?./*OK*/ scrollWidth; const width = this.element./*OK*/ offsetWidth; if (width !== targetWidth) { SPZCore.Dom.setStyles(this.element, { 'min-width': `${targetWidth}px`, }); } } triggerEvent_ = (name, data) => { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomRevueUtil); const TAG = 'spz-custom-revue-render'; class SPZCustomRevueRender extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } mountCallback = () => {} render = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { if (this.element.children.length > 0) { this.element.children[0].style.display = 'none'; } this.element.appendChild(el); // const utilsEl = document.getElementById('spz_custom_revue_util'); // utilsEl && SPZ.whenApiDefined(utilsEl).then((api) => { // api.debounceRender(el, this); // }); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueRender) const TAG = 'spz-custom-revue-star'; class SPZCustomRevueStar extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.starNum = this.element.getAttribute('starNum'); this.starTotal = this.element.getAttribute('starTotal'); this.showStarText = this.element.getAttribute('showStarText'); this.starColor = this.element.getAttribute('color'); this.interact = this.element.getAttribute('interact'); this.starSize = this.element.getAttribute('starSize') || 14; } mountCallback = () => { this.doRender_({ starTotal: this.starTotal, totalArray: Array.from({ length: Number(this.starTotal) }, (v, k) => k + 1), starNum: this.starNum, showStarText: this.showStarText, starColor: this.starColor, starSize: this.starSize }).then(() => { if (this.interact) { this.addEventListeners_(); } }); } addEventListeners_ = () => { const stars = document.querySelectorAll('.revue-star__star'); stars.forEach(star => { star.addEventListener('click', event => { const starEl = star.closest('.revue-star__star'); const starIndex = Number(starEl.dataset.index); let isHalf = event.offsetX < star.offsetWidth / 2; // rtl if (document.documentElement.getAttribute('dir') === 'rtl') { isHalf = event.offsetX > star.offsetWidth / 2; } const starValue = isHalf ? starIndex - 0.5 : starIndex; this.starClickHandler_({ value: starValue }); }); }); } renderStar = () => { const isRtl = document.documentElement.getAttribute('dir') === 'rtl'; const stars = this.element.querySelectorAll('.revue-star__star'); stars.forEach((star, i) => { const starIndex = i + 1; const starEl = star.querySelector('svg:nth-child(2)'); const isHalf = this.starNum % 1 > 0 && Math.ceil(this.starNum) === starIndex; const isSolid = starIndex <= Math.ceil(this.starNum); starEl.style.display = isSolid ? 'block' : 'none'; if (isHalf) { if (isRtl) { // RTL布局下,如果是半星,显示星星的右半边 starEl.style.clipPath = `polygon(50% 0, 100% 0, 100% 100%, 50% 100%)`; } else { // LTR布局下,如果是半星,显示星星的左半边 starEl.style.clipPath = `polygon(0 0, 50% 0, 50% 100%, 0 100%)`; } } else { starEl.style.clipPath = `polygon(0 0, 100% 0, 100% 100%, 0 100%)` } }); const showCountEle = this.element.querySelector('#revue-star-show-count'); showCountEle && SPZ.whenApiDefined(showCountEle).then((api) => { api.render({ starNum: this.starNum, starTotal: this.starTotal }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, { starSize: this.starSize, ...data }, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }) .then(() => { this.starNum = data.starNum; this.renderStar(); }); } starClickHandler_ = (event) => { this.starNum = event.value; this.renderStar(); this.triggerEvent_('change', { value: event.value }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueStar) const TAG = 'spz-custom-revue-like'; class SPZCustomRevueLike extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.grayColor = this.element.getAttribute('gray_color') || "#BDBDBD"; this.likedColor = this.element.getAttribute('like_color') || "#FFCB44"; this.color = this.grayColor; this.count = this.element.getAttribute('count'); this.revueId = this.element.getAttribute('revue-id'); this.location = this.element.getAttribute('location'); } mountCallback = () => { const likes = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : []; const like = likes.find(item => item.id === this.revueId); if (like) { this.color = like.like_status === 1 ? this.likedColor : this.grayColor; } // 如果location是modal,则找到相同revue-id的list的元素,拿到其count,存在list count变了,但是modal的count没变的情况 if (this.location === 'modal') { const listElement = document.querySelector(`spz-custom-revue-like[revue-id="${this.revueId}"] .revue-like-count`); if (listElement) { this.count = listElement.getAttribute('data-real-count'); } } this.doRender_({ color: this.color, count: this.count }).then(() => { this.addEventListeners_(); if(this.location === 'list') { // modal数量变更,list同步变更 document.addEventListener('like-clicked', (e) => { if (e.detail.location !== this.location && e.detail.id === this.revueId) { this.color = e.detail.like_status === 1 ? this.likedColor : this.grayColor; this.count = e.detail.count; this.element.querySelector('.revue-like__icon').querySelector('svg').setAttribute('fill', this.color); this.element.querySelector('.revue-like__icon').querySelector('svg').querySelector('path').setAttribute('fill', this.color); this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count; this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count); if(this.count > 0){ this.element.querySelector('.revue-like-count').classList.remove('hidden'); }else{ this.element.querySelector('.revue-like-count').classList.add('hidden'); } } }); } }); } addEventListeners_ = () => { const icon = this.element.querySelector('.revue-like__icon'); icon.addEventListener('click', (e) => { e.stopPropagation(); const likeStatus = this.color === this.likedColor ? 0 : 1; this.color = this.color === this.likedColor ? this.grayColor : this.likedColor; this.count = likeStatus === 1 ? parseInt(this.count) + 1 : parseInt(this.count) - 1; icon.querySelector('svg').setAttribute('fill', this.color); icon.querySelector('svg').querySelector('path').setAttribute('fill', this.color); this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count; this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count); if(this.count > 0){ this.element.querySelector('.revue-like-count').classList.remove('hidden'); }else{ this.element.querySelector('.revue-like-count').classList.add('hidden'); } this.postLike(likeStatus); if (this.location === 'modal') { const clickedEvent = new CustomEvent('like-clicked', { detail: { id: this.revueId, like_status: likeStatus, count: this.count, location: this.location } }); document.dispatchEvent(clickedEvent); } }); } setLikeToStorage = (likeToStore) => { if (typeof (Storage) !== 'function') return; const likesInStore = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : []; const reviewIndex = likesInStore.findIndex(item => item.id === likeToStore.id); if (reviewIndex !== -1) { likesInStore[reviewIndex].like_status = likeToStore.like_status; likesInStore[reviewIndex].count = likeToStore.count; } else { likesInStore.push(likeToStore); } sessionStorage.setItem('likes', JSON.stringify(likesInStore)); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } postLike = (likeStatus) => { fetch('/api/comment/like', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ id: this.revueId, status: likeStatus }) }).then((res) => { if (res.status === 200) { this.setLikeToStorage({ id: this.revueId, like_status: likeStatus, count: this.count }); } }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueLike) const TAG = 'spz-custom-revue-media'; class SPZCustomRevueMedia extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.imgCover = this.element.getAttribute('img-cover') ?? false; this.pc_layout = this.element.getAttribute('pc-layout') ?? ''; // data-images 格式为 xxxx.png?width=1&height=1,xxxx.png?width=1&height=1 const images = this.element.getAttribute('data-images').split(',') || []; const parsedImages = images.map(image => { return this.mediaParse_(image); }); this.images = parsedImages; this.isPC = window.innerWidth > 960; } mountCallback = () => { this.doRender_({ images: this.images, isPC: this.isPC, imgCover: this.imgCover, pc_layout: this.pc_layout }).then(() => { this.addEventListeners_(); }); } addEventListeners_ = () => { const images = this.element.querySelectorAll('.revue-image-item'); images.forEach((image, index) => { image.addEventListener('click', () => { const carousel = document.querySelector('#revue-image-carousel-render'); carousel && SPZ.whenApiDefined(carousel).then((api) => { const width = this.isPC ? 460 : window.innerWidth * 0.9; const height = this.isPC ? 630 : 500; api.render({ images: this.images, index: index, width: width, height: height }); }); }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.preview_image = url.split('?')[0]; } catch (e) {}; return result; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueMedia) const TAG = 'spz-custom-revue-sort'; class SPZCustomRevueSort extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > 960; this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%'; this.randomStr = Math.random().toString(36).substr(2); this.sectionId = this.element.getAttribute('section-id') || '1708570229557'; this.prefix = this.element.getAttribute('prefix'); } mountCallback = () => { const data = { width: this.width, randomStr: this.randomStr }; this.doRender_(data).then(() => { let revueSortListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-sort-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`); revueSortListRender && SPZ.whenApiDefined(revueSortListRender).then((api) => { api.render(data).then(() => { if (this.isPC) { this.addEventListenersForPC_(); } else { this.addEventListenersForMobile_(); } }); }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } addEventListenersForPC_ = () => { const revueSelectList = this.element.querySelector('.revue_select_list'); const revueSelectItem = this.element.querySelectorAll('.revue_select_item'); const revueSelectSortIcon = this.element.querySelector(`#${this.prefix}-revue_select_sort_icon-${this.sectionId}`); revueSelectItem.forEach(item => { item.addEventListener('click', () => { const sort = item.getAttribute('data-sort'); const direction = item.getAttribute('data-direction'); this.triggerEvent_('sort', { sort, direction }); this.element.querySelector('.revue_select_label').innerText = item.innerText; revueSelectList.classList.remove('revue_select_list_active'); const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const pcDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-pc-dropdown-${this.sectionId}`); if (!revueSelectSortIcon.classList.contains('up_icon')) { return; } revueSelectSortIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); }); window.addEventListener('scroll', (e) => { if (!revueSelectSortIcon || !revueSelectSortIcon.classList.contains('up_icon')) { return; } revueSelectSortIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); } addEventListenersForMobile_ = () => { const revueSortDropdownRender = document.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`); revueSortDropdownRender && SPZ.whenApiDefined(revueSortDropdownRender).then(async (api) => { await api.render(); const revueSortDropdownItem = document.querySelectorAll(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} .revue_sort_dropdown_item`); revueSortDropdownItem.forEach(item => { item.addEventListener('click', () => { const sort = item.getAttribute('data-sort'); const direction = item.getAttribute('data-direction'); revueSortDropdownItem.forEach((_item)=>{_item.classList.remove('selected')}) item.classList.add('selected'); // 抛出事件 this.triggerEvent_('sort', { sort, direction }); // 移除revue_checked元素,复制一个新的到当前选中的元素 const revueChecked = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} #${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const mDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId}`); SPZ.whenApiDefined(mDropdownEle).then((api) => { api.close(); }); }); }); }) } } SPZ.defineElement(TAG, SPZCustomRevueSort) const TAG = 'spz-custom-revue-flow'; class SpzCustomRevueFlow extends SPZ.BaseElement { constructor(element) { super(element); this.sectionId = this.element.getAttribute('section-id'); this.show_product = ''; this.with_photo = ''; this.limit = ''; this.star_least = ''; this.layout = '' this.wall_pc_num = '' this.wall_mobile_num = '' this.accent_color = '' this.isProductPage = '15' == 1; this.isCollectionPage = '15' == 2; this.isCartPage = '15' == 13; this.lastWidth = window.innerWidth; } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); const url = new URL(window.location.href); const preview_theme_id = url.searchParams.get('preview_theme_id'); if (preview_theme_id) { this.preview_theme_id = preview_theme_id; } this.commentConfig = {}; this.sort = 'created_at'; this.direction = 'desc'; this.isPC = window.innerWidth > (window.breakpoint || 960); this.appendList = []; this.commentListRes = []; this.cardConfig = window.reviewSettings[this.sectionId]; } render_ = (data={}) => { const {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section} = this.cardConfig; Object.assign(this, {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section}); if(this.layout === 'wall'){ this.with_photo = 1; }; this.params = { offset: this.appendList.length || 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 || this.commentConfig.show_reply ? 1 : 0, with_photo: this.with_photo, ...data } if(this.fill_strategy == 'store'){ if(this.review_insufficient == 'less_than'){ this.params.fill_min_threshold = minimum_comment_num; }else{ this.params.fill_min_threshold = 1; } this.params.fill_strategy = this.fill_strategy; } const summaryObj = { star_least:this.star_least, product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', filter_type: (this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store', fill_strategy: this.params?.fill_strategy || '', only_media: !!this.params.with_photo, only_featured:this.only_featured } if(this.params.fill_min_threshold){ summaryObj.fill_min_threshold = this.params.fill_min_threshold; } Promise.all([ this.fetchSummary_(summaryObj), this.fetchCommentConfig_(), this.fetchCommentList_(this.params) ]).then(response => { const [starCountRes,commentConfigRes, commentListRes] = response; this.commentConfig = commentConfigRes.data; this.commentConfig.show_product = this.show_product; this.commentListRes = commentListRes; this.starCountRes = starCountRes; /* 评论不足逻辑 */ const listLen = Number(commentListRes?.data?.count) || 0; const isListEmpty = listLen === 0; const isLessThanMinimum = this.review_insufficient === 'less_than' && listLen < this.minimum_comment_num; const shouldHide = isListEmpty || isLessThanMinimum; /* 隐藏评论区域 */ if ((this.fill_strategy === 'hide' && shouldHide) || (this.hide_review_section && isListEmpty)) { this.renderHideSkeleton_(); this.renderHideComment_(); return; } /* 显示空状态 */ if (this.fill_strategy === 'empty' && shouldHide) { this.renderHideSkeleton_(); if (this.isProductPage) { this.renderEmptyComment_(); } else { this.renderHideComment_(); } return; } if (this.preview_theme_id) { this.fetchThemeConfig_(this.preview_theme_id).then(themeConfig => { if (themeConfig?.star_color) { this.commentConfig.star_color = themeConfig.star_color; } if(this.accent_color && this.accent_color != 'null'){ this.commentConfig.star_color = this.accent_color; } }); } /* render */ const colums = this.calculateColums_(); this.renderFlowMain_({ config: this.commentConfig, comment: commentListRes.data, column_count: colums }).then(() => { this.renderHeader_({ starData: this.starCountRes.data, listData: this.commentListRes.data, star_color: this.commentConfig.star_color, comment_avg_star: this.commentListRes.data.avg_star, comment_count: this.commentListRes.data?.count, isPC: this.isPC, ...this.commentConfig, }); this.renderStarCounts_({ ...this.starCountRes.data, ...this.commentConfig }); this.addImpression(`[data-section-id="${this.sectionId}"] .revue_container`); this.renderCommentList_({ list: commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name },true); }); window.removeEventListener('resize', this.rerenderFn); window.addEventListener('resize', this.rerenderFn); }) .catch(error => { this.renderHideSkeleton_(); this.renderHideComment_(); console.error('error', error); }); } mountCallback = () => { this.render_() } /* fetch api/comment-config */ fetchCommentConfig_ = async () => { const response = await fetch('/api/comment-config'); return response.json(); } fetchSummary_ = async (data) => { const response = await fetch('/api/v1/comments/summary',{ method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } fetchCommentList_ = async(data) => { const response = await fetch(`/api/v1/comments`,{ method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ ...data, offset: data.offset, show_product:!!this.show_product, star_least:this.star_least, limit:this.limit, sort_by:data.sort_by || 'created_at', sort_direction: data.sort_direction || 'desc', filter_type:(this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store', show_reply: !!data.show_reply, only_media: !!data.with_photo, product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', only_featured: this.only_featured, }) }); if(response.status != 200){ return Promise.reject(false); } return response.json(); } fetchThemeConfig_ = async(themeId) => { const response = await fetch(`/api/comment/theme-config?theme_id=${themeId}`); return response.json(); } renderHideSkeleton_ = () => { const skeleton = document.getElementById(`revue_flow_skeleton-${this.sectionId}`); if(skeleton){ skeleton.style.display = 'none'; }; } renderHideComment_ = () => { const holderEl = document.getElementById(`revue_no_data_placeholder_${this.sectionId}`); if (window.top !== window.self) { SPZ.whenApiDefined(holderEl).then((api) => { api.render({}, true); }); }else{ holderEl.style.display = 'none'; } } renderEmptyComment_ = () => { const emptyEle = document.querySelector(`#revue-empty-1708570229557`); if(emptyEle) { emptyEle.classList.remove('hidden'); } } renderFlowMain_ = async (data) => { const mainEle = document.querySelector(`#revue_flow_render-${this.sectionId}`); if (mainEle) { const api = await SPZ.whenApiDefined(mainEle); return api.render({ ...data },true); } } calculateColums_ = () => { let colums = 1; this.isPC = window.innerWidth > (window.breakpoint || 960); if (this.layout === 'grid') { colums = this.isPC ? 4 : 2; } else { colums = this.isPC ? 2 : 1; } if(this.layout == 'wall'){ colums = this.isPC ? (this.wall_pc_num || 4) : (this.wall_mobile_num || 2); } return colums } rerenderFn = (list) => { try{ if(!this?.commentListRes?.data) return; const currentWidth = window.innerWidth; if (currentWidth == this.lastWidth ) { return } else { this.lastWidth = currentWidth; } const throttleHandle = SPZCore.Types.throttle(window,()=>{ let colums = this.calculateColums_(); this.renderFlowMain_({ config: this.commentConfig, comment: this.commentListRes.data, column_count: colums }).then(() => { this.renderHeader_({ starData: this.starCountRes.data, listData: this.commentListRes.data, star_color: this.commentConfig.star_color, comment_avg_star: this.commentListRes.data.avg_star, comment_count: this.commentListRes.data?.count, isPC: this.isPC, ...this.commentConfig, }); this.renderStarCounts_({ ...this.starCountRes.data, ...this.commentConfig }); this.renderCommentList_({ list: this.commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); },200) throttleHandle() }catch(e){ console.log(e); } } renderCommentList_ = (data,redo=false) => { if(this.accent_color && this.accent_color != 'null'){ this.commentConfig.star_color = this.accent_color; } const listEle = document.querySelector(`#revue_flow_list-${this.sectionId}`); if (listEle) { const current_list = data.list.list.map((item, index) => { return { ...item, config: this.commentConfig, index: data.sorted ? index : this.appendList.length + index, shop_name: window.SHOPLAZZA.shop.shop_name } }); if (data.sorted) { this.appendList = current_list; SPZ.whenApiDefined(listEle).then((api) => { api.listRender({ count: data.list.count, list: current_list },true); }); } else { let obj = {}; this.appendList = this.appendList.concat(current_list).reduce((cur,next) => { obj[next.id] ? "" : obj[next.id] = true && cur.push(next); return cur; },[]); SPZ.whenApiDefined(listEle).then((api) => { api.listRender({ count: data.list.count, list: current_list },redo); }); } }; this.renderLoadMoreBtn(data.list); } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.src = url.split('?')[0]; } catch (e) {}; return result; } impFunc = function (selector, cb) { // 添加自动曝光 const el = document.querySelector(selector); const onImpress = () => { cb(); }; // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器 if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress); } else if (el) { onImpress(); } }; addImpression = function (selector) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_masonry_pv', { layout_type: this.layout, level_type: this.star_least, show_number: this.limit, plugin_timestamp: new Date().valueOf().toString(), reviews_num: this.appendList.length }); }); }; addModalImpression = function (selector, params) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_modal_pv', { ...params, plugin_timestamp: new Date().valueOf().toString(), }); }); }; setupAction_ = () => { this.registerAction('refresh', async(invocation) => { this.render_({ ...this.params, offset: 0, sort_by: 'created_at', sort_direction: 'desc', show_reply: true, with_photo: false, }) }); this.registerAction('renderTypeChangeList', async(invocation) => { const {type,direction } = invocation.args.data; this.with_photo = type === 'with_photo'; this.direction = direction; this.params = { ...this.params, offset: 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1, with_photo: this.with_photo }; this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); }) this.registerAction('renderSortedList', async(invocation) => { const {sort, direction} = invocation.args.data; this.sort = sort; this.direction = direction; const panelId = this.panelId; this.params = { ...this.params, offset: 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 , with_photo: this.with_photo } this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); }); this.registerAction('renderProductCommentModal', async(invocation) => { const id = invocation.args.id; const current = this.appendList?.find(_data => _data.id == id); const modalEle = document.querySelector(`#revueDetailModal-${this.sectionId}`); const imgArr = current.img.map(image => { const width = this.getUrlKey('width', image); const height = this.getUrlKey('height', image); return { width, height, rate: (height/width).toFixed(2)*100, url: image }; }); if (modalEle) { SPZ.whenApiDefined(modalEle).then((api) => { api.renderModalFn({ data: { ...current, img: imgArr }, commentConfig: this.commentConfig, layout: this.layout, level_type: this.star_least, show_number: this.limit }); }); } }); this.registerAction('loadMore', async(invocation) => { this.params = { ...this.params, offset: this.appendList.length, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 || this.commentConfig.show_reply ? 1 : 0, with_photo: this.with_photo } this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }); }); }); } getUrlKey = (name, url) => { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } renderHeader_ = (data) => { if(this.accent_color && this.accent_color != 'null'){ data.star_color = this.commentConfig.star_color = this.accent_color; } const headerEle = document.querySelector(`#review-revue-header-${this.sectionId}`); if (headerEle) { SPZ.whenApiDefined(headerEle).then(async (api) => { api.render(data); }); } } renderStarCounts_ = (data) => { const summaryEle = document.querySelector(`#revue-summary-${this.sectionId}`); if (summaryEle) { SPZ.whenApiDefined(summaryEle).then((api) => { api.render({ ...data, star_color: this.commentConfig.star_color }); }); } } renderLoadMoreBtn = (data) => { const loadEle = document.querySelector(`#revue_flow_load_more_render-${this.sectionId}`); if (loadEle) { SPZ.whenApiDefined(loadEle).then((api) => { api.render({ comment: data }, true); }); } } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } unmountCallback(){ } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomRevueFlow) const TAG = 'spz-custom-revue-modal'; class SPZCustomRevueModal extends SPZ.BaseElement { constructor(element) { super(element); this.renderedId = ''; this.closeCB = null; this.sectionId = this.element.getAttribute('section-id'); } static deferredMount() { return false; } buildCallback = () => { this.setupAction_(); } mountCallback = () => { } setupAction_ = () => { this.registerAction('renderModal', this.renderModalFn) this.registerAction('closeFn',() => { this?.closeCB?.() }) } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.src = url.split('?')[0]; } catch (e) {}; return result; } impFunc = function (selector, cb) { // 添加自动曝光 const el = document.querySelector(selector); const onImpress = () => { cb(); }; // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器 if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress); } else if (el) { onImpress(); } }; addModalImpression = function (selector, params) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_modal_pv', { ...params, plugin_timestamp: new Date().valueOf().toString(), }); }); }; renderModalFn(receivedData){ if(!receivedData) return; const { data:current, commentConfig, layout, level_type, show_number, closeCB, mimic_mobile_style, props } = receivedData; try{ if(closeCB){ this.closeCB = () => { closeCB() }; } }catch(e){ console.log(e); }; const commentModalEl = document.querySelector(`#revue-product-comment-modal-${this.sectionId}`); const modalRenderEl = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`); if (!!mimic_mobile_style) { if (commentModalEl) { commentModalEl.classList.add('mobile-wrap'); } if (modalRenderEl) { modalRenderEl.classList.add('w-h-full-h5'); } }; const parsedImages = current?.img?.map(image => { return this.mediaParse_(`${image.url}?width=${image.width}&height=${image.height}`); }); const modalEle = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`); if (modalEle) { SPZ.whenApiDefined(modalEle).then((api) => { api.render({ ...current, img: parsedImages, config: commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name, mimic_mobile_style }, true).then(() => { this.addModalImpression('.revue_modal_container', { id: current.id, username: current.username, content: current.content, star: current.star, is_verified: current.is_verified, is_featured: current.is_featured, anonymous: current.anonymous, iso_code_3: current.iso_code_3, like_count: current.like, layout_type: layout, level_type: level_type, show_number: show_number, }); }).then(()=>{ this.renderedId = current.id }); }); } } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement('spz-custom-revue-modal', SPZCustomRevueModal) const TAG = 'spz-custom-revue-video'; class SPZCustomRevueVideo extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); // data-images 格式为 xxxx.png?width=1&height=1,xxxx.png?width=1&height=1 const images = this.element.getAttribute('data-images').split(',') || []; const parsedImages = images.map(image => { return this.mediaParse_(image); }); this.images = parsedImages; this.isPC = window.innerWidth > 960; } loadVideo = () => { this.doRender_({ images: this.images, isPC: this.isPC }).then(()=>{ this.triggerEvent_('connected', {}); }) } mountCallback = () => { this.loadVideo(); this.registerAction('loadVideo', async(invocation) => { this.loadVideo(); }) } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.preview_image = url.split('?')[0]; } catch (e) {}; return result; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueVideo) const TAG = 'spz-custom-revue-header'; class SPZCustomRevueHeader extends SPZ.BaseElement { constructor(element) { super(element); this.showCount = this.element.getAttribute('show-count'); } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.showCount = this.element.getAttribute('show-count'); this.showSummary = this.element.getAttribute('show-summary'); this.showWriteReview = this.element.getAttribute('show-write-review'); this.showType = this.element.getAttribute('show-type') ; this.showSort = this.element.getAttribute('show-sort') ; this.sectionId = this.element.getAttribute('section-id'); this.viewall = this.element.getAttribute('viewall') ?? false; this.prefix = this.element.getAttribute('prefix'); } mountCallback() { } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } render(data) { const ndata = { ...data, showCount: this.showCount, showSummary: this.showSummary, showWriteReview: this.showWriteReview, showType: this.showType, showSort: this.showSort, } if(this.viewall == 'review'){ ndata.viewall = false } return this.templates_ .findAndRenderTemplate(this.element, ndata, null, true) .then(({el}) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { if(data && Object.keys(data).length > 0) { this.updateRender(data); this.setupSummaryContainerEffects_(data); } }); } updateRender(data) { this.renderStarCounts_(data); this.renderTypeSelect(data); this.renderSortSelect(data); } renderStarCounts_ (data) { const renderData = { ...data.starData, ...data, star_color: data.star_color, isPC: data.isPC, } const summaryEle = data.isPC ? this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`) : this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`); if(summaryEle) { SPZ.whenApiDefined(summaryEle).then((api) => { api.render(renderData); }); } } renderTypeSelect(data) { const typeSelect = this.element.querySelector(`#${this.prefix}-revue-header-type-${this.sectionId}`); if(typeSelect) { SPZ.whenApiDefined(typeSelect).then((api) => { api.render(data); api.registerAction('headerType_', (invocation) => { this.triggerEvent_('headerType', invocation.args.data); }); }); } } renderSortSelect(data) { const suffix = data.suffix || this.sectionId; const sortSelect = this.element.querySelector(`#${this.prefix}-revue-header-sort-${suffix}`); if(sortSelect) { SPZ.whenApiDefined(sortSelect).then((api) => { api.registerAction('headerSort_', (invocation) => { this.triggerEvent_('headerSort', invocation.args.data); }); }); } } setupSummaryContainerEffects_(data) { if(data.isPC) { this.setupSummaryContainerHover_(); } else { this.setupSummaryContainerTap_(); } } setupSummaryContainerHover_() { const summaryContainer = this.element.querySelector(`#revue-header-summary-container-${this.sectionId}`); const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`); if (!summaryContainer || !summaryEle) return; let isHovering = false; // 鼠标移入容器时显示summary SPZUtils.Event.listen(summaryContainer, 'mouseenter', () => { isHovering = true; summaryEle.removeAttribute('hidden'); const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.add('up-icon'); } }); // 鼠标移入summary时也保持显示 SPZUtils.Event.listen(summaryEle, 'mouseenter', () => { isHovering = true; }); // 鼠标移出容器时,检查是否还在summary上 SPZUtils.Event.listen(summaryContainer, 'mouseleave', () => { isHovering = false; setTimeout(() => { if (!isHovering) { summaryEle.setAttribute('hidden', 'true'); const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.remove('up-icon'); } } }, 50); }); // 鼠标移出summary时,检查是否还在容器上 SPZUtils.Event.listen(summaryEle, 'mouseleave', () => { isHovering = false; setTimeout(() => { if (!isHovering) { summaryEle.setAttribute('hidden', 'true'); const selectIcon = summaryEle.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.remove('up-icon'); } } }, 50); }); } setupSummaryContainerTap_() { const selectIcon = this.element.querySelector(`#revue-header-summary-icon-${this.sectionId}`); const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`); if(!summaryEle) return; let isTapped = false; // 是否显示summary SPZ.whenApiDefined(summaryEle).then((api) => { api.registerAction('display', () => { if(isTapped) { isTapped = false; summaryEle.removeAttribute('hidden'); selectIcon.classList.add('up-icon'); } else { isTapped = true; summaryEle.setAttribute('hidden', 'true'); selectIcon.classList.remove('up-icon'); } }); }); } } SPZ.defineElement(TAG, SPZCustomRevueHeader); const TAG = 'spz-custom-revue-type'; class SPZCustomRevueType extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > 960; this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%'; this.randomStr = Math.random().toString(36).substr(2); this.sectionId = this.element.getAttribute('section-id') || '1708570229557'; this.prefix = this.element.getAttribute('prefix'); } mountCallback = () => { } render = (data) => { const renderData = { ...data, width: this.width, randomStr: this.randomStr }; return this.templates_ .findAndRenderTemplate(this.element, renderData, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { let revueTypeListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-type-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-type-dropdown-render-${this.sectionId}`); revueTypeListRender && SPZ.whenApiDefined(revueTypeListRender).then((api) => { api.render(renderData).then(() => { if (this.isPC) { this.addEventListenersForPC_(); } else { this.addEventListenersForMobile_(); } }); }); }); } addEventListenersForPC_ = () => { const revueSelectList = this.element.querySelector('.revue_select_list'); const revueSelectItem = this.element.querySelectorAll('.revue_select_item'); const revueSelectTypeIcon = this.element.querySelector(`#${this.prefix}-revue_select_type_icon-${this.sectionId}`); revueSelectItem.forEach(item => { item.addEventListener('click', () => { const type = item.getAttribute('data-type'); const direction = item.getAttribute('data-direction'); this.triggerEvent_('type', { type, direction }); this.element.querySelector('.revue_select_label').innerText = item.innerText; revueSelectList.classList.remove('revue_select_list_active'); const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); if (!revueSelectTypeIcon.classList.contains('up_icon')) { return; } const pcDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-pc-dropdown-${this.sectionId}`); revueSelectTypeIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); }); window.addEventListener('scroll', (e) => { if (!revueSelectTypeIcon.classList.contains('up_icon')) { return; } revueSelectTypeIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); } addEventListenersForMobile_ = () => { const revueTypeDropdownItem = this.element.querySelectorAll(`#${this.prefix}-revue-type-dropdown-${this.sectionId} .revue_type_dropdown_item`); revueTypeDropdownItem.forEach(item => { item.addEventListener('click', () => { const type = item.getAttribute('data-type'); const direction = item.getAttribute('data-direction'); revueTypeDropdownItem.forEach((_item)=>{_item.classList.remove('selected')}) item.classList.add('selected'); // 抛出事件 this.triggerEvent_('type', { type, direction }); // 移除revue_checked元素,复制一个新的到当前选中的元素 const revueChecked = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId} #${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const mDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId}`); SPZ.whenApiDefined(mDropdownEle).then((api) => { api.close(); }); }); }); } } SPZ.defineElement(TAG, SPZCustomRevueType) const TAG = 'spz-custom-revue-progress'; class SPZCustomRevueProgress extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > (window.breakpoint || 960); this.height = '6px'; this.show_percentage = this.element.getAttribute('show_percentage') || 'false'; this.show_percentage_num = this.element.getAttribute('show_percentage_num') || 100; this.color = this.element.getAttribute('color') || '#000000'; this.count = this.element.getAttribute('count'); this.total = this.element.getAttribute('total'); } mountCallback = () => { this.doRender_({ count: Number(this.count), total: Number(this.total), height: this.height, color: this.color, show_percentage: this.show_percentage, show_percentage_num: this.show_percentage_num }).then(() => { }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueProgress)
    Customer Reviews
    No reviews yet, why don't you leave the first review?
    Write a Review
    Subscribe today and get 10% off your first purchase
    Please fill in this field
    Please enter a valid email address
    Thanks for subscribing
    © 2026 moongor
    About us
    Contact us
    VIP Points
    Shoes Size Chart
    Maintenance Suggestion
    Returns & Exchanges
    Privacy&Terms
    Q&A
    Payments
    Terms and Conditions
    Shipping Info
    INTELLECTUAL PROPERTY RIGHTS
    Wholesale

    Cart

    Your cart is reserved for !
    cart
    cart
    Your cart is currently empty.
    Continue shopping