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;
// --- 工具函数 ---
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() {
return window.matchMedia(`(min-width: ${BREAKPOINT}px)`).matches;
}
// --- 主题配置 ---
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: '#shoplaza-section-header .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');
// 插件位置纠正配置:当商家将插件插入到不可见的区域时,自动迁移到正确的 DOM 位置
// pc / mobile 分别指定 PC 端和移动端的目标父容器选择器,未配置则不做迁移
const PLUGIN_RELOCATION_CONFIG = resolveThemeValue({
reformia: {
pc: '.header-layout .header__actions',
mobile: '.header-layout .header__actions',
},
}, null);
// --- 组件 ---
class SpzCustomSmartSearchLocation extends SPZ.BaseElement {
constructor(element) {
super(element);
this.outsideCarouselIndex = 0;
this.insideCarouselIndex = 0;
this.searchItemType = 'icon';
this._originalSearchWrapParent = null;
this._skipMobileInit = false;
}
static deferredMount() {
return false;
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
buildCallback() {
this.bindResizeListener();
this.registerActions();
}
mountCallback(){
this.init();
}
unmountCallback(){
this.unbindResizeListener();
this.unregisterActions();
}
// --- 元素查找(支持 DocumentFragment 上下文)---
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-77');
}
getOutsideItemEl() {
return this.resolveBlockElement('.app-smart-search-outside-item', 'app-smart-search-outside-item-77');
}
// --- 插件位置纠正 ---
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-77');
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);
}
// --- 初始化 ---
init() {
this.relocatePlugin();
this.applySearchIconClass();
this.adjustLifestyleIcon();
if (this.searchItemType === 'input') {
this.initInputMode();
return;
}
// icon 模式
this.initIconMode();
if (isDesktop()) return;
if (this._skipMobileInit) return;
// icon 模式下的移动端额外处理(处理主题特定的 header 布局)
if (!window.__isLoadAppSmartSearch__) {
this.initMobileSmartSearch();
if (window.self === window.top) {
window.__isLoadAppSmartSearch__ = true;
}
}
}
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;
const container = this.getBlockContainer();
if (!container) return;
const alreadyMoved = !!document.querySelector(
'.header__wrapper .container .row.header>div>#app-smart-product-search-container-77'
);
if (alreadyMoved) return;
const headerDivs = document.querySelectorAll('.header__wrapper .container .row.header>div');
const lastDiv = headerDivs[headerDivs.length - 1];
lastDiv.appendChild(container);
}
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()) {
// PC 端:确保 searchWrap 在原始位置并显示
if (mobileContainer) mobileContainer.style.display = 'none';
if (searchWrap && this._originalSearchWrapParent) {
// 如果 searchWrap 被移到了移动端容器,移回原始位置
if (mobileContainer && mobileContainer.contains(searchWrap)) {
this._originalSearchWrapParent.appendChild(searchWrap);
}
}
if (pcContainer) pcContainer.style.display = 'block';
return;
}
if (templateName === 'search') {
this._skipMobileInit = true;
return;
}
// 移动端:隐藏当前实例的 PC 容器
if (pcContainer) pcContainer.style.display = 'none';
this.ensureMobileSearchContainer();
const mobileContainerAfterEnsure = document.querySelector('.smart-search-mobile-container');
if (!mobileContainerAfterEnsure) return;
// 检查移动端容器是否已经有其他实例的内容
const existingWrap = mobileContainerAfterEnsure.querySelector('.app-smart-product-search-wrap');
if (existingWrap && existingWrap !== searchWrap) {
// 已有其他实例,当前实例不需要移动,保持隐藏即可
return;
}
// 将当前实例的 searchWrap 移到移动端容器
if (searchWrap && !mobileContainerAfterEnsure.contains(searchWrap)) {
mobileContainerAfterEnsure.appendChild(searchWrap);
}
mobileContainerAfterEnsure.style.display = '';
}
ensureMobileSearchContainer() {
if (document.querySelector('.smart-search-mobile-container')) return;
const container = document.createElement('div');
container.classList.add('smart-search-mobile-container');
container.classList.add('smart-search-mobile-container-' + THEME_NAME.toLocaleLowerCase());
document.querySelector(HEADER_SELECTOR).appendChild(container);
}
initIconMode() {
document.querySelectorAll('.app-smart-icon-search-large').forEach(el => {
el.style.display = 'flex';
});
}
initMobileSmartSearch() {
if (this.hasMobilePluginParent()) {
this.showMobileSmartSearch();
} else {
this.addMobileSmartSearch();
}
}
// --- 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);
});
}
// --- 搜索输入 & 提交 ---
onSearchInputChange(keyword) {
const display = (!keyword || !keyword.length) ? 'block' : 'none';
document.querySelectorAll('.hot-words-carousel-inner-container').forEach(el => {
el.style.display = display;
});
}
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) {
this.getSmartSearchEl().then((ljsSearch) => {
if (!ljsSearch) return;
try {
ljsSearch.handleSearchSubmit_({ value });
} catch (e) {
if (retryCount < 3) {
this.executeSearch(value, retryCount + 1);
return;
}
const searchStr = value[0] || '';
const searchResult = ljsSearch.setThinkSearchData_(searchStr);
ljsSearch.afterSearching({
query: searchResult.query,
url: `${SEARCH_URL}?q=${searchStr}`,
queryType: searchResult.queryType,
});
}
});
}
// --- 搜索项类型 ---
fetchAndApplySearchItemType() {
this.getOutsideItemEl().then((outsideItem) => {
if (!outsideItem) return;
const type = outsideItem.getData()?.search_item_type;
this.searchItemType = type || this.searchItemType;
this.init();
});
}
// --- 热词 ---
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 });
});
});
}
// --- 底纹词工具方法 ---
// 将 find_keywords(字符串数组)转换为统一的关键词对象格式
// 优先使用 find_keywords,兜底使用 search_keywords
normalizeOutsideKeywords(findKeywords, searchKeywords) {
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 || [];
}
// 规范化关键词项的 URL
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 || [];
// 外部和内部 carousel 都使用相同逻辑:优先 find_keywords,兜底 search_keywords
const keywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords);
const currentItem = this.normalizeKeywordUrl(keywords[index] || null);
this.getSmartSearchEl().then((ljsSearch) => {
if (!ljsSearch) return;
if (currentItem) {
ljsSearch.handleHotKeyword_({
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 carouselKeywords = this.normalizeOutsideKeywords(findKeywords, searchKeywords);
return {
...apiData,
search_keywords: carouselKeywords,
outsideCarouselIndex: this.outsideCarouselIndex,
};
});
}
// --- 窗口监听 ---
bindResizeListener() {
window.removeEventListener('resize', window.smartSearchResizeCallback);
window.smartSearchResizeCallback = SPZCore.Types.debounce(
this.win,
() => {
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',
];
actionNames.forEach((name) => {
this.registerAction(name, () => {});
});
}
// --- 移动端布局:插件父容器模式 ---
hasMobilePluginParent() {
// reformia 使用 relocatePlugin 统一处理,不走 showMobileSmartSearch
return !['geek', 'flash', 'boost', 'reformia'].includes(THEME_NAME.toLocaleLowerCase());
}
showMobileSmartSearch() {
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';
}
}
}
isSmartSearchElement(el) {
return (
el.classList.contains(SEARCH_CONTAINER_CLASS) ||
el.querySelectorAll(`.${SEARCH_CONTAINER_CLASS}`).length > 0
);
}
// --- 移动端布局:图标插入模式 ---
addMobileSmartSearch() {
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);
}
}
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-77';
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);
All Diamond Painting Mask As Low As £3
3pcs £12; 6pcs £21; 9pcs £27
Mask
1 Product
Sorry, there are no products in this collection.