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-918');
},
getOutsideItemEl() {
return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-918');
},
};
// --- 主题配置 ---
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-918');
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-918';
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);
Get 15%OFF, Discount Code : Happy15
Embroidery Kits
0 Product
Sorry, there are no products in this collection.