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-40'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-40'); }, }; // --- 主题配置 --- 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-40'); 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; } // 移动端:先确认 mobile container 归属,再决定是否隐藏 PC 容器 // 避免多实例场景:Instance B 不应影响 Instance A 已设置好的 mobile container 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) { // 另一个实例已占用 mobile container,不干预全局 DOM return; } // 确认是本实例管理 mobile container,再隐藏所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'none'; }); 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 searchWrap = this.getBlockWrap(); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (mobileContainer) { const existingWrap = mobileContainer.querySelector('.app-smart-product-search-wrap'); // 只有 mobile container 是空的或归属本实例时才隐藏,避免隐藏其他实例的搜索插件 if (!existingWrap || existingWrap === searchWrap) { 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; // 侧边栏按钮文本只在首次渲染时写入,避免与多语言插件产生覆写死循环 const isSidebarBtn = !!btn.closest('.smart-search-sidebar-overlay'); if (textSpan && (!isSidebarBtn || !this._sidebarBtnTextApplied)) { textSpan.textContent = config.searchText || 'Search'; if (isSidebarBtn) this._sidebarBtnTextApplied = true; } } }, 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._sidebarOpenedAsDesktop = isDesktop(); this._savedScrollbarWidth = parseFloat(getComputedStyle(document.documentElement).marginRight) || 0; this._sidebarOpenTimestamp = performance.now(); 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) { // 移动端 panel 全屏,backdrop 不可见但 @tap 还在,禁止 pointer-events 防止滚动/幽灵点击触发关闭 const backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (backdrop) backdrop.style.setProperty('pointer-events', 'none', 'important'); const contentWrap = panel.querySelector('.smart-search-wrap'); 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'); } }); }); } if (_isDesktop) { const input = overlay.querySelector('.smart-search-input'); if (input) { setTimeout(() => input.focus(), 50); } } this._syncInsideCarousel(overlay); }; if (this._contentPreloaded) { this.renderSearchHistory(); 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(() => { 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._sidebarOpenedAsDesktop = undefined; overlay.setAttribute('data-state', 'idle'); overlay.style.cssText = ''; const _backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (_backdrop) _backdrop.style.removeProperty('pointer-events'); 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) { console.log('SpzCustomSmartSearchLocation55'); 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 && templateName !== 'search') { 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, () => { // 防止在 ljs-render 渲染过程中触发重复操作 if (this._isApplyingStyle) return; const overlay = this._getSidebarOverlay && this._getSidebarOverlay(); const overlayOpen = overlay && overlay.getAttribute('data-state') === 'open'; // 多实例场景:检查页面上所有 sidebar overlay,任何一个处于活跃状态都应阻止重初始化 const anyOverlayActive = !!document.querySelector( '.smart-search-sidebar-overlay[data-state="open"],' + '.smart-search-sidebar-overlay[data-state="ready"],' + '.smart-search-sidebar-overlay[data-state="preloading"]' ); const sidebarActive = anyOverlayActive || overlayOpen || this._sidebarState === 'open' || this._sidebarState === 'ready' || this._sidebarState === 'preloading'; if (sidebarActive) { // 仅在 PC/移动端模式切换时关闭弹窗(编辑器预览切换场景) const modeChanged = this._sidebarOpenedAsDesktop !== undefined && this._sidebarOpenedAsDesktop !== isDesktop(); if (modeChanged) { this.closeSidebar(); } // sidebar 处于任何活跃状态时不重新初始化搜索 UI // 防止 initInputMode() 把父容器 display:none 导致搜索插件消失 return; } 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-40'; 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);
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-914'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-914'); }, }; // --- 主题配置 --- 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-914'); 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; } // 移动端:先确认 mobile container 归属,再决定是否隐藏 PC 容器 // 避免多实例场景:Instance B 不应影响 Instance A 已设置好的 mobile container 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) { // 另一个实例已占用 mobile container,不干预全局 DOM return; } // 确认是本实例管理 mobile container,再隐藏所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'none'; }); 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 searchWrap = this.getBlockWrap(); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (mobileContainer) { const existingWrap = mobileContainer.querySelector('.app-smart-product-search-wrap'); // 只有 mobile container 是空的或归属本实例时才隐藏,避免隐藏其他实例的搜索插件 if (!existingWrap || existingWrap === searchWrap) { 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; // 侧边栏按钮文本只在首次渲染时写入,避免与多语言插件产生覆写死循环 const isSidebarBtn = !!btn.closest('.smart-search-sidebar-overlay'); if (textSpan && (!isSidebarBtn || !this._sidebarBtnTextApplied)) { textSpan.textContent = config.searchText || 'Search'; if (isSidebarBtn) this._sidebarBtnTextApplied = true; } } }, 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._sidebarOpenedAsDesktop = isDesktop(); this._savedScrollbarWidth = parseFloat(getComputedStyle(document.documentElement).marginRight) || 0; this._sidebarOpenTimestamp = performance.now(); 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) { // 移动端 panel 全屏,backdrop 不可见但 @tap 还在,禁止 pointer-events 防止滚动/幽灵点击触发关闭 const backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (backdrop) backdrop.style.setProperty('pointer-events', 'none', 'important'); const contentWrap = panel.querySelector('.smart-search-wrap'); 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'); } }); }); } if (_isDesktop) { const input = overlay.querySelector('.smart-search-input'); if (input) { setTimeout(() => input.focus(), 50); } } this._syncInsideCarousel(overlay); }; if (this._contentPreloaded) { this.renderSearchHistory(); 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(() => { 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._sidebarOpenedAsDesktop = undefined; overlay.setAttribute('data-state', 'idle'); overlay.style.cssText = ''; const _backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (_backdrop) _backdrop.style.removeProperty('pointer-events'); 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) { console.log('SpzCustomSmartSearchLocation55'); 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 && templateName !== 'search') { 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, () => { // 防止在 ljs-render 渲染过程中触发重复操作 if (this._isApplyingStyle) return; const overlay = this._getSidebarOverlay && this._getSidebarOverlay(); const overlayOpen = overlay && overlay.getAttribute('data-state') === 'open'; // 多实例场景:检查页面上所有 sidebar overlay,任何一个处于活跃状态都应阻止重初始化 const anyOverlayActive = !!document.querySelector( '.smart-search-sidebar-overlay[data-state="open"],' + '.smart-search-sidebar-overlay[data-state="ready"],' + '.smart-search-sidebar-overlay[data-state="preloading"]' ); const sidebarActive = anyOverlayActive || overlayOpen || this._sidebarState === 'open' || this._sidebarState === 'ready' || this._sidebarState === 'preloading'; if (sidebarActive) { // 仅在 PC/移动端模式切换时关闭弹窗(编辑器预览切换场景) const modeChanged = this._sidebarOpenedAsDesktop !== undefined && this._sidebarOpenedAsDesktop !== isDesktop(); if (modeChanged) { this.closeSidebar(); } // sidebar 处于任何活跃状态时不重新初始化搜索 UI // 防止 initInputMode() 把父容器 display:none 导致搜索插件消失 return; } 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-914'; 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);
Close
  • Comfort Orthopedic Sandals
  • Slip On Comfortable Orthopedic Plantar Fasciitis Women Shoes
  • Rechargeable Digital Hearing Aid Severe Loss Invisible Ear Aids High Power
  • Oscillating Fan Bladeless Tower Fan
  • Calming Dog Bed - The Original Super Comfy & Anti Anxiety Pet Bed
  • LED Rechargeable Cordless Metal Table Lamp
  • Log in
    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-898'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-898'); }, }; // --- 主题配置 --- 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-898'); 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; } // 移动端:先确认 mobile container 归属,再决定是否隐藏 PC 容器 // 避免多实例场景:Instance B 不应影响 Instance A 已设置好的 mobile container 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) { // 另一个实例已占用 mobile container,不干预全局 DOM return; } // 确认是本实例管理 mobile container,再隐藏所有 PC 容器 document.querySelectorAll('.' + SEARCH_CONTAINER_CLASS).forEach(el => { el.style.display = 'none'; }); 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 searchWrap = this.getBlockWrap(); const mobileContainer = document.querySelector('.smart-search-mobile-container'); if (mobileContainer) { const existingWrap = mobileContainer.querySelector('.app-smart-product-search-wrap'); // 只有 mobile container 是空的或归属本实例时才隐藏,避免隐藏其他实例的搜索插件 if (!existingWrap || existingWrap === searchWrap) { 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; // 侧边栏按钮文本只在首次渲染时写入,避免与多语言插件产生覆写死循环 const isSidebarBtn = !!btn.closest('.smart-search-sidebar-overlay'); if (textSpan && (!isSidebarBtn || !this._sidebarBtnTextApplied)) { textSpan.textContent = config.searchText || 'Search'; if (isSidebarBtn) this._sidebarBtnTextApplied = true; } } }, 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._sidebarOpenedAsDesktop = isDesktop(); this._savedScrollbarWidth = parseFloat(getComputedStyle(document.documentElement).marginRight) || 0; this._sidebarOpenTimestamp = performance.now(); 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) { // 移动端 panel 全屏,backdrop 不可见但 @tap 还在,禁止 pointer-events 防止滚动/幽灵点击触发关闭 const backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (backdrop) backdrop.style.setProperty('pointer-events', 'none', 'important'); const contentWrap = panel.querySelector('.smart-search-wrap'); 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'); } }); }); } if (_isDesktop) { const input = overlay.querySelector('.smart-search-input'); if (input) { setTimeout(() => input.focus(), 50); } } this._syncInsideCarousel(overlay); }; if (this._contentPreloaded) { this.renderSearchHistory(); 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(() => { 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._sidebarOpenedAsDesktop = undefined; overlay.setAttribute('data-state', 'idle'); overlay.style.cssText = ''; const _backdrop = overlay.querySelector('.smart-search-sidebar-backdrop'); if (_backdrop) _backdrop.style.removeProperty('pointer-events'); 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) { console.log('SpzCustomSmartSearchLocation55'); 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 && templateName !== 'search') { 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, () => { // 防止在 ljs-render 渲染过程中触发重复操作 if (this._isApplyingStyle) return; const overlay = this._getSidebarOverlay && this._getSidebarOverlay(); const overlayOpen = overlay && overlay.getAttribute('data-state') === 'open'; // 多实例场景:检查页面上所有 sidebar overlay,任何一个处于活跃状态都应阻止重初始化 const anyOverlayActive = !!document.querySelector( '.smart-search-sidebar-overlay[data-state="open"],' + '.smart-search-sidebar-overlay[data-state="ready"],' + '.smart-search-sidebar-overlay[data-state="preloading"]' ); const sidebarActive = anyOverlayActive || overlayOpen || this._sidebarState === 'open' || this._sidebarState === 'ready' || this._sidebarState === 'preloading'; if (sidebarActive) { // 仅在 PC/移动端模式切换时关闭弹窗(编辑器预览切换场景) const modeChanged = this._sidebarOpenedAsDesktop !== undefined && this._sidebarOpenedAsDesktop !== isDesktop(); if (modeChanged) { this.closeSidebar(); } // sidebar 处于任何活跃状态时不重新初始化搜索 UI // 防止 initInputMode() 把父容器 display:none 导致搜索插件消失 return; } 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-898'; 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);
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes
Super Soft Women's Orthopedic Walking Shoes

Super Soft Women's Orthopedic Walking Shoes

Top-selling in the UK
Customers say this fits true to size
97.8% Customer satisfaction
Hassle-free returns. 30-day postage paid returns
Price
£50.00
£29.99
Save  £20.01
The current produc does not participate any Rebate. Switch the participating product to check the design.
(This prompt will not be displayed on the client-side.)
var theme = window.C_SETTINGS && C_SETTINGS.theme && C_SETTINGS.theme.merchant_theme_name; var isFlash = /Flash/gi.test(theme); var isGeek = /Geek/gi.test(theme); var isNova23 = /Nova 2023/gi.test(theme); var isWind = /Wind/gi.test(theme); var isOnePage = /OnePage/gi.test(theme); var isHero = /Hero/gi.test(theme); var isBoost = /Boost/gi.test(theme); var isEva = /Eva/gi.test(theme); var isFarida = /Farida/gi.test(theme); var isPluto = /Pluto/gi.test(theme); var isLifeStyle = /Life Style/gi.test(theme); if(window.self === window.top) { (window.disabled_exts ||=[]).push('product_detail_rebate'); } class SpzRebateComponent extends SPZ.BaseElement { constructor(element) { super(element); } xhr_ = SPZServices.xhrFor(this.win); viewport_ = this.getViewport(); action_ = null; lang = document.documentElement.lang || 'en-US'; landPage = "\/promotions\/rebate\/"; pageType = 1; cart = []; initData = null; rebateInfo = null; renderData = null; footerImage = `${this.win.SHOPLAZZA["image_domain"]}oss/operation/e8ebb03dbb710457ca3b4b6a70898ab2.svg`; isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.initData = this.getProduct(); this.action_ = SPZServices.actionServiceForDoc(this.element); this.registerAction("triggerGetRenderData", () => { const event = SPZUtils.Event.create(this.win, "triggerGetRenderData", this.renderData); this.action_.trigger(this.element, "getRenderData", event); }); this.registerAction("bindPropagation", () => { document.querySelector(".product_detail_rebate_list").addEventListener("click", e => { e.stopPropagation(); this.win.sa && this.win.sa.track("plugin_rebate_promotion_click", { plugin_timestamp: Date.now(), plugin_location: "info", product_id: this.initData.product.id, discount_id: this.rebateInfo.discount_list.map((item) => item.discount_id)[0], }); }); }); } async mountCallback() { document.addEventListener("dj.variantChange", e => { const data = e.detail; if (document.querySelector("#product-select-modal.show")) return; this.initData = this.getProductJson(data); if (this.initData && this.initData.product && data.product && this.initData.product.id === data.product.id) { this.initRebate(this.initData, true); } else { this.getRebateInfo(); } }); document.addEventListener("dj.addToCart", e => { const v = e.detail; this.rebateInfo && this.win.sa && this.win.sa.track("plugin_rebate_atc", { variant_discount_id: this.getVariantDiscountId(v.variant_id).map(item => item.discount_id), discount_ids: this.rebateInfo.discount_list.map(item => item.discount_id), variant_id: v.variant_id, product_id: v.product_id, price: v.item_price, number: v.number, }); }); await this.getRebateInfo(); setTimeout(()=>{ if (document.querySelector(".plugin-container__bottom-fixed")) { this.showDiscountPopupsInfoBar(); } else { this.win.addEventListener("extloaded", () => { this.showDiscountPopupsInfoBar(); }); } },1000) } getProductJson = (mergeData = {}) => { const productJson = document.querySelector("#product-json"); let productJsonData = {}; if (productJson && productJson.textContent) { try { productJsonData = JSON.parse(productJson.textContent); } catch (e) {} } // 深度合并函数 const deepMerge = (target, source) => { if (source === null || source === undefined) { return target; } if (typeof source !== 'object' || Array.isArray(source)) { return source; } const result = { ...target }; for (const key in source) { if (source.hasOwnProperty(key)) { if ( typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key]) && typeof target[key] === 'object' && target[key] !== null && !Array.isArray(target[key]) ) { result[key] = deepMerge(target[key], source[key]); } else { result[key] = source[key]; } } } return result; }; return deepMerge(productJsonData, mergeData); } getProduct = (() => { document.addEventListener("dj.variantChange", e => { if (!e.detail || !e.detail.product) return; let productJsonData = getProductJson(e.detail); if (this.win.jQuery && this.win.jQuery.fn && this.win.jQuery(document).data("djproduct") && productJsonData) { this.win.jQuery(document).data("djproduct", productJsonData); } }); return () => { let productData = null; if (this.win.jQuery && this.win.jQuery.fn) { try { let product = this.win.jQuery(document).data("djproduct"); if (product) { productData = JSON.parse(JSON.stringify(product)); } else { productData = null; } } catch (error) { productData = null; } } if (!productData) { const productJson = document.querySelector("#product-json"); productData = (productJson && productJson.textContent && JSON.parse(productJson.textContent)) || null; } return productData; }; })(); clearRebateInfoDom = () => { // 1. 清除 apis.render 渲染的 DOM (app_rebate_section) const rebateSection = document.getElementById('app_rebate_section'); if (rebateSection) { const rebateRuleSection = rebateSection.querySelector('.rebate_rule_section'); if (rebateRuleSection) { rebateRuleSection.remove(); } } // 2. 清除 templates_.renderTemplate 渲染的 DOM (app_rebate_block) const rebateBlock = document.getElementById('app_rebate_block'); if (rebateBlock) { const appRebateList = rebateBlock.querySelector('.app_rebate_list'); if (appRebateList) { appRebateList.remove(); } } // 3. 清除 insertProductDetailRebateTag 插入的 DOM // 清除所有 rebate-tag 元素 document.querySelectorAll('.slider-discount-tag.dj_skin_product_title.rebate-tag').forEach(tag => { tag.remove(); }); // 移除 data-rebate-tag 属性 const productContainer = document.querySelector('.product-details, .product-details, .page_container, .product-images, [data-section-type="product"]'); if (productContainer && productContainer.hasAttribute('data-rebate-tag')) { productContainer.removeAttribute('data-rebate-tag'); } }; initRebate = this.win.SPZCore.Types.debounce( this.win, (async (data) => { let discount_list = Object.assign([], this.rebateInfo?.discount_list); /* 按子商品的多少对优惠信息进行排序 */ discount_list && discount_list.sort((a, b) => { return b.variant_ids.length - a.variant_ids.length; }); /* 选中子商品时 筛选子商品的优惠信息 */ if (data.selected && data.selected.id) { discount_list = this.getVariantDiscountId(data.selected.id); } /* 无满减信息 */ if (!(discount_list && discount_list.length)) { this.clearRebateInfoDom(); return; } const isSection = !!document.querySelector( `div[data-section-type^="shoplazza://apps/publicapp/blocks/rebate"] #rebate_custom_component` ); if ( (this.rebateInfo.rebate_type == "sku" && data && data.selected && data.selected.id) || this.rebateInfo.rebate_type == "spu" ) { let nowLandpage = this.landPage; if (discount_list[0]) { nowLandpage = this.landPage + discount_list[0].discount_id || ""; } const info = { rebate: discount_list[0], maxShowCount: this.win.innerWidth > 768 ? 3 : 1, landPage: nowLandpage, modalFooterImg: `url(${`${this.win.SHOPLAZZA["image_domain"]}oss/operation/e8ebb03dbb710457ca3b4b6a70898ab2.svg`})`, }; this.renderData = info; if(isSection) { SPZ.whenApiDefined( document.getElementById("app_rebate_section") ).then(apis => { apis.render(info, true); }); } else { // 重新渲染 抖动问题处理 this.templates_ = SPZServices.templatesForDoc(); const newTplDom = await this.templates_.renderTemplate(document.querySelector('#appRebateBlockTpl'), info) const parentDiv = document.querySelector('#app_rebate_block'); const oldDom = parentDiv.querySelector('.app_rebate_list'); if(oldDom){ parentDiv.replaceChild(newTplDom, oldDom); } else { parentDiv.appendChild(newTplDom); } } } this.insertProductDetailRebateTag(this.rebateInfo.tag); var pluginCurrencyEvent = new CustomEvent("plugin_currency_update"); document.dispatchEvent(pluginCurrencyEvent); }).bind(this), 10 ); getRebateInfo = async () => { if (this.initData && this.initData.product && this.initData.product.id) { var variant_ids = this.initData.product.variants.map(variant => variant.id); const res = await this.xhr_.fetchJson( "\/api\/discount-rebate\/product-discount", { method: "POST", body: { product_id: this.initData.product.id, product_type: this.initData.product.product_type, variant_ids: variant_ids, }, } ); if (!SPZCore.Types.isEmptyObject(res.rebate_info)) { res.rebate_info.tag = res.tag; res.rebate_info.rebate_type = res.rebate_type; this.rebateInfo = res.rebate_info; this.initRebate(this.initData); } else { if (this.win.top !== this.win.self) { const noActivity = document.getElementById("no-rebate-activity"); noActivity && (noActivity["style"].display = "block"); } } } }; getVariantDiscountId = (variant_id) => { if (!variant_id || !this.rebateInfo) return []; var rebateId = this.rebateInfo.variant_discount_map[variant_id]; return this.rebateInfo.discount_list.filter(item => item.discount_id == rebateId) || []; }; insertProductDetailRebateTag = (tag) => { if (!tag) return // 旧判断逻辑 const productSelectModal = document.querySelector('#product-select-modal'); if (productSelectModal && productSelectModal.classList.contains('show')) { return; } setTimeout(() => { var $tag_container = []; if (isNova23) { $tag_container = document.querySelectorAll('.product-details .product-images-container'); } else if (isFlash) { $tag_container = document.querySelectorAll('.product-detail .product-images .product-main-images-container'); } else if (isGeek) { $tag_container = document.querySelectorAll('.product-images #product-images-inner-container spz-carousel .i-spzhtml-slide-item'); } else if (isWind) { $tag_container = document.querySelectorAll('.product-detail .product-images-container .i-spzhtml-slides-container'); } else if (isOnePage) { $tag_container = document.querySelectorAll('.product-details .product-main-images'); } else if (isHero) { $tag_container = document.querySelectorAll('.product-detail #product-images-container #product-images-carousel .spz-carousel-slide'); } else if (isBoost) { $tag_container = document.querySelectorAll('.boost-product-detail .product-image__layout-list .slides .slides-item .product-info__slide .slider-zoom'); } else if (isEva) { $tag_container = document.querySelectorAll('.page_container [data-section-type="product"] .support-slick'); } else if (isFarida) { $tag_container = document.querySelectorAll('.product-details .product-images-container'); } else if (isLifeStyle) { $tag_container = document.querySelectorAll('.page_container [data-section-type="product_detail"] .sep-slider,.support-slick'); } else if (isPluto) { $tag_container = document.querySelectorAll('.page_container [data-section-type="product_detail"] .sep-slider,.support-slick'); } if($tag_container.length === 0) return; // 给商祥页添加满送插件的标识属性 const $product_container = document.querySelector('.product-details, .product-details, .page_container, .product-images, [data-section-type="product"]') if($product_container) { $product_container.setAttribute('data-rebate-tag', 'true'); } // 部分主题需要调整样式 if (isWind) { Array.from($tag_container).forEach(container => { container.style.position = 'relative'; }); } document.querySelectorAll('.slider-discount-tag.dj_skin_product_title.rebate-tag').forEach(tag => tag.remove()); // 遍历所有容器并插入标签 Array.from($tag_container).forEach(container => { container.insertAdjacentHTML('beforeend', `<div class="slider-discount-tag dj_skin_product_title rebate-tag">${tag}</div>`); }); }, 1000) }; fetchInfoBar = async () => { let discount_ids = []; if (this.pageType === 1) { discount_ids = this.rebateInfo && this.rebateInfo.discount_list.map(item => item.discount_id); } else if (this.pageType === 38) { discount_ids = [this.win.rebateObj.rebateCollection_id] || []; } const productObj = this.getProduct(); const { cart } = await this.xhr_.fetchJson('/api/cart') return this.xhr_.fetchJson("\/api\/discount-rebate\/global-text", { method: "POST", body: { product_type: productObj && productObj.product && productObj.product.product_type, line_items: (cart?.line_items || []).map(item => ({ variant_id: item.variant_id, product_id: item.product_id, quantity: item.quantity, price: item.price, selected: !item.unchecked, })), discount_ids: discount_ids, }, }); }; renderBottomBanner = res => { if (!res.tips) return; document.querySelector(".discount__info-bar")?.remove(); var bar_style = `background:linear-gradient(90deg,${res.config.background_color_start},${res.config.background_color_end}); color:${res.config.color};`; let data = { tips: res.tips, landPage: this.landPage + res.id, bar_style }; const html = SPZCore.Dom.htmlFor(this.element); const banner = html([ `<a impr="1" imprevt="1" id="rebate_bottom_bar" href=${data.landPage} class="discount__info-bar text-truncate" data-activity-type="rebate" style="${data.bar_style}">${data.tips}</a>`, ]); document.querySelector(".plugin-container__bottom-fixed").appendChild(banner); const pluginCurrencyEvent = new CustomEvent("plugin_currency_update"); document.dispatchEvent(pluginCurrencyEvent); if (res.id) { var trackParams = { page: this.pageType, discount_id: res.id, product_id: this.getProduct()?.product.id, }; banner.addEventListener("click", () => { this.win.sa && this.win.sa.track("plugin_rebate_promotion_click", { plugin_timestamp: Date.now(), plugin_location: "bottom_bar", product_id: trackParams.product_id, discount_id: trackParams.discount_id, }); }); this.win.sa && this.win.sa.track("plugin_rebate_banner_pv", trackParams); } }; showDiscountPopupsInfoBar = () => { if ([13, 14, 19, 30, 31].includes(this.pageType)) return; if (document.querySelector(".plugin-container__bottom-fixed .discount__info-bar")) return; this.fetchInfoBar().then(this.renderBottomBanner); document.addEventListener("dj.cartChange", () => { this.fetchInfoBar().then(this.renderBottomBanner); }); }; } SPZ.defineElement("spz-custom-rebate", SpzRebateComponent);
Color — Pink
Please select a color
Size
Please select a size
Quantity
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-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-review-media'; class SPZCustomReviewMedia 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; } mountCallback = () => { this.doRender_({ images: this.images, isPC: this.isPC }).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); }); } 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, SPZCustomReviewMedia) const TAG = 'spz-custom-revue-carousel'; class SpzCustomRevueCarourel extends SPZ.BaseElement { constructor(element) { super(element); this.debouncedCartChangeHandler = null; } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.setupAction_(); this.reviewsList = [] this.isPC = window.innerWidth > (window.breakpoint || 960); this.blockIndex = this.element.getAttribute('data-block-index'); this.blockSectionId = this.element.getAttribute('data-section-id'); this.commentConfig = window.globaCarouselSettings[this.blockIndex] const { star_least, only_complex, only_show_selected } = this.commentConfig; this.params = { star_least: Number(star_least) || 1, only_media: !!only_complex, show_reply: true, limit: 20, offset: 0, filter_type: 'product', show_product: true, only_featured: !!only_show_selected, } this.debouncedCartChangeHandler = this.debounce_(this.renderReviewsByCartProducts_.bind(this), 500); } mountCallback = () => { const { isNeedFill, min } = this.getIfFillReviews_(); if (this.blockSectionId == 'cart_drawer') { this.product_ids = this.commentConfig.cart_products_id; } else { this.product_ids = window.SHOPLAZZA.meta.page.resource_id; }; this.params = { ...this.params, product_ids: this.product_ids || '', ...isNeedFill ? { fill_strategy: 'store', fill_min_threshold: min } : {} }; this.fetchConfigReviewsCarousel_(); if (this.blockSectionId == 'cart_drawer') { document.removeEventListener('dj.cartChange', this.debouncedCartChangeHandler); document.addEventListener('dj.cartChange', this.debouncedCartChangeHandler); } } unmountCallback() { document.removeEventListener('dj.cartChange', this.debouncedCartChangeHandler); } setupAction_ = () => { this.registerAction('renderProductCommentModal', async(invocation) => { const { current } = invocation.args; const currentReview = this.reviewsList.find(_data => _data.id == current); const imgArr = currentReview.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 }; }); const modalEle = document.querySelector(`#revueDetailModal-${this.blockSectionId}`); if (modalEle) { SPZ.whenApiDefined(modalEle).then((api) => { api.renderModalFn({ data: { ...currentReview, img: imgArr }, ...this.blockSectionId == 'cart_drawer' ? { mimic_mobile_style: true } : {}, closeCB: () => { const carouselEl = document.querySelector(`#reviews-carousel-${this.blockSectionId}-${this.blockIndex}`); if(carouselEl){ carouselEl.removeAttribute('pause'); } }, commentConfig: this.commentConfig, // 评论配置 layout: '', // 布局 level_type: this.commentConfig.star_least, // 最低星级 show_number: 1 // 显示数量 }); }) const carouselEl = document.querySelector(`#reviews-carousel-${this.blockSectionId}-${this.blockIndex}`); if(carouselEl){ carouselEl.setAttribute('pause', ''); } } }); } getUrlKey_ = (name, url) => { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } getIfFillReviews_ = () => { const { comment_handle_type, review_type, min_comments_count } = this.commentConfig; const min = Number(min_comments_count); const isExistFillType = comment_handle_type !== 'no_carousel_card'; return { isNeedFill: isExistFillType, min: review_type == 'less than' ? min : 1 }; } debounce_ = (func, delay) => { let timeoutId; return (...args) => { // 使用箭头函数保留实例上下文 clearTimeout(timeoutId); timeoutId = setTimeout(() => { func(...args); }, delay); }; } fetchCommentConfig_ = async () => { const response = await fetch('/api/comment-config'); return response.json(); } fetchCommentList_ = async(data) => { const response = await fetch('/api/v1/comments', { method: 'POST', body: JSON.stringify(data) }); return response.json(); } fetchCartList_ = async() => { const response = await fetch(`/api/cart`); return response.json(); } renderReviewsByCartProducts_ = async () => { try { const data = await this.fetchCartList_(); this.product_ids = data?.cart?.line_items.map(item => item.product_id).join(','); if (this.product_ids) { this.params = { ...this.params, product_ids: this.product_ids, }; const commentsRes = await this.fetchCommentList_(this.params); const { isNeedFill, min } = this.getIfFillReviews_(); const isBlank = !isNeedFill && Number(commentsRes.data.count) < min; this.renderReviewsList_({ list: isBlank ? { list: [] } : commentsRes.data, config: this.commentConfig }); } } catch (err) { this.renderEmptyReviewCarousel_(); } } fetchConfigReviewsCarousel_ = ()=> { Promise.all([this.fetchCommentConfig_(), this.fetchCommentList_(this.params)]) .then(([configRes, commentsRes]) => { const rawColor = this.commentConfig.carousel_accent_color const star_color = !rawColor ? configRes.data.star_color : rawColor; this.commentConfig = { ...this.commentConfig, ...configRes.data, star_color, }; const { isNeedFill, min } = this.getIfFillReviews_(); const isBlank = !isNeedFill && Number(commentsRes.data.count) < min; this.renderReviewsList_({ list: isBlank ? { list: [] } : commentsRes.data, config: this.commentConfig }); }) .catch(error => { this.renderEmptyReviewCarousel_(); }); } renderEmptyReviewCarousel_ = () => { const emptyEle = document.querySelector(`#revue_empty-${this.blockSectionId}-${this.blockIndex}`); const isInB = window.top != window.self; if (emptyEle && isInB) { emptyEle.classList.remove('hidden'); } const carouselEle = document.querySelector(`#revue-carousel-box-${this.blockSectionId}-${this.blockIndex}`); if (carouselEle) { carouselEle.classList.add('hidden'); } } renderReviewsList_ = (data) => { const listEle = document.querySelector(`#revue-carousel-box-${this.blockSectionId}-${this.blockIndex}`); const emptyEle = document.querySelector(`#revue_empty-${this.blockSectionId}-${this.blockIndex}`); if (listEle) { if (data.list.list.length == 0) { this.renderEmptyReviewCarousel_(); } else { listEle.classList.remove('hidden'); if (emptyEle) { emptyEle.classList.add('hidden'); } SPZ.whenApiDefined(listEle).then((api) => { api.render({ ...data, list: data.list.list, star_color: this.commentConfig.star_color, isPC: this.isPC, }, true); }) .catch((error) => { console.log(error); }); }; } this.reviewsList = data.list.list } 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, SpzCustomRevueCarourel) 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-selector'; class SpzCustomRevueSelector extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.total = this.element.getAttribute('total'); this.blockIndex = this.element.getAttribute('blockIndex'); this.setupAction_(); } setupAction_ = () => { this.registerAction('toggleChangeSelector', async (invocation) => { const { option } = invocation.args; const total = Number(this.total); this.updateDots_(option, total); }); } updateDots_ = (currentIndex, totalDots) => { const dots = this.element.querySelectorAll('.dot'); if (totalDots < 2 || dots?.length < 1) return; const maxVisibleDots = 5; // 重置所有点 dots?.forEach(dot => { dot.classList.remove('active', 'scaled'); }); // 设置当前激活点 if (dots[currentIndex]) { dots[currentIndex].classList.add('active'); } // 计算显示的点和缩放效果 let startIndex = 0; let endIndex = totalDots - 1; let visibleRange = [0, totalDots - 1]; let translateX = 0; if (totalDots > maxVisibleDots) { // 计算可见范围 if (currentIndex < maxVisibleDots - 1) { // 前 maxVisibleDots-1 个点 startIndex = 0; endIndex = maxVisibleDots - 1; translateX = 0; } else if (currentIndex >= totalDots - (maxVisibleDots - 1)) { // 最后 maxVisibleDots-1 个点 startIndex = totalDots - maxVisibleDots; endIndex = totalDots - 1; translateX = -(totalDots - maxVisibleDots) * 8; } else { // 中间点 startIndex = currentIndex - Math.floor(maxVisibleDots / 2); endIndex = currentIndex + Math.floor(maxVisibleDots / 2); // 调整边界情况 if (startIndex < 0) { endIndex -= startIndex; startIndex = 0; } if (endIndex >= totalDots) { startIndex -= (endIndex - totalDots + 1); endIndex = totalDots - 1; } translateX = -startIndex * 8; } // 设置可见范围 visibleRange = [startIndex, endIndex]; // 设置点的缩放效果 // 最左边的点(除了第一个) if (startIndex > 0) { dots[startIndex].classList.add('scaled'); } // 最右边的点(除了最后一个) if (endIndex < totalDots - 1) { dots[endIndex].classList.add('scaled'); } // 特殊处理第一个和最后一个点 if (currentIndex === 0) { dots[0].classList.remove('scaled'); } if (currentIndex === totalDots - 1) { dots[totalDots - 1].classList.remove('scaled'); } } // 设置dotsWrap的位置 const dotsWrap = this.element.querySelector('#dotsWrap'); if (dotsWrap) { dotsWrap.style.transform = `translateX(${translateX}px)`; } } mountCallback() { this.doRender_({ total: this.total, blockIndex: this.blockIndex }); } 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, SpzCustomRevueSelector);
/** @private {string} */ class SpzCustomAnchorScroll extends SPZ.BaseElement { static deferredMount() { return false; } constructor(element) { super(element); /** @private {Element} */ this.scrollableContainer_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.viewport_ = this.getViewport(); this.initActions_(); } setTarget(containerId, targetId) { this.containerId = '#' + containerId; this.targetId = '#' + targetId; } scrollToTarget() { const container = document.querySelector(this.containerId); const target = container.querySelector(this.targetId); const {scrollTop} = container; const eleOffsetTop = this.getOffsetTop_(target, container); this.viewport_ .interpolateScrollIntoView_( container, scrollTop, scrollTop + eleOffsetTop ); } initActions_() { this.registerAction( 'scrollToTarget', (invocation) => this.scrollToTarget(invocation?.caller) ); this.registerAction( 'setTarget', (invocation) => this.setTarget(invocation?.args?.containerId, invocation?.args?.targetId) ); } /** * @param {Element} element * @param {Element} container * @return {number} * @private */ getOffsetTop_(element, container) { if (!element./*OK*/ getClientRects().length) { return 0; } const rect = element./*OK*/ getBoundingClientRect(); if (rect.width || rect.height) { return rect.top - container./*OK*/ getBoundingClientRect().top; } return rect.top; } } SPZ.defineElement('spz-custom-anchor-scroll', SpzCustomAnchorScroll); const STRENGTHEN_TRUST_URL = "/api/strengthen_trust/settings"; class SpzCustomStrengthenTrust extends SPZ.BaseElement { constructor(element) { super(element); this.renderElement_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.xhr_ = SPZServices.xhrFor(this.win); const renderId = this.element.getAttribute('render-id'); SPZCore.Dom.waitForChild( document.body, () => !!document.getElementById(renderId), () => { this.renderElement_ = SPZCore.Dom.scopedQuerySelector( document.body, `#${renderId}` ); if (this.renderElement_) { this.render_(); } this.registerAction('track', (invocation) => { this.track_(invocation.args); }); } ); } render_() { this.fetchData_().then((data) => { if (!data) { return; } SPZ.whenApiDefined(this.renderElement_).then((apis) => { apis?.render(data); document.querySelector('#strengthen-trust-render-1539149753700').addEventListener('click',(event)=>{ if(event.target.nodeName == 'A'){ this.track_({type: 'trust_content_click'}); } }) }); }); } track_(data = {}) { const track = window.sa && window.sa.track; if (!track) { return; } track('trust_enhancement_event', data); } parseJSON_(string) { let result = {}; try { result = JSON.parse(string); } catch (e) {} return result; } fetchData_() { return this.xhr_ .fetchJson(STRENGTHEN_TRUST_URL) .then((responseData) => { if (!responseData || !responseData.data) { return null; } const data = responseData.data; const moduleSettings = (data.module_settings || []).reduce((result, moduleSetting) => { return result.concat(Object.assign(moduleSetting, { logos: (moduleSetting.logos || []).map((item) => { return moduleSetting.logos_type == 'custom' ? this.parseJSON_(item) : item; }) })); }, []); return Object.assign(data, { module_settings: moduleSettings, isEditor: window.self !== window.top, }); }); } } SPZ.defineElement('spz-custom-strengthen-trust', SpzCustomStrengthenTrust);
const isSpecialHeroTheme = window.SHOPLAZZA?.theme?.merchant_theme_name == 'Hero' && window.SHOPLAZZA?.theme?.merchant_theme_c_version == '2.2.19'; const specialHeroThemeClassName = 'hero_2_2_19_smart_recommend_block'; class SpzSmartBlockComponent extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.container_ = null; this.i18n_ = {}; this.config_ = {}; this.show_type_ = 3; this.product_resource_id_ = ''; this.collection_resource_id_ = ''; this.cart_items_ = []; this.customer_id_ = ''; this.order_id_ = ''; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { const template_type = window.C_SETTINGS.meta.page.template_type; if (template_type === 1) { this.show_type_ = 3; this.product_resource_id_ = window.C_SETTINGS.meta.page.resource_id; } else if (template_type === 2) { this.show_type_ = 4; this.collection_resource_id_ = window.C_SETTINGS.meta.page.resource_id; } else if (template_type === 15){ this.show_type_ = 5; } else if (template_type === 13){ this.show_type_ = 6; } else if (template_type === 20){ this.show_type_ = 7; this.customer_id_ = window.C_SETTINGS.customer.customer_id; } else if (template_type === 35){ this.show_type_ = 8; this.order_id_ = window.location.pathname.split('/').pop(); } this.templates_ = SPZServices.templatesForDoc(this.element); this.setAction_(); } mountCallback() { const that = this; const themeName = window.C_SETTINGS.theme.merchant_theme_name; const isGeek = /Geek/.test(themeName); this.fetchRules().then((res) => { if (res && res.rules && res.rules.length) { const blockEl = document.getElementById('smart_recommend_block'); this.initBlockClass(blockEl); this.initItemClass(blockEl); SPZ.whenApiDefined(blockEl).then((api) => { api.render({data: res}, true).then(() => { if (isGeek && that.show_type_ === 6) { blockEl.querySelector('.plugin_container_wrpper').style.padding = '30px 0'; } const recommendStyle = document.createElement('style'); recommendStyle.innerHTML = ` .plugin__recommend_container,.app-recommend-card { display: none !important; } `; document.head.appendChild(recommendStyle); const fetchList = []; res.rules.forEach((rule) => { fetchList.push(this.fetchRuleProductList(rule.id)); }); const fetchAll = Promise.all(fetchList); fetchAll.then((p_res) => { res.rules.forEach((rule, index) => { rule.products = p_res[index] && p_res[index].products; if (rule.products && rule.products.length) { const modalRender = document.getElementById('smart_recommend_js_root'); const $dest = document.getElementById('cart'); const isLifeStyle = /Life.*Style/.test(window.C_SETTINGS.theme.merchant_theme_name); if (modalRender && isLifeStyle && $dest.clientWidth > 767) { modalRender.classList.add('zb-mt-[-180px]') } } const ruleEl = document.getElementById('smart_recommend_rule_' + rule.id); SPZ.whenApiDefined(ruleEl).then((api) => { api.render({data: rule}, true).then(() => { that.impressListen(`#smart_recommend_rule_ul_${rule.id}`, function(){ that.trackRuleImpress(rule); }); const btnElList = document.querySelectorAll(`#smart_recommend_rule_ul_${rule.id} button`); btnElList.forEach((btnEl) => { if (btnEl && rule.config && rule.config.quick_shop_button_bg_color && rule.config.quick_shop_button_text_color) { btnEl.style.backgroundColor = rule.config.quick_shop_button_bg_color; btnEl.style.color = rule.config.quick_shop_button_text_color; } }); if (isSpecialHeroTheme) { ruleEl.querySelectorAll(`.smart_recommend_title`).forEach(dom=>{ dom.classList.add('type-title-font-family'); }); document.querySelectorAll(`.${specialHeroThemeClassName} #smart_recommend_rule_ul_${rule.id} .zb-recommend-price-line-through .money`).forEach(dom=>{ dom.classList.add('type-body-font-family'); }); }; }); }); }); }); }) }) } else { if (window.top !== window.self) { const template_type = window.C_SETTINGS.meta.page.template_type; const holderEl = document.getElementById('smart_recommend_preview_no_data_placeholder'); SPZ.whenApiDefined(holderEl).then((api) => { api.render({data: { isCart: template_type === 13, isCollection: template_type === 2, isProduct: template_type === 1, isIndex: template_type === 15 }}, true); }); } } }); } initBlockClass(blockEl) { if (!blockEl) return; if (blockEl.parentElement && blockEl.parentElement.offsetWidth === document.body.clientWidth) { blockEl.classList.add('smart_recommend_block_fullscreen'); }; if (isSpecialHeroTheme) { blockEl.classList.add(specialHeroThemeClassName); }; } initItemClass(blockEl) { if (blockEl) { const containerWidth = blockEl.offsetWidth; let itemWidth = ''; if (containerWidth > 780) { itemWidth = '16%'; } else if (containerWidth > 600) { itemWidth = '20%'; } else { itemWidth = '24%'; } const itemStyleEl = document.createElement('style'); itemStyleEl.innerHTML = `.zb-recommend-li-item{ width: ${itemWidth}; }`; document.body.appendChild(itemStyleEl); } } setAction_() { this.registerAction('quickShop', (data) => { const that = this; const product_id = data.args.product_id; const productIndex = data.args.productIndex; const rule_id = data.args.rule_id; const ssp = data.args.ssp; const scm = data.args.scm; const cfb = data.args.cfb; const ifb = data.args.ifb; const modalRender = document.getElementById('smart_recommend_product_modal_render'); if (modalRender) { document.body.appendChild(modalRender); } if (product_id) { this.fetchProductData(product_id).then((res) => { const product = res.products && res.products.length && res.products[0] || {}; product.cfb = cfb; product.ifb = ifb; SPZ.whenApiDefined(modalRender).then((api) => { api.render({product: product, productIndex: productIndex, rule_id: rule_id, ssp: ssp, scm: scm, show_type: that.show_type_}, true).then(() => { const modalEl = document.getElementById('smart_recommend_product_modal'); SPZ.whenApiDefined(modalEl).then((modal) => { that.impressListen('#smart_recommend_product_modal', function(){ that.trackQuickShop({ rule_id: rule_id, product_id: product_id }); }); modal.open(); }); const formEl = document.getElementById('smart_recommend_product_form'); SPZ.whenApiDefined(formEl).then((form) => { form.setProduct(product); }); const variantEl = document.getElementById('smart_recommend_product_variants'); SPZ.whenApiDefined(variantEl).then((variant) => { variant.handleRender(product); }); }); }) }); } }); this.registerAction('handleScroll', (data) => { this.directTo(data.args.rule_id, data.args.direction); }); this.registerAction('handleProductChange', (data) => { const variant = data.args.data.variant; const product = data.args.data.product; const imageRenderEl = document.getElementById('smart_recommend_product_image'); SPZ.whenApiDefined(imageRenderEl).then((api) => { api.render({ variant: variant, product: product }); }); }); this.registerAction('handleAtcSuccess', (detail) => { const data = detail.args; data.data.product = data.data.product || {}; data.data.variant = data.data.variant || {}; const product_id = data.data.product.id; const product_title = data.data.product.title; const variant_id = data.data.variant.id; const price = data.data.variant.price; const rule_id = data.rule_id; const aid = `smart_recommend.${this.show_type_}.${rule_id}`; const ifb = data.data.product.ifb; const cfb = data.data.product.cfb; const ssp = data.ssp; const scm = data.scm; const spm = `smart_recommend_${this.show_type_}.${data.spmIndex}`; const params = { id: product_id, product_id: product_id, number: 1, name: product_title, variant_id: variant_id, childrenId: variant_id, item_price: price, source: 'add_to_cart', _extra: { aid: aid, ifb: ifb, cfb: cfb, scm: scm, spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`, ssp: ssp, } }; this.tranckAddToCart(params); }); this.registerAction('addATCHook', (data) => { const params = data.args; const spm = `smart_recommend_${this.show_type_}.${params.spmIndex}`; this.myInterceptor_ = window.djInterceptors && window.djInterceptors.track.use({ event: 'dj.addToCart', params: { aid: `smart_recommend.${this.show_type_}.` + params.rule_id, ssp: params.ssp, scm: params.scm, cfb: params.cfb, spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`, }, once: true }); }); } tranckAddToCart(detail) { if (window.$) { window.$(document.body).trigger('dj.addToCart', detail); } } fetchRules() { const payload = { show_type: this.show_type_, }; let that = this; if (this.show_type_ === 6) { let line_items = []; return this.fetchCart().then((res) => { if (res && res.cart && res.cart.line_items) { line_items = res.cart.line_items.map((item) => { return { product_id: item.product_id, variant_id: item.variant_id, quantity: item.quantity, price: item.price } }); } payload.line_items = line_items; that.cart_items_ = line_items; return that.fetchRulesRequest(payload); }); } else { if (this.show_type_ === 3) { payload.line_items = [{ product_id: this.product_resource_id_ }]; } else if (this.show_type_ === 4) { payload.collection_id = this.collection_resource_id_; } else if (this.show_type_ === 7) { payload.customer_id = this.customer_id_; } else if (this.show_type_ === 8) { payload.order_id = this.order_id_; } return this.fetchRulesRequest(payload); } } fetchRulesRequest(payload) { return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(function(res){ if(res.ok){ return res.json(); } }); } fetchCart() { return fetch(`/api/cart/cart-select?r=${Math.random().toString(36).slice(-4)}`) .then((res) => { if (res.ok) { return res.json(); } }); } fetchRuleProductList(rule_id) { const payload = { page: 1, limit: 100, fields: ["title", "url", "image", "min_price_variant.price", "min_price_variant.compare_at_price"], rule_id: rule_id, }; if (this.show_type_ === 3) { payload.line_items = [{ product_id: this.product_resource_id_ }]; } else if (this.show_type_ === 4) { payload.collection_id = this.collection_resource_id_; } else if (this.show_type_ === 6) { payload.line_items = this.cart_items_; } else if (this.show_type_ === 7) { payload.customer_id = this.customer_id_; } else if (this.show_type_ === 8) { payload.order_id = this.order_id_; } return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_products", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(function(res){ if(res.ok){ return res.json(); } }).catch(function(err){ console.log(err); }); } fetchProductData(product_id) { return fetch(window.C_SETTINGS.routes.root + "/api/possum/products", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ product_ids: [product_id], fields: [ "images", "options", "min_price_variant", "variants"] }) }).then(function(res){ if(res.ok){ return res.json(); } }).catch(function(err){ console.log(err); const loadingEl = document.getElementById('smart_recommend_loading'); if (loadingEl) { loadingEl.style.display = 'none'; } }); } getStyle(ele, style) { if (!ele) return; if (window.getComputedStyle) { return window.getComputedStyle(ele)[style]; } return ele.currentStyle[style]; } directTo(id, direction) { const scrollElement = document.getElementById(`smart_recommend_rule_ul_${id}`); const blockWidth = parseInt(this.getStyle(scrollElement, 'width')); const scrollLength = (blockWidth * 0.19 - 12) * 5; const scrollPoint = scrollElement.scrollWidth - scrollElement.clientWidth; if (!scrollElement) return; if (direction === 'left') { if (document.dir === 'rtl') { scrollElement.scrollTo({ left: Math.abs(scrollElement.scrollLeft) >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft - scrollLength, behavior: 'smooth' }); return; } scrollElement.scrollTo({ left: Math.max(scrollElement.scrollLeft - scrollLength, 0), behavior: 'smooth' }); } else { if (document.dir === 'rtl') { scrollElement.scrollTo({ left: Math.abs(scrollElement.scrollLeft) >= scrollPoint + 100 ? 0 : scrollElement.scrollLeft + scrollLength, behavior: 'smooth' }); return; } scrollElement.scrollTo({ left: scrollElement.scrollLeft >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft + scrollLength, behavior: 'smooth' }); } } trackRuleImpress(rule) { if (window.sa && window.sa.track) { window.sa.track("plugin_common", { plugin_name: "upsell", event_type: "impressions", rule_id: rule.id, ssp: rule.ssp, scm: rule.scm, show_type: this.show_type_, support_app_block: window.C_SETTINGS.theme.support_app_block }); window.sa.track("module_impressions", { aid: `smart_recommend.${this.show_type_}.${rule.id}`, support_app_block: window.C_SETTINGS.theme.support_app_block }); } } trackQuickShop(data) { window.sa && sa.track && sa.track("plugin_common", { plugin_name: "upsell", event_type: "quick_shop", rule_id: data.rule_id, product_id: data.product_id, show_type: this.show_type_, }); } impressListen(selector, cb) { const el = document.querySelector(selector); const onImpress = (e) => { if (e) { e.stopPropagation(); } cb(); }; if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress) } else if (el) { onImpress(); } } } SPZ.defineElement('spz-custom-smart-block', SpzSmartBlockComponent);

We work directly with manufacturers all over the world to ensure the best quality of our products. We have Quality Control department which help us to keep our promise!

Price is always competitive.

Awesome Customer Service

Amazing products along with High Quality

Here at Zealbonn™ we believe in the effectiveness of our work and the quality of our products. Therefore we offer a 30-day money-back guarantee on every order.

Bring 100% satisfaction shopping experience to every customer.

Need help with something?
Email us at :support@zealbonn.com

General

When can I expect to receive my shipment?

Our an average delivery time of 7-12 days. If you don't see our delivery time when you place your order and are not willing to wait 7-12days for delivery, please contact us. We will be more than happy to send you a full refund. 
(Only for orders with items not shipped)

How can I cancel my order?

If you need to pick up an order you have placed, please email us your orderer or username, so that we can find your order quickly, you must cancel/change the order within 12 hours after placing the order , we will try our best to meet your needs.

Email address of the store: support@zealbonn.com

Our customer service team will get back to you within 12-24 hours. 

What happens if I forget my password?

If you forget your password you can request a new one to be emailed to you by clicking on the ‘Forgotten Password’ link on the sign in page. If this fails to resolve your problem, please contact our customer services department.

Can I check the order information if there is no registered member when placing an order?

Yes, You can sign up for a new membership using the same email address you used to purchase the order, and the order information will be synchronized.

Delivery

Do you charge for delivery?

We have a fixed shipping rate of £6.99 to all destinations worldwide.Free postage on orders over £ 49.99.

How can I track my order?

we will provide a shipping tracking to you in your Shipping Confirmation email. You can use the number and track your order on 17track offical net.

How can I change my Shipping Address?

Orders are only allowed to change address before they are shippe.Please contact us via support@zealbonn.com to request for such changes.

Can I deliver to another address, other than my card billing address?

Yes, it is possible, and you should leave the correct address.But please note that once order ship out,it can't be change anymore.

How is my order shipped?

We can ship the order during regular business days, excluding holidays. but if on Saturday or Sunday, it will be shipped the following Monday.

Super Soft Women's Orthopedic Walking Shoes are specially developed with a three arch support design and a soft sole to keep your feet comfortable, healthy safety, and style on point!

FOOT SCIENTIFIC ARCHES SPECIALTY ORTHOTICS

Is the joint pain making like frustrating? In most cases, the problem is deep-rooted at the base of your body: your feet. Having a weak imbalance can potentially lead to imbalances in the knees, hips, and muscles

 

NANO-FOAM

The important invention used in this pair of sandals is that the sole with nano-foam will transformation to fit and comfort on all feet. So this is the most comfortable type of sandals in the world. 

MEMORY FOAM ARE FANTASTIC FOR SUPPORTING FEET

Memory soles,highly adaptive, able to move with your feet as you walk and absorbing a lot of shocks.

3-ARCH SUPPORT

The Sandal corrects posture and eliminates muscle imbalances by balancing your feet at the perfect angle its original position to realign skeleton positioning.

Simply, slip them on and snug your feet in place around the toe clasp for enhanced support.

It's lightweight; but with a wedge design, you can walk more steadily & stand for hours without any strain or imbalance.

The Women Flowers Comfy Casual Wedges Sandals was designed by our team of podiatrists and orthopedists with one goal in mind: to correct your posture and give your body the best comfort! The Sandal corrects posture and eliminates muscle imbalances by moving angled toes back to their original position to realign skeleton positioning.

Save your time, money, and health from joint and muscle pain by fixing the base of your entire body: your feet. Walkthrough life pain-free and in 100% physical condition.

FEATURES 

Reduces Pain: Eliminate the pain caused by flat feet, poor walking posture and plantar fasciitis.

Posture Correction: Straighten your hips, correct kick knees and muscle imbalance by adjusting the structure of your feet.

Even Pressure Distribution: Our three - arch - support design makes sure your weight is evenly distributed across the toes, arch and ball of your feet at all times.

Did you know that a lot of muscle and joint pain can originate in your feet? Without realizing it, we can compensate for walking errors with an imbalance that affects the whole body! See in the photo!

Also, check out the features that make this shoe a great success and see why it is a great choice to compose several cool looks with maximum comfort.

SIZE CHART:

1.5 34 22
2 35 22.5
3 36 23
4 37 23.5
5 38 24
6 39 24.5
7 40 25
8 41 26
9 42 27
10 43 27.5
11 44 28
12 45 29
13 46 30
14 47 31
15 48 32

Questions About The Product

We believe 100% in our products. This is why we offer a 30-Day Wear Test Guarantee where you can test the shoes and decide for yourself.

Orthopaedic shoes are shoes that have been specifically designed to provide maximum support to the feet and ankles. They benefit people who suffer from foot pain, as well as a range of conditions, which make it difficult and uncomfortable to walk or exercise in regular footwear.

Having supportive footwear helps to alleviate foot pain, increase blood flow, improve posture and reduce the risk of injury. Plus, wearing orthopaedic footwear can prevent foot problems from developing in the future.

Although orthopaedic shoes do help to provide comfort to those with foot pain and certain foot conditions, it is not a replacement for proper medical care where it is needed. If you feel you need medical assistance with your foot pain or are concerned, seek advice from the NHS website or visit your local GP.

Some people rely more heavily on orthotics to help with discomfort and to improve their quality of life, however that doesn't mean they aren't suitable for everyone! Orthotic shoes can also be used as a preventative measure, to ward off foot problems as you get older.

Yes, our shoes are the right size! We understand the importance of shoes that fit, so you can buy with confidence.Please choose your size according to the corresponding foot length on the size chart.

let section_id = '1757407368866'; window.reviewSettings = {}; window.reviewSettings[section_id] = { "sub_title": null, "star_least": "4", "only_featured": false, "with_photo": false, "review_insufficient": "no_reviews", "minimum_comment_num": 5, "fill_strategy": "hide", "layout": "grid", "image_size": "natural", "wall_mobile_num": 2, "wall_pc_num": 4, "limit": 16, "show_product": false, "hide_review_section": true, "title": "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') || '1757407368866'; 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 = '1' == 1; this.isCollectionPage = '1' == 2; this.isCartPage = '1' == 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 ? '51bc98ff-b434-4cfa-840d-d73ea1b129e7' : 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 ? '51bc98ff-b434-4cfa-840d-d73ea1b129e7' : 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-1757407368866`); 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') || '1757407368866'; 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) (function() { const TAG = 'spz-custom-new-revue'; class SpzCustomNewRevue extends SPZ.BaseElement { constructor(element) { super(element); this.config_ = null; this.loading_ = false; this.accent_color = this.element.getAttribute('accent-color'); this.sectionId = this.element.getAttribute('section-id'); this.prefix = this.element.getAttribute('prefix'); } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.form_ = SPZCore.Dom.scopedQuerySelector( this.element, 'form' ); this.hasShowLengthInputs_ = SPZCore.Dom.scopedQuerySelectorAll( this.form_, '[showlength]' ); [...this.hasShowLengthInputs_].forEach(item => { const countRecordDom = SPZCore.Dom.scopedQuerySelector( this.form_, `#${item.id} ~ div[type="count-record"]` ); if (!countRecordDom) { console.error(`Cannot find count record DOM element for input ${item.id}`); return; } item.addEventListener('input', (e) => { countRecordDom.innerText = `${e.target.value.length}/3000`; }); }); this.setupAction_(); this.getRevueConfigData_(); } setupAction_() { this.registerAction('submitForm', async(invocation) => { if (this.loading_) { return; } this.loading_ = true; const formData = Object.entries(invocation.args.data).reduce((acc, [key, value]) => { if (key === 'star' || key === 'type') { acc[key] = Number(value[0]); } else { acc[key] = value[0]; } return acc; }, {}); try { const data = await fetch('/api/comment', { method: "post", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData) }).then(res => res.json()); if (data.state === 0) { this.triggerEvent_('submitSuccess', { panelId: 'with_photo', message: '' }); return; } throw new Error(data.msg); } catch(e) { e = await e; this.triggerEvent_('submitError', {data: e.message}); } finally { this.loading_ = false; } }); this.registerAction('renderFormStar', async(invocation) => { this.triggerEvent_('rerenderFormStar', { star_color: this.starColor_ }); }) } mountCallback() { } getRevueConfigData_ = () => { fetch('/api/comment-config') .then(res => res.json()) .then(data => { this.config_ = data.data; // anonymous_permission 是否支持匿名 if (!this.config_.anonymous_permission) { const anonymousInput = this.form_.querySelector(`#${this.prefix}-revue-anonymous-${this.sectionId}`); anonymousInput.value = 'false'; anonymousInput.parentNode.classList.add('hidden', 'anonymous-permission-hidden'); } this.starColor_ = this.config_.star_color; if(this.accent_color && this.accent_color != 'null'){ this.starColor_ = this.accent_color; } // render star // star_color 星星颜色 const starEl = this.form_.querySelector(`#${this.prefix}-revue_write_modal_star-${this.sectionId}`); if (starEl) { SPZ.whenApiDefined(starEl).then((api) => { api.render({ star_color: this.starColor_ }); }); } }); } 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, SpzCustomNewRevue); })() (function() { const TAG = 'spz-custom-revue-product-info-script'; class SpzCustomRevueProductInfoScript extends SPZ.BaseElement { constructor(element) { super(element); /** @private {!Element} */ this.product_id = null; } async buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.product_id = this.getProductId_(); this.triggerEvent_('init', { product_id: this.product_id }); try { const data = await this.getProductInfo_(); if (data?.data?.product) { this.triggerEvent_('finish', data.data.product); } } catch (error) { console.error('Failed to fetch product info:', error); // Handle the error appropriately } } getProductId_ = () => { return window.SHOPLAZZA.meta.page.resource_id; } async getProductInfo_() { if (!this.product_id) { console.error('Product ID is undefined or null'); return null; } try { const response = await fetch(`/api/products/${this.product_id}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Error fetching product info:', error); throw error; // Rethrow to be caught by the caller } } 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.LOGIC; } } SPZ.defineElement(TAG, SpzCustomRevueProductInfoScript); })() 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) (function() { const TAG = 'spz-custom-new-revue-files-show'; class SpzCustomNewRevueFilesShow extends SPZ.BaseElement { constructor(element) { super(element); /** @private {!Element} */ this.files_ = [] } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.setupAction_(); this.element.setAttribute('nums', this.files_.length); } mountCallback() { } setupAction_() { this.registerAction('upload', async(invocation) => { const uploadFileList = invocation.args?.data || []; uploadFileList.forEach(file => { if(this.files_.some(item => item.url === file.url)) return this.files_.push(file); }) this.doRender_(); }); this.registerAction('delete', async(invocation) => { this.files_ = this.files_.filter((_, index) => index !== invocation.args.index); this.doRender_(); this.triggerEvent_('delete', { count: this.files_.length, files: this.files_ }); }); this.registerAction('preview', async(invocation) => { let previewFileData = this.files_[invocation.args.index]; if (previewFileData.type === 'video') { previewFileData = {...this.parseVideoSrc_(previewFileData.url), ...previewFileData}; } this.triggerEvent_('preview', previewFileData); }); this.registerAction('clear', async(invocation) => { this.files_ = []; this.doRender_(); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } parseVideoSrc_(src) { const url = new URL(src); const params = new URLSearchParams(url.search); return { videoUrl: url.origin + url.pathname, mediaType: params.get('media_type'), vID: params.get('vID'), mp4: params.get('mp4'), hls: params.get('hls') }; } doRender_ = () => { this.triggerEvent_('setInputValue', { data: this.files_ .map(file => { const url = file.type === 'video' ? file.poster : file.url; return `${url}?width=${file.width}&height=${file.height}`; }) .join(',') }); this.element.setAttribute('nums', this.files_.length); return this.templates_ .findAndRenderTemplate(this.element, { files: this.files_ }) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }) } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomNewRevueFilesShow); })()
const isSpecialHeroTheme = window.SHOPLAZZA?.theme?.merchant_theme_name == 'Hero' && window.SHOPLAZZA?.theme?.merchant_theme_c_version == '2.2.19'; const specialHeroThemeClassName = 'hero_2_2_19_smart_recommend_block'; class SpzSmartBlockComponent extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.container_ = null; this.i18n_ = {}; this.config_ = {}; this.show_type_ = 3; this.product_resource_id_ = ''; this.collection_resource_id_ = ''; this.cart_items_ = []; this.customer_id_ = ''; this.order_id_ = ''; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { const template_type = window.C_SETTINGS.meta.page.template_type; if (template_type === 1) { this.show_type_ = 3; this.product_resource_id_ = window.C_SETTINGS.meta.page.resource_id; } else if (template_type === 2) { this.show_type_ = 4; this.collection_resource_id_ = window.C_SETTINGS.meta.page.resource_id; } else if (template_type === 15){ this.show_type_ = 5; } else if (template_type === 13){ this.show_type_ = 6; } else if (template_type === 20){ this.show_type_ = 7; this.customer_id_ = window.C_SETTINGS.customer.customer_id; } else if (template_type === 35){ this.show_type_ = 8; this.order_id_ = window.location.pathname.split('/').pop(); } this.templates_ = SPZServices.templatesForDoc(this.element); this.setAction_(); } mountCallback() { const that = this; const themeName = window.C_SETTINGS.theme.merchant_theme_name; const isGeek = /Geek/.test(themeName); this.fetchRules().then((res) => { if (res && res.rules && res.rules.length) { const blockEl = document.getElementById('smart_recommend_block'); this.initBlockClass(blockEl); this.initItemClass(blockEl); SPZ.whenApiDefined(blockEl).then((api) => { api.render({data: res}, true).then(() => { if (isGeek && that.show_type_ === 6) { blockEl.querySelector('.plugin_container_wrpper').style.padding = '30px 0'; } const recommendStyle = document.createElement('style'); recommendStyle.innerHTML = ` .plugin__recommend_container,.app-recommend-card { display: none !important; } `; document.head.appendChild(recommendStyle); const fetchList = []; res.rules.forEach((rule) => { fetchList.push(this.fetchRuleProductList(rule.id)); }); const fetchAll = Promise.all(fetchList); fetchAll.then((p_res) => { res.rules.forEach((rule, index) => { rule.products = p_res[index] && p_res[index].products; if (rule.products && rule.products.length) { const modalRender = document.getElementById('smart_recommend_js_root'); const $dest = document.getElementById('cart'); const isLifeStyle = /Life.*Style/.test(window.C_SETTINGS.theme.merchant_theme_name); if (modalRender && isLifeStyle && $dest.clientWidth > 767) { modalRender.classList.add('zb-mt-[-180px]') } } const ruleEl = document.getElementById('smart_recommend_rule_' + rule.id); SPZ.whenApiDefined(ruleEl).then((api) => { api.render({data: rule}, true).then(() => { that.impressListen(`#smart_recommend_rule_ul_${rule.id}`, function(){ that.trackRuleImpress(rule); }); const btnElList = document.querySelectorAll(`#smart_recommend_rule_ul_${rule.id} button`); btnElList.forEach((btnEl) => { if (btnEl && rule.config && rule.config.quick_shop_button_bg_color && rule.config.quick_shop_button_text_color) { btnEl.style.backgroundColor = rule.config.quick_shop_button_bg_color; btnEl.style.color = rule.config.quick_shop_button_text_color; } }); if (isSpecialHeroTheme) { ruleEl.querySelectorAll(`.smart_recommend_title`).forEach(dom=>{ dom.classList.add('type-title-font-family'); }); document.querySelectorAll(`.${specialHeroThemeClassName} #smart_recommend_rule_ul_${rule.id} .zb-recommend-price-line-through .money`).forEach(dom=>{ dom.classList.add('type-body-font-family'); }); }; }); }); }); }); }) }) } else { if (window.top !== window.self) { const template_type = window.C_SETTINGS.meta.page.template_type; const holderEl = document.getElementById('smart_recommend_preview_no_data_placeholder'); SPZ.whenApiDefined(holderEl).then((api) => { api.render({data: { isCart: template_type === 13, isCollection: template_type === 2, isProduct: template_type === 1, isIndex: template_type === 15 }}, true); }); } } }); } initBlockClass(blockEl) { if (!blockEl) return; if (blockEl.parentElement && blockEl.parentElement.offsetWidth === document.body.clientWidth) { blockEl.classList.add('smart_recommend_block_fullscreen'); }; if (isSpecialHeroTheme) { blockEl.classList.add(specialHeroThemeClassName); }; } initItemClass(blockEl) { if (blockEl) { const containerWidth = blockEl.offsetWidth; let itemWidth = ''; if (containerWidth > 780) { itemWidth = '16%'; } else if (containerWidth > 600) { itemWidth = '20%'; } else { itemWidth = '24%'; } const itemStyleEl = document.createElement('style'); itemStyleEl.innerHTML = `.zb-recommend-li-item{ width: ${itemWidth}; }`; document.body.appendChild(itemStyleEl); } } setAction_() { this.registerAction('quickShop', (data) => { const that = this; const product_id = data.args.product_id; const productIndex = data.args.productIndex; const rule_id = data.args.rule_id; const ssp = data.args.ssp; const scm = data.args.scm; const cfb = data.args.cfb; const ifb = data.args.ifb; const modalRender = document.getElementById('smart_recommend_product_modal_render'); if (modalRender) { document.body.appendChild(modalRender); } if (product_id) { this.fetchProductData(product_id).then((res) => { const product = res.products && res.products.length && res.products[0] || {}; product.cfb = cfb; product.ifb = ifb; SPZ.whenApiDefined(modalRender).then((api) => { api.render({product: product, productIndex: productIndex, rule_id: rule_id, ssp: ssp, scm: scm, show_type: that.show_type_}, true).then(() => { const modalEl = document.getElementById('smart_recommend_product_modal'); SPZ.whenApiDefined(modalEl).then((modal) => { that.impressListen('#smart_recommend_product_modal', function(){ that.trackQuickShop({ rule_id: rule_id, product_id: product_id }); }); modal.open(); }); const formEl = document.getElementById('smart_recommend_product_form'); SPZ.whenApiDefined(formEl).then((form) => { form.setProduct(product); }); const variantEl = document.getElementById('smart_recommend_product_variants'); SPZ.whenApiDefined(variantEl).then((variant) => { variant.handleRender(product); }); }); }) }); } }); this.registerAction('handleScroll', (data) => { this.directTo(data.args.rule_id, data.args.direction); }); this.registerAction('handleProductChange', (data) => { const variant = data.args.data.variant; const product = data.args.data.product; const imageRenderEl = document.getElementById('smart_recommend_product_image'); SPZ.whenApiDefined(imageRenderEl).then((api) => { api.render({ variant: variant, product: product }); }); }); this.registerAction('handleAtcSuccess', (detail) => { const data = detail.args; data.data.product = data.data.product || {}; data.data.variant = data.data.variant || {}; const product_id = data.data.product.id; const product_title = data.data.product.title; const variant_id = data.data.variant.id; const price = data.data.variant.price; const rule_id = data.rule_id; const aid = `smart_recommend.${this.show_type_}.${rule_id}`; const ifb = data.data.product.ifb; const cfb = data.data.product.cfb; const ssp = data.ssp; const scm = data.scm; const spm = `smart_recommend_${this.show_type_}.${data.spmIndex}`; const params = { id: product_id, product_id: product_id, number: 1, name: product_title, variant_id: variant_id, childrenId: variant_id, item_price: price, source: 'add_to_cart', _extra: { aid: aid, ifb: ifb, cfb: cfb, scm: scm, spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`, ssp: ssp, } }; this.tranckAddToCart(params); }); this.registerAction('addATCHook', (data) => { const params = data.args; const spm = `smart_recommend_${this.show_type_}.${params.spmIndex}`; this.myInterceptor_ = window.djInterceptors && window.djInterceptors.track.use({ event: 'dj.addToCart', params: { aid: `smart_recommend.${this.show_type_}.` + params.rule_id, ssp: params.ssp, scm: params.scm, cfb: params.cfb, spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`, }, once: true }); }); } tranckAddToCart(detail) { if (window.$) { window.$(document.body).trigger('dj.addToCart', detail); } } fetchRules() { const payload = { show_type: this.show_type_, }; let that = this; if (this.show_type_ === 6) { let line_items = []; return this.fetchCart().then((res) => { if (res && res.cart && res.cart.line_items) { line_items = res.cart.line_items.map((item) => { return { product_id: item.product_id, variant_id: item.variant_id, quantity: item.quantity, price: item.price } }); } payload.line_items = line_items; that.cart_items_ = line_items; return that.fetchRulesRequest(payload); }); } else { if (this.show_type_ === 3) { payload.line_items = [{ product_id: this.product_resource_id_ }]; } else if (this.show_type_ === 4) { payload.collection_id = this.collection_resource_id_; } else if (this.show_type_ === 7) { payload.customer_id = this.customer_id_; } else if (this.show_type_ === 8) { payload.order_id = this.order_id_; } return this.fetchRulesRequest(payload); } } fetchRulesRequest(payload) { return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(function(res){ if(res.ok){ return res.json(); } }); } fetchCart() { return fetch(`/api/cart/cart-select?r=${Math.random().toString(36).slice(-4)}`) .then((res) => { if (res.ok) { return res.json(); } }); } fetchRuleProductList(rule_id) { const payload = { page: 1, limit: 100, fields: ["title", "url", "image", "min_price_variant.price", "min_price_variant.compare_at_price"], rule_id: rule_id, }; if (this.show_type_ === 3) { payload.line_items = [{ product_id: this.product_resource_id_ }]; } else if (this.show_type_ === 4) { payload.collection_id = this.collection_resource_id_; } else if (this.show_type_ === 6) { payload.line_items = this.cart_items_; } else if (this.show_type_ === 7) { payload.customer_id = this.customer_id_; } else if (this.show_type_ === 8) { payload.order_id = this.order_id_; } return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_products", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(function(res){ if(res.ok){ return res.json(); } }).catch(function(err){ console.log(err); }); } fetchProductData(product_id) { return fetch(window.C_SETTINGS.routes.root + "/api/possum/products", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ product_ids: [product_id], fields: [ "images", "options", "min_price_variant", "variants"] }) }).then(function(res){ if(res.ok){ return res.json(); } }).catch(function(err){ console.log(err); const loadingEl = document.getElementById('smart_recommend_loading'); if (loadingEl) { loadingEl.style.display = 'none'; } }); } getStyle(ele, style) { if (!ele) return; if (window.getComputedStyle) { return window.getComputedStyle(ele)[style]; } return ele.currentStyle[style]; } directTo(id, direction) { const scrollElement = document.getElementById(`smart_recommend_rule_ul_${id}`); const blockWidth = parseInt(this.getStyle(scrollElement, 'width')); const scrollLength = (blockWidth * 0.19 - 12) * 5; const scrollPoint = scrollElement.scrollWidth - scrollElement.clientWidth; if (!scrollElement) return; if (direction === 'left') { if (document.dir === 'rtl') { scrollElement.scrollTo({ left: Math.abs(scrollElement.scrollLeft) >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft - scrollLength, behavior: 'smooth' }); return; } scrollElement.scrollTo({ left: Math.max(scrollElement.scrollLeft - scrollLength, 0), behavior: 'smooth' }); } else { if (document.dir === 'rtl') { scrollElement.scrollTo({ left: Math.abs(scrollElement.scrollLeft) >= scrollPoint + 100 ? 0 : scrollElement.scrollLeft + scrollLength, behavior: 'smooth' }); return; } scrollElement.scrollTo({ left: scrollElement.scrollLeft >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft + scrollLength, behavior: 'smooth' }); } } trackRuleImpress(rule) { if (window.sa && window.sa.track) { window.sa.track("plugin_common", { plugin_name: "upsell", event_type: "impressions", rule_id: rule.id, ssp: rule.ssp, scm: rule.scm, show_type: this.show_type_, support_app_block: window.C_SETTINGS.theme.support_app_block }); window.sa.track("module_impressions", { aid: `smart_recommend.${this.show_type_}.${rule.id}`, support_app_block: window.C_SETTINGS.theme.support_app_block }); } } trackQuickShop(data) { window.sa && sa.track && sa.track("plugin_common", { plugin_name: "upsell", event_type: "quick_shop", rule_id: data.rule_id, product_id: data.product_id, show_type: this.show_type_, }); } impressListen(selector, cb) { const el = document.querySelector(selector); const onImpress = (e) => { if (e) { e.stopPropagation(); } cb(); }; if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress) } else if (el) { onImpress(); } } } SPZ.defineElement('spz-custom-smart-block', SpzSmartBlockComponent);

Recommended Viewing Categories

More Women's Orthopedic Shoes

More Men's Orthopedic Shoes

Wide Shoes/Diabetic Shoes (Unisex)

Shoes Acc

More Hearing Aids

Sign up and save

Entice customers to sign up for your mailing list with discounts or exclusive offers. Include an image for extra impact.
Thanks for subscribing
Optional button