See all offers
  • Welcome to our store Welcome to our store Shop now
  • Welcome to our store Welcome to our store Shop now
Welcome to our store Shop now
Welcome to our store Shop now
Welcome to our store Shop now
Welcome to our store Shop now
See all offers (2)
See all offers (2)

Petonly

  • Home
  • Shop
    • Food Hot
    • Beds
    • Clothe sale
    • Toys
    • Shampoo
    • Collar
    Save $100.00

    Inch Large Cat Tree Cream White

    $239.00 $339.00 Save $100.00
    Save $100.00

    Enclosed summer wooden capsule space capsule cat house

    $299.00 $399.00 Save $100.00
    Save $100.00

    Wood Cat Tree for Large Cats up to 22 lb

    $239.00 $339.00 Save $100.00
    Save $100.00

    Summer double storey cat house cat villa

    $299.00 $399.00 Save $100.00
    Save $41.00

    Beach hoody

    $68.00 $109.00 Save $41.00
    Save $28.00

    Hoodie

    $108.00 $136.00 Save $28.00
    Save $30.00

    Zip hoody

    $68.00 $98.00 Save $30.00
    Save $10.00

    Little Cupcake

    $29.00 $39.00 Save $10.00
    Save $10.00

    Happy Tempura

    $29.00 $39.00 Save $10.00

    Puppy Toys Small Rope Balls

    $11.99
    Save $2.28

    Dog Toys for Aggressive Chewers

    $11.70 $13.98 Save $2.28
    Food Hot
    • food Hot
    Beds
    Clothe sale
    Toys
    Shampoo
    Collar
  • Feature
    Collection Page
    • Collection List 01
    • Collection List 02
    • Collection List 03
    • Collection List 04
    • Collection List 05
    • Left Filter
    • Top Filter
    • Subcollection
    Product Page
    • Trust-building Plugin
    • Shopping Ambience Plugin
    • Product swatch
    • Description Tab Panel
    • Description Accordion
    • Product Reviews
    Cart Page
    • Shopping Ambience Plugin
    Blog
    • Featured product
  • Journal
  • FAQ
  • More links
    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-13'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-13'); }, }; // --- 主题配置 --- 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-13'); 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-13'; 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);
    Log in Create an account

    Petonly

    • Home
    • Shop
      Shop
      • Food Hot
        Food Hot
      • Beds
      • Clothe sale
      • Toys
      • Shampoo
      • Collar
    • Feature
      Feature
      • Collection Page
        Collection Page
        • Collection List 01
        • Collection List 02
        • Collection List 03
        • Collection List 04
        • Collection List 05
        • Left Filter
        • Top Filter
        • Subcollection
      • Product Page
        Product Page
        • Trust-building Plugin
        • Shopping Ambience Plugin
        • Product swatch
        • Description Tab Panel
        • Description Accordion
        • Product Reviews
      • Cart Page
        Cart Page
        • Shopping Ambience Plugin
      • Blog
        Blog
        • Featured product
    • Journal
    • FAQ
    • 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-997'); }, getOutsideItemEl() { return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-997'); }, }; // --- 主题配置 --- 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-997'); 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-997'; 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);
    Log in
    Home / My account

    Reset password with email

    Email is required. Enter a valid email address
    Verification code is invalid
    Password is required. Password must be between 6-16 characters long.
    Password is required. Password must be between 6-16 characters long. Your password and confirmation password do not match
    Have not get verification code?
    USEFUL LINKS
    Term of Service Contact Us Privacy Service About Us
    MY ACCOUNT
    Term of Service Contact Us Privacy Service About Us
    CONTACT
    Looking for collaboration?
    Email:email@example.com
    Visit our local store
    TEL:18888888888
    SAVE $20
    Subscribe to our newsletter and get $20 off your first purchase
    Please fill in this field
    Please enter a valid email address
    Thanks for subscribing
    USEFUL LINKS
    Term of Service Contact Us Privacy Service About Us
    MY ACCOUNT
    Term of Service Contact Us Privacy Service About Us
    CONTACT
    Looking for collaboration?
    Email:email@example.com
    Visit our local store
    TEL:18888888888
    SAVE $20
    Subscribe to our newsletter and get $20 off your first purchase
    Please fill in this field
    Please enter a valid email address
    Thanks for subscribing
    © 2026 Petonly

    10% off your first order1

    Thank you for your subscription, you can get $20 off your first purchase
    Enter a discount code