New Products Daily

Worldwide Shipping from Europe

Premium Brands Across All Categories

New Products Daily

Worldwide Shipping from Europe

Premium Brands Across All Categories

const template_name = SHOPLAZZA?.meta?.page?.template_name || ''; const SEARCH_URL = '/search'; const TAG = 'spz-custom-smart-search-location'; const SMART_PRODUCR_SEARCH_WRAP_CLASSNAME = 'app-smart-product-search-container'; const THEME_NAME = window.SHOPLAZZA.theme.merchant_theme_name.replace(/ /g,''); const BREAKPOINT = 960; const DELAY = 300; function diffThemeName(themeNameA, themeNameB){ return themeNameA.toLocaleLowerCase().includes(themeNameB.toLocaleLowerCase()) } const HEADER_DOM_MAP = { 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' } let HEADER_DOM = 'header'; Object.keys(HEADER_DOM_MAP) .map(themeName=>{ if (diffThemeName(THEME_NAME, themeName)) { HEADER_DOM = HEADER_DOM_MAP[themeName]; } }) const SEARCH_ICON_CLASS_MAP = { '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', }; let SEARCH_ICON_CLASS = 'app-smart-icon-search-large-default'; Object.keys(SEARCH_ICON_CLASS_MAP) .map(themeName=>{ if (diffThemeName(THEME_NAME, themeName)) { SEARCH_ICON_CLASS = SEARCH_ICON_CLASS_MAP[themeName]; } }) class SpzCustomSmartSearchLocation extends SPZ.BaseElement { constructor(element) { super(element); this.mobileHeaderPluginParentEle = null; this.outsideCarouselIndex = 0; this.insideCarouselIndex = 0; this.searchItemType = 'icon'; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.init(); this.listenerResize(); this.initRegisterActions(); } addIconClass(){ document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>{ e.classList.add(SEARCH_ICON_CLASS) }); } moveIcon(){ if (!diffThemeName(THEME_NAME, 'lifestyle')) return; if (this.searchItemType === 'input') return; if (this.isDesktop()) return; const smart_search_dom = document.querySelector('#app-smart-product-search-container-45'); if (!smart_search_dom) return; const hasMovedIcon = !!document.querySelector('.header__wrapper .container .row.header>div>#app-smart-product-search-container-45'); if (hasMovedIcon) return; const headerDivList = document.querySelectorAll('.header__wrapper .container .row.header>div'); const iconBoxDom = headerDivList[headerDivList.length-1] iconBoxDom.appendChild(smart_search_dom, iconBoxDom.firstChild); } init() { this.addIconClass(); this.moveIcon(); if ( this.searchItemType === 'input' ) { document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>e.style.display = 'none'); const mobileSmartSearchDom = document.querySelector(`.smart-search-mobile-container .app-smart-product-search-wrap`); if ( this.isDesktop() ) { document.querySelector(`#app-smart-product-search-container-45`).style="display: block"; if (mobileSmartSearchDom) { document.querySelector(`#app-smart-product-search-container-45`).appendChild(mobileSmartSearchDom); } }else{ if( template_name=='search' ) return; if (!document.querySelector(`.smart-search-mobile-container`)) { const appSmartSearchContainer = document.createElement('div'); appSmartSearchContainer.classList.add('smart-search-mobile-container'); appSmartSearchContainer.classList.add('smart-search-mobile-container-'+THEME_NAME.toLocaleLowerCase()); document.querySelector(HEADER_DOM).appendChild(appSmartSearchContainer); } if (!mobileSmartSearchDom) { document.querySelector(`.smart-search-mobile-container`).appendChild( document.querySelector(`.app-smart-product-search-wrap`) ) } } }else{ document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>e.style.display = 'flex'); } // PC-end not load if (this.isDesktop()) { return; } if (!window.__isLoadAppSmartSearch__) { this.initSmartSearch(); console.log('__isLoadAppSmartSearch__'); } // B-end must reload if (window.self === window.top && !window.__isLoadAppSmartSearch__) { window.__isLoadAppSmartSearch__ = true; } } initSmartSearch() { if (this.hasMobileUpperRightPlugin()) { this.showMobileSmartSearch(); } else { this.addMobileSmartSearch(); } } initRegisterActions(){ this.registOnSearchInputChange(); this.registOnSearchFormSubmit(); this.registOnOutsideCarouselIndexChange(); this.registOnInsideCarouselIndexChange(); this.registGetSearchItemType(); this.registGenerateHotKeywordList(); this.registerAction('onTapHotWord',(invocation)=>{ this.onTapHotWord(invocation.args.type); }); } registOnSearchInputChange(){ this.registerAction('onSearchInputChange',(invocation)=>{ const keyword = invocation.args.keyword; if (keyword === null || !keyword.length) { document.querySelectorAll('.hot-words-carousel-inner-container').forEach(e=>{ e.style='display: block'; }); } else { document.querySelectorAll('.hot-words-carousel-inner-container').forEach(e=>{ e.style='display: none'; }); } }) } registOnSearchFormSubmit(){ this.registerAction('onSearchFormSubmit',(invocation)=>{ const event = invocation.args.event; const keywordArray = event.q || []; const keyword = keywordArray[0]; if (keyword!==null && keyword.length) { this.handleSearchSubmit_(keywordArray,1); } else { this.onTapHotWord('inside') } }) } handleSearchSubmit_(value, retryNum){ SPZ.whenApiDefined(document.getElementById('app-smart-search-45')) .then((ljsSearch) => { try{ ljsSearch.handleSearchSubmit_({ value: value }) }catch(e){ console.log('catch error',retryNum) if( 3 > retryNum ){ this.handleSearchSubmit_(value, retryNum + 1); return; } const searchStr = value[0] || ''; const searchResult = ljsSearch.setThinkSearchData_(searchStr); ljsSearch.afterSearching({ query: searchResult.query, url: `${SEARCH_URL}?q=${searchStr}`, queryType: searchResult.queryType, }) } }) } registOnOutsideCarouselIndexChange(){ this.registerAction('onOutsideCarouselIndexChange',(invocation)=>{ this.outsideCarouselIndex = invocation.args.index || 0; }) } registOnInsideCarouselIndexChange(){ this.registerAction('onInsideCarouselIndexChange',(invocation)=>{ this.insideCarouselIndex = invocation.args.index || 0; }) } registGetSearchItemType(searchItemType){ this.registerAction('getSearchItemType',(invocation)=>{ SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-45')) .then((appSmartSearchOutsideItem) => { const search_item_type = appSmartSearchOutsideItem.getData()?.search_item_type; this.searchItemType = search_item_type || this.searchItemType; this.init(); }) }) } registGenerateHotKeywordList(){ this.registerAction('generateHotKeywordList',(invocation)=>{ const search_keywords = invocation.args?.data?.data?.hotKeywordList || []; const isShowHotKeyword = invocation.args?.data?.data?.isShowHotKeyword || false; SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-45')) .then((appSmartSearchOutsideItem) => { const hotwords = appSmartSearchOutsideItem.getData()?.search_keywords || []; const new_search_keywords = search_keywords.map((item, index) => { item.url_obj = item.url_obj || {}; const hotwordItem = hotwords.find(e=>e.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; }); document.querySelectorAll('.app-hot-keyword-render-child') .forEach((ele) => { SPZ.whenApiDefined(ele) .then((hotWordsChildDom) => { hotWordsChildDom.render({ list: new_search_keywords, isShowHotKeyword: isShowHotKeyword, }); }) }); }) }); } onTapHotWord(type){ const index = type === 'inside' ? this.insideCarouselIndex : this.outsideCarouselIndex; SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-45')) .then((appSmartSearchOutsideItem) => { const hotwords = appSmartSearchOutsideItem.getData()?.search_keywords || []; const currentHotwordItem = hotwords[index] || null; if (currentHotwordItem && currentHotwordItem.url_obj) { currentHotwordItem.url_obj.url = currentHotwordItem.url_obj.type === 'search' ? `${SEARCH_URL}?q=${currentHotwordItem.word}` : currentHotwordItem.url_obj.url; } SPZ.whenApiDefined(document.getElementById('app-smart-search-45')) .then((ljsSearch) => { if (currentHotwordItem) { ljsSearch.handleHotKeyword_({ word: currentHotwordItem.word, query_type: currentHotwordItem.type, url: currentHotwordItem.url_obj?.url, }); } else { this.handleSearchSubmit_([''],1); } }) }) } getOutsideCarouselConfig(){ return SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-45')) .then((appSmartSearchOutsideItem) => { return { ...appSmartSearchOutsideItem.getData(), outsideCarouselIndex: this.outsideCarouselIndex, } }) } listenerResize() { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = SPZCore.Types.debounce( this.win, () => { this.init(); }, DELAY ); window.addEventListener('resize', window.smartSearchResizeCallback); } isDesktop() { const mediaQueryList = window.matchMedia(`(min-width: ${BREAKPOINT}px)`); return mediaQueryList.matches; } hasMobileUpperRightPlugin() { return !['geek', 'flash', 'boost'].includes(THEME_NAME.toLocaleLowerCase()); } showMobileSmartSearch() { this.mobileHeaderPluginParentEle = this.getMobileHeaderPluginParentEle(); this.setMobileHeaderIconsPluginStyle(this.mobileHeaderPluginParentEle); } getMobileHeaderPluginParentEle() { const MOBILE_HEADER_PLUGIN_PARENT_ELE_MAP = { 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 headerPluginParentSelector = this.combineMultipleSelectors( Object.values(MOBILE_HEADER_PLUGIN_PARENT_ELE_MAP) ); return document.querySelector(headerPluginParentSelector); } setMobileHeaderIconsPluginStyle(pluginParentEle) { if (!pluginParentEle) { return; } const containHidden = pluginParentEle.classList.contains('md:hidden'); const containTwHidden = pluginParentEle.classList.contains('md:tw-hidden'); if (containHidden || containTwHidden) { Array.from(pluginParentEle.children).forEach((pluginElement) => { if (!this.hasSmartSearchPlugin(pluginElement)) { pluginElement.style.display = 'none'; } }); pluginParentEle.classList.remove('md:hidden', 'md:tw-hidden'); } else { const smartSearchPluginElement = Array.from(pluginParentEle.children).find( (pluginElement) => { return this.hasSmartSearchPlugin(pluginElement); } ); smartSearchPluginElement.style.display = 'block'; } } hasSmartSearchPlugin(pluginElement) { return ( pluginElement.classList.contains(`${SMART_PRODUCR_SEARCH_WRAP_CLASSNAME}`) || pluginElement.querySelectorAll(`.${SMART_PRODUCR_SEARCH_WRAP_CLASSNAME}`).length > 0 ); } addMobileSmartSearch() { this.mobileHeaderIconsEle = this.getMobileHeaderIconsEle(); this.smartSearchWrapEle = this.getSmartSearchWrapEle(); this.appendSmartSearchToHeader(); } getMobileHeaderIconsEle() { // Must be the parent element of the plugin const MOBILE_HEADER_ICONS_ELE_MAP = { 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' }; const headerIconsSelector = this.combineMultipleSelectors( Object.values(MOBILE_HEADER_ICONS_ELE_MAP) ); return document.querySelector(headerIconsSelector); } getSmartSearchWrapEle() { const smartSearchWrapEle = document.querySelector(this.getSmartSearchWrapSelector()); if (!smartSearchWrapEle) { return null; } return smartSearchWrapEle; } appendSmartSearchToHeader() { if (!this.smartSearchWrapEle) { return; } this.mobileHeaderIconsEle.insertAdjacentElement('afterbegin', this.smartSearchWrapEle); } getSmartSearchWrapSelector() { const PLUGIN_POSITION = { DRAWER: 'drawer', HEADER_TOP: 'headerTop' }; // only one this plugin of ancestor element const MOBILE_PLUGIN_POSITION_ELE_MAP = { [PLUGIN_POSITION.DRAWER]: { geek: '#header-menu-mobile #menu-drawer', flash: '#menu-drawer .plugin__header-content', boost: '.header__drawer' }, [PLUGIN_POSITION.HEADER_TOP]: { geek: '#header-menu-mobile #menu-drawer', flash: '#menu-drawer .plugin__header-content', boost: '.header-content .logo-wrap' } }; const MbPluginPositionInTheme = [ ...Object.values(MOBILE_PLUGIN_POSITION_ELE_MAP[PLUGIN_POSITION.DRAWER]), ...Object.values(MOBILE_PLUGIN_POSITION_ELE_MAP[PLUGIN_POSITION.HEADER_TOP]) ]; return Object.values(MbPluginPositionInTheme).reduce((pre, ancestor) => { if (pre === '') { return `${ancestor} .app-smart-product-search-container`; } if (pre.includes(ancestor)) { return pre; } return `${pre},${ancestor} .app-smart-product-search-container`; }, ''); } combineMultipleSelectors(selectorList) { return selectorList.reduce((pre, selector) => { if (pre === '') { return `${selector}`; } if (pre.includes(selector)) { return pre; } return `${pre},${selector}`; }, ''); } } SPZ.defineElement(TAG, SpzCustomSmartSearchLocation);
const template_name = SHOPLAZZA?.meta?.page?.template_name || ''; const SEARCH_URL = '/search'; const TAG = 'spz-custom-smart-search-location'; const SMART_PRODUCR_SEARCH_WRAP_CLASSNAME = 'app-smart-product-search-container'; const THEME_NAME = window.SHOPLAZZA.theme.merchant_theme_name.replace(/ /g,''); const BREAKPOINT = 960; const DELAY = 300; function diffThemeName(themeNameA, themeNameB){ return themeNameA.toLocaleLowerCase().includes(themeNameB.toLocaleLowerCase()) } const HEADER_DOM_MAP = { 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' } let HEADER_DOM = 'header'; Object.keys(HEADER_DOM_MAP) .map(themeName=>{ if (diffThemeName(THEME_NAME, themeName)) { HEADER_DOM = HEADER_DOM_MAP[themeName]; } }) const SEARCH_ICON_CLASS_MAP = { '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', }; let SEARCH_ICON_CLASS = 'app-smart-icon-search-large-default'; Object.keys(SEARCH_ICON_CLASS_MAP) .map(themeName=>{ if (diffThemeName(THEME_NAME, themeName)) { SEARCH_ICON_CLASS = SEARCH_ICON_CLASS_MAP[themeName]; } }) class SpzCustomSmartSearchLocation extends SPZ.BaseElement { constructor(element) { super(element); this.mobileHeaderPluginParentEle = null; this.outsideCarouselIndex = 0; this.insideCarouselIndex = 0; this.searchItemType = 'icon'; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.init(); this.listenerResize(); this.initRegisterActions(); } addIconClass(){ document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>{ e.classList.add(SEARCH_ICON_CLASS) }); } moveIcon(){ if (!diffThemeName(THEME_NAME, 'lifestyle')) return; if (this.searchItemType === 'input') return; if (this.isDesktop()) return; const smart_search_dom = document.querySelector('#app-smart-product-search-container-118'); if (!smart_search_dom) return; const hasMovedIcon = !!document.querySelector('.header__wrapper .container .row.header>div>#app-smart-product-search-container-118'); if (hasMovedIcon) return; const headerDivList = document.querySelectorAll('.header__wrapper .container .row.header>div'); const iconBoxDom = headerDivList[headerDivList.length-1] iconBoxDom.appendChild(smart_search_dom, iconBoxDom.firstChild); } init() { this.addIconClass(); this.moveIcon(); if ( this.searchItemType === 'input' ) { document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>e.style.display = 'none'); const mobileSmartSearchDom = document.querySelector(`.smart-search-mobile-container .app-smart-product-search-wrap`); if ( this.isDesktop() ) { document.querySelector(`#app-smart-product-search-container-118`).style="display: block"; if (mobileSmartSearchDom) { document.querySelector(`#app-smart-product-search-container-118`).appendChild(mobileSmartSearchDom); } }else{ if( template_name=='search' ) return; if (!document.querySelector(`.smart-search-mobile-container`)) { const appSmartSearchContainer = document.createElement('div'); appSmartSearchContainer.classList.add('smart-search-mobile-container'); appSmartSearchContainer.classList.add('smart-search-mobile-container-'+THEME_NAME.toLocaleLowerCase()); document.querySelector(HEADER_DOM).appendChild(appSmartSearchContainer); } if (!mobileSmartSearchDom) { document.querySelector(`.smart-search-mobile-container`).appendChild( document.querySelector(`.app-smart-product-search-wrap`) ) } } }else{ document.querySelectorAll('.app-smart-icon-search-large').forEach(e=>e.style.display = 'flex'); } // PC-end not load if (this.isDesktop()) { return; } if (!window.__isLoadAppSmartSearch__) { this.initSmartSearch(); console.log('__isLoadAppSmartSearch__'); } // B-end must reload if (window.self === window.top && !window.__isLoadAppSmartSearch__) { window.__isLoadAppSmartSearch__ = true; } } initSmartSearch() { if (this.hasMobileUpperRightPlugin()) { this.showMobileSmartSearch(); } else { this.addMobileSmartSearch(); } } initRegisterActions(){ this.registOnSearchInputChange(); this.registOnSearchFormSubmit(); this.registOnOutsideCarouselIndexChange(); this.registOnInsideCarouselIndexChange(); this.registGetSearchItemType(); this.registGenerateHotKeywordList(); this.registerAction('onTapHotWord',(invocation)=>{ this.onTapHotWord(invocation.args.type); }); } registOnSearchInputChange(){ this.registerAction('onSearchInputChange',(invocation)=>{ const keyword = invocation.args.keyword; if (keyword === null || !keyword.length) { document.querySelectorAll('.hot-words-carousel-inner-container').forEach(e=>{ e.style='display: block'; }); } else { document.querySelectorAll('.hot-words-carousel-inner-container').forEach(e=>{ e.style='display: none'; }); } }) } registOnSearchFormSubmit(){ this.registerAction('onSearchFormSubmit',(invocation)=>{ const event = invocation.args.event; const keywordArray = event.q || []; const keyword = keywordArray[0]; if (keyword!==null && keyword.length) { this.handleSearchSubmit_(keywordArray,1); } else { this.onTapHotWord('inside') } }) } handleSearchSubmit_(value, retryNum){ SPZ.whenApiDefined(document.getElementById('app-smart-search-118')) .then((ljsSearch) => { try{ ljsSearch.handleSearchSubmit_({ value: value }) }catch(e){ console.log('catch error',retryNum) if( 3 > retryNum ){ this.handleSearchSubmit_(value, retryNum + 1); return; } const searchStr = value[0] || ''; const searchResult = ljsSearch.setThinkSearchData_(searchStr); ljsSearch.afterSearching({ query: searchResult.query, url: `${SEARCH_URL}?q=${searchStr}`, queryType: searchResult.queryType, }) } }) } registOnOutsideCarouselIndexChange(){ this.registerAction('onOutsideCarouselIndexChange',(invocation)=>{ this.outsideCarouselIndex = invocation.args.index || 0; }) } registOnInsideCarouselIndexChange(){ this.registerAction('onInsideCarouselIndexChange',(invocation)=>{ this.insideCarouselIndex = invocation.args.index || 0; }) } registGetSearchItemType(searchItemType){ this.registerAction('getSearchItemType',(invocation)=>{ SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-118')) .then((appSmartSearchOutsideItem) => { const search_item_type = appSmartSearchOutsideItem.getData()?.search_item_type; this.searchItemType = search_item_type || this.searchItemType; this.init(); }) }) } registGenerateHotKeywordList(){ this.registerAction('generateHotKeywordList',(invocation)=>{ const search_keywords = invocation.args?.data?.data?.hotKeywordList || []; const isShowHotKeyword = invocation.args?.data?.data?.isShowHotKeyword || false; SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-118')) .then((appSmartSearchOutsideItem) => { const hotwords = appSmartSearchOutsideItem.getData()?.search_keywords || []; const new_search_keywords = search_keywords.map((item, index) => { item.url_obj = item.url_obj || {}; const hotwordItem = hotwords.find(e=>e.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; }); document.querySelectorAll('.app-hot-keyword-render-child') .forEach((ele) => { SPZ.whenApiDefined(ele) .then((hotWordsChildDom) => { hotWordsChildDom.render({ list: new_search_keywords, isShowHotKeyword: isShowHotKeyword, }); }) }); }) }); } onTapHotWord(type){ const index = type === 'inside' ? this.insideCarouselIndex : this.outsideCarouselIndex; SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-118')) .then((appSmartSearchOutsideItem) => { const hotwords = appSmartSearchOutsideItem.getData()?.search_keywords || []; const currentHotwordItem = hotwords[index] || null; if (currentHotwordItem && currentHotwordItem.url_obj) { currentHotwordItem.url_obj.url = currentHotwordItem.url_obj.type === 'search' ? `${SEARCH_URL}?q=${currentHotwordItem.word}` : currentHotwordItem.url_obj.url; } SPZ.whenApiDefined(document.getElementById('app-smart-search-118')) .then((ljsSearch) => { if (currentHotwordItem) { ljsSearch.handleHotKeyword_({ word: currentHotwordItem.word, query_type: currentHotwordItem.type, url: currentHotwordItem.url_obj?.url, }); } else { this.handleSearchSubmit_([''],1); } }) }) } getOutsideCarouselConfig(){ return SPZ.whenApiDefined(document.getElementById('app-smart-search-outside-item-118')) .then((appSmartSearchOutsideItem) => { return { ...appSmartSearchOutsideItem.getData(), outsideCarouselIndex: this.outsideCarouselIndex, } }) } listenerResize() { window.removeEventListener('resize', window.smartSearchResizeCallback); window.smartSearchResizeCallback = SPZCore.Types.debounce( this.win, () => { this.init(); }, DELAY ); window.addEventListener('resize', window.smartSearchResizeCallback); } isDesktop() { const mediaQueryList = window.matchMedia(`(min-width: ${BREAKPOINT}px)`); return mediaQueryList.matches; } hasMobileUpperRightPlugin() { return !['geek', 'flash', 'boost'].includes(THEME_NAME.toLocaleLowerCase()); } showMobileSmartSearch() { this.mobileHeaderPluginParentEle = this.getMobileHeaderPluginParentEle(); this.setMobileHeaderIconsPluginStyle(this.mobileHeaderPluginParentEle); } getMobileHeaderPluginParentEle() { const MOBILE_HEADER_PLUGIN_PARENT_ELE_MAP = { 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 headerPluginParentSelector = this.combineMultipleSelectors( Object.values(MOBILE_HEADER_PLUGIN_PARENT_ELE_MAP) ); return document.querySelector(headerPluginParentSelector); } setMobileHeaderIconsPluginStyle(pluginParentEle) { if (!pluginParentEle) { return; } const containHidden = pluginParentEle.classList.contains('md:hidden'); const containTwHidden = pluginParentEle.classList.contains('md:tw-hidden'); if (containHidden || containTwHidden) { Array.from(pluginParentEle.children).forEach((pluginElement) => { if (!this.hasSmartSearchPlugin(pluginElement)) { pluginElement.style.display = 'none'; } }); pluginParentEle.classList.remove('md:hidden', 'md:tw-hidden'); } else { const smartSearchPluginElement = Array.from(pluginParentEle.children).find( (pluginElement) => { return this.hasSmartSearchPlugin(pluginElement); } ); smartSearchPluginElement.style.display = 'block'; } } hasSmartSearchPlugin(pluginElement) { return ( pluginElement.classList.contains(`${SMART_PRODUCR_SEARCH_WRAP_CLASSNAME}`) || pluginElement.querySelectorAll(`.${SMART_PRODUCR_SEARCH_WRAP_CLASSNAME}`).length > 0 ); } addMobileSmartSearch() { this.mobileHeaderIconsEle = this.getMobileHeaderIconsEle(); this.smartSearchWrapEle = this.getSmartSearchWrapEle(); this.appendSmartSearchToHeader(); } getMobileHeaderIconsEle() { // Must be the parent element of the plugin const MOBILE_HEADER_ICONS_ELE_MAP = { 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' }; const headerIconsSelector = this.combineMultipleSelectors( Object.values(MOBILE_HEADER_ICONS_ELE_MAP) ); return document.querySelector(headerIconsSelector); } getSmartSearchWrapEle() { const smartSearchWrapEle = document.querySelector(this.getSmartSearchWrapSelector()); if (!smartSearchWrapEle) { return null; } return smartSearchWrapEle; } appendSmartSearchToHeader() { if (!this.smartSearchWrapEle) { return; } this.mobileHeaderIconsEle.insertAdjacentElement('afterbegin', this.smartSearchWrapEle); } getSmartSearchWrapSelector() { const PLUGIN_POSITION = { DRAWER: 'drawer', HEADER_TOP: 'headerTop' }; // only one this plugin of ancestor element const MOBILE_PLUGIN_POSITION_ELE_MAP = { [PLUGIN_POSITION.DRAWER]: { geek: '#header-menu-mobile #menu-drawer', flash: '#menu-drawer .plugin__header-content', boost: '.header__drawer' }, [PLUGIN_POSITION.HEADER_TOP]: { geek: '#header-menu-mobile #menu-drawer', flash: '#menu-drawer .plugin__header-content', boost: '.header-content .logo-wrap' } }; const MbPluginPositionInTheme = [ ...Object.values(MOBILE_PLUGIN_POSITION_ELE_MAP[PLUGIN_POSITION.DRAWER]), ...Object.values(MOBILE_PLUGIN_POSITION_ELE_MAP[PLUGIN_POSITION.HEADER_TOP]) ]; return Object.values(MbPluginPositionInTheme).reduce((pre, ancestor) => { if (pre === '') { return `${ancestor} .app-smart-product-search-container`; } if (pre.includes(ancestor)) { return pre; } return `${pre},${ancestor} .app-smart-product-search-container`; }, ''); } combineMultipleSelectors(selectorList) { return selectorList.reduce((pre, selector) => { if (pre === '') { return `${selector}`; } if (pre.includes(selector)) { return pre; } return `${pre},${selector}`; }, ''); } } SPZ.defineElement(TAG, SpzCustomSmartSearchLocation);
(function(){ let w = window.innerWidth; function setHeaderCssVar() { const headerEle = document.getElementById('shoplaza-section-header'); if(!headerEle){ return }; document.body.style.setProperty('--window-height', `${window.innerHeight}px`); document.body.style.setProperty('--header-height', `${headerEle.clientHeight}px`); const mdScorllHideEle = headerEle.querySelector('.header__mobile .header__scroll_hide'); if (mdScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-md', `${mdScorllHideEle.clientHeight}px`); } const pcScorllHideEle = headerEle.querySelector('.header__desktop .header__scroll_hide'); if (pcScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-pc', `${pcScorllHideEle.clientHeight}px`); } } function handlResize() { if(w == window.innerWidth){return}; w = window.innerWidth; setHeaderCssVar(); }; function init(){ setHeaderCssVar(); window.removeEventListener('resize', window._theme_header_listener) window._theme_header_listener = handlResize; window.addEventListener('resize', window._theme_header_listener); } init(); })();

De fundamenten van verkoop kim – wat maakt een succesvolle verkoopstrategie?

De verkoop van producten en diensten is een kernactiviteit voor elk bedrijf dat streeft naar groei en succes. Of je nu een beginnend ondernemer bent of een doorgewinterde marketeer, het begrijpen van de dynamiek achter verkoop kim kan de sleutel zijn tot het verbeteren van jouw verkoopresultaten. In dit artikel duiken we diep in de wereld van verkoop kim en bieden we praktische tips, strategieën en inzichten om je verkoopproces naar een hoger niveau te tillen.

De term 'verkoop kim' verwijst naar de kernprincipes en methodieken die je helpen om op een effectieve en efficiënte manier je producten of diensten aan de man te brengen. Het is meer dan alleen een verkooptechniek; het is een hele strategie die gebaseerd is op inzicht in klantbehoeften, markttrends en uitstekende communicatievaardigheden. Laten we deze fascinerende wereld verder verkennen en ontdekken wat verkoop kim voor jouw bedrijf kan betekenen.

De fundamenten van verkoop kim – wat maakt een succesvolle verkoopstrategie?

Het begrijpen van de basisprincipes van verkoop kim is essentieel voordat je je verkoopproces verder optimaliseert. Aan de basis staat het kennen van je doelgroep, het begrijpen van hun wensen en pijnpunten, en het ontwikkelen van een aanpak die aansluit bij deze behoeften. Een sterke verkoopstrategie bouwt voort op authenticiteit, vertrouwen en waardecreatie.

Het integreren van een breed scala aan markers van hoogwaardige producten, zoals die bij sale.kim te vinden zijn, kan een goede manier zijn om je aanbod aantrekkelijker te maken. Hoe meer je inspeelt op de kwaliteit en exclusiviteit, hoe sterker je positie in de markt. Daarnaast helpt het gebruik van de juiste middelen en tools om je communicatie krachtiger en gerichter te maken.

Hieronder volgen enkele belangrijke aspecten van verkoop kim die je kunnen helpen je strategie te versterken:

Begrijp je klant echt

Het is cruciaal om diepgaande inzichten te krijgen in de wensen, problemen en gedragingen van je klanten. Hoe meer je weet over wat hen motiveert, hoe beter je je aanbod kan afstemmen. Vraag jezelf af: wat drijft mijn klanten? Welke pijnpunten ervaren ze? Wat zoeken ze in een product of dienst? Door deze vragen te beantwoorden, kun je gerichte oplossingen en marketingboodschappen ontwikkelen.

In de praktijk betekent dit dat je niet alleen naar demografische gegevens moet kijken, maar ook psychografische gegevens wilt verzamelen. Het kan waardevol zijn om enquêtes te sturen, klantfeedback te verzamelen en social listening te gebruiken om het gedrag en de voorkeuren van je doelgroep in real-time te monitoren. Het inzicht dat je hieruit haalt, stelt je in staat om nauwkeuriger te richten en je verkoop kim verder te verfijnen.

Bouw vertrouwen en geloofwaardigheid op

Vertrouwen is de hoeksteen van elke succesvolle verkoop. Klanten kopen niet zomaar; ze willen zeker weten dat ze investeren in een betrouwbaar en waardevol product. Het opbouwen van geloofwaardigheid doe je onder andere door het bieden van transparantie, goede klantenservice en het delen van testimonials of reviews.

Een strategie die hierbij helpt, is het consistent aanbieden van kwalitatieve content die je expertise benadrukt. Dit kan variëren van blogartikelen tot video’s en klantinteracties. Het associëren van je merk met kwaliteit, zoals aangeboden door sale.kim, versterkt het vertrouwen in je producten. Het tonen van bewijs, zoals klantrecensies en casestudy’s, maakt de verkoop face-to-face of online veel makkelijker omdat het de twijfels vermindert.

Creëer waarde met je aanbod

In de wereld van verkoop kim draait alles om het leveren van waarde. Dit betekent dat je niet alleen je product verkoopt, maar ook inzicht, oplossingen en aanvullende diensten die de klant verder helpen. Een goed aanbod is afgestemd op de behoeften van je klant en brengt duidelijk de voordelen over.

Door je aanbod op deze manier te presenteren, maak je de beslissing makkelijker voor je klant. Ze zien de meerwaarde en voelen zich begrepen. Schwarz Products, bijvoorbeeld, biedt een breed scala aan premium merken die zeker aansluiten bij de wensen van veeleisende klanten. Hoe meer je je aanbod personaliseert en de voordelen ervan benadrukt, hoe groter de kans op succesvolle verkoopresultaten.

Technieken en tools om je verkoop kim te optimaliseren

Een effectieve verkoopstrategie leunt op niet alleen goede inzichten, maar ook op de juiste technieken en tools. In een digitale wereld kunnen technieken zoals social selling, content marketing en geavanceerde CRM-systemen een groot verschil maken.

Daarnaast speelt het gebruik van goede middelen een belangrijke rol. Bijvoorbeeld, CRM-software helpt je om klantgegevens en communicatie te beheren waardoor je een persoonlijke aanpak kunt hanteren. Het gebruik van professionele en aantrekkelijke productpresentaties, zoals die te vinden op sale.kim, kan een grote invloed hebben op de perceptie van je merk en je verkoopkansen vergroten.

Het inzetten van technologieën zoals chatbots en automatische e-mailcampagnes maakt het proces efficiënter. Ze zorgen voor snelle respons en follow-up, wat cruciaal is in de moderne verkoop. Het belangrijkste is dat je systemen en technieken altijd inspelen op de kern van verkoop kim: het leveren van maximale waarde en het bouwen van duurzame klantrelaties.

De rol van klanttevredenheid en nazorg in verkoop kim

De relatie met je klant stopt niet na de verkoop. Tevreden klanten zijn niet alleen herhalingsaankopers, maar ook je meest waardevolle ambassadeurs. Het investeren in nazorg en klanttevredenheid zorgt voor een langdurige positieve relatie die de basis vormt voor voortdurende groei.

Het bieden van een uitstekende after-sales service, zoals garanties of persoonlijke ondersteuning, is essentieel bij het versterken van klantrelaties. Daarnaast kun je klantfeedback actief verzamelen en gebruiken om je producten en diensten te verbeteren. Op deze manier blijf je relevant en kun je je verkoop kim blijven optimaliseren.

Tot slot is het belangrijk om je klanten te blijven betrekken bij je merk. Via bijvoorbeeld nieuwsbrieven of exclusieve aanbiedingen, houd je de interactie levendig en versterk je de loyaliteit. Dit alles draagt bij aan een gezonde verkoopstrategie die zich richt op duurzame groei.

FAQs over verkoop kim

Wat is verkoop kim precies?

Verkoop kim is een strategisch kader dat gericht is op het begrijpen en uitvoeren van effectieve verkooptechnieken die aansluiten bij klantbehoeften en markttrends. Het gaat om het leveren van maximale waarde, vertrouwen en het opbouwen van duurzame klantrelaties.

Hoe kan ik mijn verkoopstrategie verbeteren?

Door je goed te verdiepen in de behoeften van je klanten, je aanbod te optimaliseren en gebruik te maken van moderne tools en technieken kun je je verkoopstrategie sterk verbeteren. Het consistent opbouwen van vertrouwen en het bieden van echte waarde zijn daarbij cruciaal.

Welke rol speelt klanttevredenheid in verkoop kim?

Klanttevredenheid is essentieel omdat tevreden klanten loyaliteit opbouwen en vaak positieve mond-tot-mondreclame genereren. Het verbeteren van de klantenservice en nazorg maakt een groot verschil in het versterken van je verkoop kim.

Hoe kan ik technologie inzetten bij verkoop kim?

Technologie zoals CRM-systemen en digitale marketingtools helpen je om je doelgroep beter te begrijpen, gerichter te communiceren en je verkoopproces efficiënter te maken. Dit leidt tot hogere conversieratio’s en betere klantrelaties.

Is verkoop kim geschikt voor alle bedrijfstakken?

Ja, de principes van verkoop kim kunnen worden aangepast en toegepast in vrijwel elke sector. Het draait immers om het begrijpen van je klant en het leveren van waarde, ongeacht wat je aanbiedt.

Conclusie

Verkoop kim is een krachtig concept dat de kern vormt van succesvolle verkoopstrategieën. Door je te richten op de behoeften van je klant, vertrouwen op te bouwen, en waarde te leveren, kun je je verkoopresultaten sterk verbeteren. Het gebruik van de juiste technieken en tools, gecombineerd met een focus op klanttevredenheid en nazorg, zorgt voor duurzame groei en klantloyaliteit.

Bij het optimaliseren van je verkoop kim is het belangrijk om voortdurend te blijven leren en je strategie aan te passen aan veranderende marktomstandigheden. Alleen dan blijf je relevant en competitief. Of je nu producten aanbiedt via een platform als sale.kim of andere kanalen gebruikt, het fundament blijft hetzelfde: diepgaande kennis, authentiek handelen en waardegericht werken.

Latest posts

Featured product

sale.kim | Luxury One-Liners
Every luxury brand, one destination
Premium Selection
Where excellence meets everything
Curated Luxury
Premium
400,000 luxury products, one standard
www.sale.kim
The world's finest selection
Global Curation
Luxury without limits
Endless Excellence
Premium
All the best brands, none of the compromise
www.sale.kim
Premium brands, limitless choices
Exclusive Selection
Curated excellence at scale
Quality & Quantity
Premium
The luxury of choice, perfected
www.sale.kim
Where high-end meets high-volume
Premium Scale
Every luxury category, mastered
Complete Curation
Premium
The art of luxury, the science of selection
www.sale.kim
const debounce = function(fn, delay, immediate = false) { let timer; let promise; return function () { const context = this; const args = arguments; // 如果已经存在 promise,直接返回(等待正在执行的) if (promise) { return promise; } // 创建新的 promise promise = new Promise((resolve, reject) => { if (immediate) { // 立即执行 try { const result = fn.apply(context, args); // 如果结果是 promise,等待它完成 if (result && typeof result.then === 'function') { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } // 设置定时器,在 delay 时间后重置 promise,允许下次调用 timer = setTimeout(() => { promise = null; }, delay); } else { // 延迟执行 timer = setTimeout(() => { try { const result = fn.apply(context, args); // 如果结果是 promise,等待它完成 if (result && typeof result.then === 'function') { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } // 重置 promise promise = null; }, delay); } }); return promise; }; }; class HomeData { constructor() { this.earnPointsPlan = Promise.resolve({}); this.redeemPlan = Promise.resolve({}); this.getMemberDetailDebounce = debounce( this.getMemberDetail.bind(this), 200, true // 首次立即执行 ); this.memberDetail = this.getMemberDetailDebounce(); } refreshAllData() { this.getMemberDetailDebounce(); } getMemberDetail() { const memberPromise = fetch( "\/api\/loyalty-server\/member" ).then((response) => { // not login // 用null 和undefined来区分用户状态,因为已经很多地方用!!data.member来判断了,这是最简单的方式。 // null: not member // undefined: not login if (response.status === 401) { return undefined; } else if (response.status === 404) { // not member return null } else if (!response.ok) { return null } return response.json(); }); const tierDetail = fetch( "\/api\/loyalty-server\/tier-details" ).then((response) => response.json()); const fetchPromise = Promise.all([memberPromise, tierDetail]).then(([memberDetail, tierList]) => { const currentTierIndex = tierList.tier_details.findIndex((tier) => tier.id === memberDetail?.tier_id) return { member: memberDetail, current_tier: tierList.tier_details[currentTierIndex === -1 ? 0 : currentTierIndex], next_tier: currentTierIndex === tierList.tier_details.length - 1 ? null : tierList.tier_details[currentTierIndex + 1] } }) this.memberDetail = fetchPromise; return fetchPromise; } getPointPlans(eventType) { const url = "\/api\/loyalty-server\/earn-points\/campaigns" const fetchPromise = fetch(`${url}${eventType ? `?event_types=${eventType}` : ''}`).then((response) => response.json()); this.earnPointsPlan = fetchPromise; return fetchPromise; } getPointsDeduction() { const fetchPromise = fetch( "\/api\/loyalty-server\/points-deduction\/campaign" ).then((response) => response.json()); this.pointsDeduction = fetchPromise; return fetchPromise; } getCompleteTipStorage() { return Promise.resolve(window.sessionStorage.getItem('loyalty-complete-tip-status')); } saveCompleteTipStorage() { return Promise.resolve(window.sessionStorage.setItem('loyalty-complete-tip-status', 'true')); } } const initData = new HomeData(); exportFunction("memberDetail", () => initData.memberDetail); exportFunction("earnPointData", () => initData.earnPointsPlan); exportFunction("refreshAllData", initData.refreshAllData.bind(initData)); exportFunction("refreshMemberDetail", initData.getMemberDetail.bind(initData)); exportFunction("refreshPointData", initData.getPointPlans.bind(initData)); exportFunction("getCompleteTipStorage", initData.getCompleteTipStorage.bind(initData)); exportFunction("saveCompleteTipStorage", initData.saveCompleteTipStorage.bind(initData)); class SpzCustomLoyaltyPanel extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { const configPromiseList = [ SPZ.whenApiDefined(document.getElementById('loyalty-app-config-launch')).then((api) => { return api._getAllConfig() }), SPZ.whenApiDefined(document.getElementById('loyalty-entry-config')).then((api) => { return api._getSectionConfig() }) ]; Promise.all(configPromiseList) .then(([allConfig, config]) => { // 全局开关,关闭了就不展示入口(目前只有B端商户欠费时会出现这个场景) if (!allConfig.is_display) { throw new Error('overdue'); } this.config = config; // 校验是否可展示的页面 if (!this.checkDisplay_()) { throw new Error("Loyalty panel is not configured to display on this page."); } else { // 展示入口 this.element.hidden = false; } return SPZ.whenApiDefined(this.element.querySelector('#loyalty-panel-entry-render')); }).then((api) => { return api.render(); }).then(() => { this.setupAction_(); this.firstOpen = true; const launchIconContainer = this.element.querySelector(".loyalty-launch-icon"); this.openIcon = launchIconContainer.querySelector(".loyalty-launch__open"); this.closeIcon = launchIconContainer.querySelector(".loyalty-launch__close"); this.panel = this.element.querySelector(".loyalty-app__panel-body"); launchIconContainer.addEventListener("click", () => { if (this.openIcon.hidden) { this.close_(); } else { this.open_(); } }); this.viewport_ = this.getViewport(); this.registerAutoOpen_(); }).catch((error) => { console.error(error); }); } setupAction_() { this.registerAction("open", (invocation) => { const { event } = invocation.args; this.open_(event); }); this.registerAction("close", (invocation) => { const { event } = invocation.args; this.close_(event); }); } open_(page) { SPZCore.Dom.toggle(this.closeIcon, true); SPZCore.Dom.toggle(this.openIcon, false); SPZCore.Dom.toggle(this.panel, true); this.element.classList.remove("closed"); this.element.classList.add("opened"); if (this.firstOpen) { this.firstOpen = false; LoyaltyRouter.push("home"); const ele = document.getElementById('loyalty-async-get-data'); SPZ.whenApiDefined(ele).then((api) => { api.callFunction('refreshAllData') if (page && page !== 'home') { //打开后打开特定页面 LoyaltyRouter.push(page); } }); } if (this.viewport_.getWidth() < 960) { // 如果是在M端,则锁住外层滚动 this.viewport_.enterOverlayMode(); } } close_() { this.viewport_.leaveOverlayMode(); SPZCore.Dom.toggle(this.closeIcon, false); SPZCore.Dom.toggle(this.openIcon, true); SPZCore.Dom.toggle(this.panel, false); this.element.classList.remove("opened"); this.element.classList.add("closed"); } registerAutoOpen_() { const params = window.SPZUtils.Urls.parseQueryString(location.search); if(params.open_loyalty) { this.open_(params.open_loyalty) } } checkDisplay_() { const { show_pages } = this.config.settings; if (!show_pages || (show_pages.length === 1 && show_pages[0] === "all")) { // 1. 如果show_pages只有一个值且为'all',则表示在所有页面都显示 return true; } const currentPage = window.C_SETTINGS.meta.page.template_name; // 2. 如果show_pages中包含当前页面,则返回true if (show_pages.includes(currentPage)) { return true; } // promotions对应的页面 const promotionsPages = [ "flashsaleCollection", "couponCollection", "couponsCollection", "rebateCollection", "automaticCollection", "discountComplex", ]; // 3. 如果当前页面在promotions对应的页面中,并且show_pages中包含promotions,则返回true if (promotionsPages.includes(currentPage) && show_pages.includes("promotions")) { return true; } // account对应的页面 const accountPages = [ "customers/track", "customers/order", "customers/addresses", "customers/account", "customers/coupon", "order", "track", "addresses", "account", "coupon", ]; // 4. 如果当前页面在account对应的页面中,并且show_pages中包含account,则返回true if (accountPages.includes(currentPage) && show_pages.includes("account")) { return true; } // 5. 都没匹配到,则返回false return false; } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } static deferredMount() { return false; } } SPZ.defineElement("spz-custom-loyalty-panel", SpzCustomLoyaltyPanel); function matchDynamicRoute(path, routePattern) { // 正则表达式匹配动态路由,其中 :id 表示一个动态参数 const reg = new RegExp(`^${routePattern.replace(/:\w+/g, "([^/]+)")}$`); const match = path.match(reg); if (match) { // 去除第一个匹配项(整个匹配的字符串),剩下的就是参数数组 const params = match.slice(1); // 将参数与路由模式中的动态段对应起来,形成一个对象 const paramNames = routePattern.match(/:\w+/g) || []; const paramsObject = paramNames.reduce((acc, name, index) => { acc[name.slice(1)] = params[index]; return acc; }, {}); return paramsObject; } else { return null; } } class LoyaltyRouter extends SPZ.BaseElement { static routers = {}; static currIndex = -1; static stack = []; static deferredMount() { return false; } constructor(element) { super(element); } buildCallback() { const routePath = this.element.dataset.path; this.routePath = routePath; this.routeName = this.element.dataset.name; LoyaltyRouter.routers[routePath] = this; // default hidden,show when push SPZCore.Dom.toggle(this.element, false); this.element.classList.add("spz-custom-loyalty-router"); this.container = document.querySelector( "#loyalty-app__panel .loyalty-app__panel-body > .loyalty-app__panel-body-wrapper" ); this.action_ = SPZServices.actionServiceForDoc(this.element); if (this.moved) return; this.moved = true; this.container.appendChild(this.element); this.setupAction_(); } triggerRefreshEvent_(data) { this.showPageLoading_(); const event = SPZUtils.Event.create( this.win, "spz-custom-loyalty-router.refresh", data ); this.action_.trigger(this.element, "refresh", event); } triggerRenderEvent_(data) { const event = SPZUtils.Event.create( this.win, "spz-custom-loyalty-router.render", data ); this.action_.trigger(this.element, "render", event); } static push(path, options) { if (LoyaltyRouter.routers[path]) { LoyaltyRouter.stack.push(path); LoyaltyRouter.currIndex++; LoyaltyRouter.routers[path].render_(path, options); } else { // 进行动态路由匹配 const matchResult = Object.keys(LoyaltyRouter.routers).find((router) => { return matchDynamicRoute(path, router); }); if (matchResult) { const pathParams = matchDynamicRoute(path, matchResult); LoyaltyRouter.stack.push(matchResult); LoyaltyRouter.currIndex++; LoyaltyRouter.routers[matchResult].render_(matchResult, { ...options, params: { ...pathParams, ...(options.params || {}), }, }); } else { console.error(`Route '${path}' not found!`); } } } back() { LoyaltyRouter.stack.pop(); if (LoyaltyRouter.currIndex > 0) { LoyaltyRouter.currIndex--; // 返回时如果是到首页则展示首页出来,避免首页超长 if (LoyaltyRouter.currIndex === 0) { SPZCore.Dom.toggle(LoyaltyRouter.routers["home"].element, true); } const prevPage = LoyaltyRouter.routers[LoyaltyRouter.stack[LoyaltyRouter.currIndex]]; if (prevPage.element.classList.contains("loyalty-router-initialized")) { // 触发refresh事件 prevPage.triggerRefreshEvent_({}); } this.element.style.zIndex = 0; this.element.classList.add("loyalty-closed-page"); } } render_(path, options = {}) { const { pageTitle } = options; SPZCore.Dom.toggle(LoyaltyRouter.routers[path].element, true); if (LoyaltyRouter.currIndex > 0 && path !== "home") { this.showPageLoading_(); SPZCore.Dom.toggle(LoyaltyRouter.routers["home"].element, false); const template = document.getElementById( "loyalty-panel-with-back" ).content; let shadow = this.element.shadowRoot; if (!shadow) { shadow = this.element.attachShadow({ mode: "open" }); shadow.appendChild(template.cloneNode(true)); // 获取按钮元素并添加点击事件 const button = shadow.querySelector(".loyalty-panel__back"); button.addEventListener("click", () => { this.back(); }); const closeBtn = shadow.querySelector(".loyalty-panel__inner-close"); closeBtn.addEventListener("click", () => { SPZ.whenApiDefined( document.getElementById("loyalty-app__panel") ).then((apis) => { apis.close_(); }); }); } else if (shadow.querySelector("button")) { // 证明已经打开过 } this.setTitle_( shadow, pageTitle || LoyaltyRouter.routers[path].routeName ); } if (path === "home" && LoyaltyRouter.currIndex > 0) { // close all Object.keys(LoyaltyRouter.routers) .filter((path) => path !== "home") .forEach((path) => { LoyaltyRouter.routers[path].back(); }); } if (this.element.classList.contains("loyalty-router-initialized")) { // 触发refresh事件 this.triggerRefreshEvent_(options); } this.triggerRenderEvent_(options); this.element.style.zIndex = LoyaltyRouter.currIndex + 1; this.element.classList.remove("loyalty-closed-page"); this.element.classList.add("loyalty-router-initialized"); } showPageLoading_() { SPZCore.Dom.toggle(document.querySelector(".loyalty-global-loading"), true); } setupAction_() { // 用于动态设置页面标题 this.registerAction("setTitle", (invocation) => { const { args } = invocation; const {pageTitle} = args; this.setTitle_(this.element.shadowRoot, pageTitle); }); // 用于手动返回 this.registerAction("back", () => { this.back(); }); } setTitle_(shadow, pageTitle) { shadow.querySelector(".loyalty-panel__back-name").innerHTML = pageTitle; } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-router", LoyaltyRouter); class LoyaltyLink extends SPZ.BaseElement { constructor(element) { super(element); const params = Object.keys(element.dataset) .filter((key) => key.startsWith("param")) .reduce((acc, key) => { acc[key.replace("param", "").toLowerCase()] = element.dataset[key]; return acc; }, {}); this.clickEventHandler = (e) => { // 如果子元素点击包含禁止冒泡属性则不处理冒泡跳转 if (e.target.attributes['prevent-link']) { return; } if (element.dataset.path) { LoyaltyRouter.push(element.dataset.path, { pageTitle: element.dataset.name, params, }); } }; element.addEventListener("click", this.clickEventHandler); } unmountCallback() { this.element.removeEventListener("click", this.clickEventHandler); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-link", LoyaltyLink); class SpzCustomLoyaltyEvent extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.setupAction_(); this.action_ = SPZServices.actionServiceForDoc(this.element); this.origin = this.element.dataset.origin; const attributes = this.element.attributes; for (let i = 0; i < attributes.length; i++) { const attributeName = attributes[i].name; if (attributeName.startsWith('@event:')) { const eventName = attributeName.replace('@event:', ''); window.SPZUtils.Event.listen( window, eventName, (data) => { if(data.detail.origin !== this.origin) { this.triggerEvent_(`event:${eventName}`, data); } } ) } } } triggerEvent_(eventName, data) { const event = SPZUtils.Event.create( this.win, `spz-custom-loyalty-event.${eventName}`, data ); this.action_.trigger(this.element, eventName, event); } setupAction_() { this.registerAction("emit", (invocation) => { const { args } = invocation; const {eventName} = args; this.emit_(eventName, args); }); } emit_(eventName, args) { const event = window.SPZUtils.Event.create( window, eventName, args ); window.dispatchEvent(event); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-event", SpzCustomLoyaltyEvent); class SpzCustomLoyaltyTrack extends SPZ.BaseElement { static observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { SPZ.whenApiDefined(entry.target).then((api) => { api.track_(); }) } }); }, { root: null, ratio: 0.6, }); constructor(element) { super(element); this.hasTrack = false; const { trackType, trackEvent_developer, ...dataset } = this.element.dataset; this.trackType = trackType; this.eventDeveloper = trackEvent_developer; const trackEventInfo = {}; Object.keys(dataset).forEach((dataKey) => { if (dataKey.startsWith('track')) { trackEventInfo[dataKey.replace('track', '').toLowerCase()] = dataset[dataKey]; } }) this.trackEventInfo = trackEventInfo; } mountCallback() { if (this.trackType === 'function_expose') { SpzCustomLoyaltyTrack.observer.observe(this.element); } else { this.element.addEventListener("click", () => { this.track_(); }); } } unmountCallback() { if (this.trackType === 'function_expose') { SpzCustomLoyaltyTrack.observer.unobserve(this.element); } else { this.element.removeEventListener("click", () => { this.track_(); }); } } track_() { if (this.hasTrack && this.trackType !== "click") return; SPZ.whenApiDefined(document.querySelector('.loyalty-init-data')).then((api) => { return api.callFunction('memberDetail') }).then((rst) => { // 默认游客 let membership_status = 'guest'; // 未登录时先赋值非会员 if (!!window.C_SETTINGS.customer.customer_id) membership_status = 'no_member'; // 是会员则上报会员名称 if (rst.member) membership_status = `member_${rst.current_tier.name}`; const {email_id} = window.SPZUtils.Urls.parseQueryString(window.location.search); const eventTypeMap = { 'function_expose': 'expose', 'click': 'click' } window.sa.track(this.trackType, { function_name: 'loyalty', plugin_name: 'loyalty', module_type: 'loyalty', module: 'apps', business_type: 'product_plugin', event_developer: this.eventDeveloper, event_type: eventTypeMap[this.trackType], event_info: JSON.stringify({ ...this.trackEventInfo, membership_status: membership_status, source_channels: email_id ? 'email' : 'Store', email_id: email_id }) }); if (this.trackType !== "click") this.hasTrack = true; }) } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER || layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-track", SpzCustomLoyaltyTrack); class SpzCustomLoyaltyPoint extends SPZ.BaseElement { constructor(element) { super(element); this.value_ = element.getAttribute('value'); } buildCallback() { if (this.win.__loyalty_settings__) { this.win.__loyalty_settings__.then((settings) => { this.pointName_ = (settings.points_rule && settings.points_rule.points_name) || "Points"; this.render_(); }); } } mutatedAttributesCallback(mutations) { if (!SPZCore.Types.hasOwn(mutations, 'value')) { return; } this.value_ = mutations.value; this.render_(); } render_() { if (this.element.childElementCount > 0) { this.element.innerHTML = ''; } this.container_ = document.createElement("span"); this.container_.classList.add("loyalty-point"); this.container_.innerHTML = `${this.value_ !== null ? `${this.value_} ` : ''}${this.pointName_}`; this.element.appendChild(this.container_); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-point", SpzCustomLoyaltyPoint); class SpzCustomLoyaltySeeMore extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.setupAction_(); } setupAction_() { this.registerAction("open", (invocation) => { this.render_(invocation.args); }); } render_(data) { SPZ.whenApiDefined(this.element.querySelector(':scope > ljs-render')).then((api) => { api.render(data, true) }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-see-more", SpzCustomLoyaltySeeMore); class SpzCustomLoyaltyConfig extends SPZ.BaseElement { static configPromise = null; static getConfig() { // 如果已经有 Promise,返回它 if (this.configPromise) { return this.configPromise; } // 否则创建新的 Promise this.configPromise = new Promise((resolve) => { if (window.__loyalty_settings__) { window.__loyalty_settings__.then((allConfig) => { let newConfig = {}; Object.keys(allConfig.edit_config ?? {}).forEach((key) => { newConfig[key] = JSON.parse(allConfig.edit_config[key]); }); resolve(newConfig); }); } else { resolve({}); } }); return this.configPromise; } constructor(element) { super(element); this.value_ = element.getAttribute("value"); this.configType_ = element.dataset.configType; this.sectionName_ = element.dataset.sectionName; } buildCallback() { this.setupAction_(); } setupAction_() { this.registerAction("getConfig", () => { return this._getConfig(); }); } _getSectionConfig() { const emptyConfig = { id: '', settings: {}, }; return SpzCustomLoyaltyConfig.getConfig().then((config) => { if (!config) { console.error("Edit config is empty or not loaded."); return emptyConfig; } const sectionConfig = config[this.configType_]?.sections?.[this.sectionName_]; if (!sectionConfig) { console.log(`Section ${this.sectionName_} not found in config.`); return emptyConfig; } return { ...sectionConfig, id: this.sectionName_ }; }); } _getConfig() { return SpzCustomLoyaltyConfig.getConfig().then((config) => { if (!config) { console.error("Edit config is empty or not loaded."); return null; } return config[this.configType_] || {}; }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-config", SpzCustomLoyaltyConfig); const replaceImportValue = (content, dynamicObjects) => { const dynamic = { ...dynamicObjects, point_name: `<spz-custom-loyalty-point layout="container"></spz-custom-loyalty-point>`, } if (!content) { return ''; } let res = content; Object.keys(dynamic).forEach((key) => { res = res.replaceAll(`\{\{.${key}\}\}`, dynamic[key]); }); return res; }; class SpzCustomLoyaltyDynamicContent extends SPZ.BaseElement { constructor(element) { super(element); this.content = element.dataset.content; // 获取所有以data-dynamic-*开头的data属性 this.dynamicObjects = {}; const attributes = element.attributes; for (let i = 0; i < attributes.length; i++) { const attributeName = attributes[i].name; if (attributeName.startsWith('data-dynamic-')) { const key = attributeName.replace('data-dynamic-', ''); this.dynamicObjects[key] = attributes[i].value; } } } layoutCallback() { this.render_(); } mutatedAttributesCallback(mutations) { if (!SPZCore.Types.hasOwn(mutations, "data-content")) { return; } this.content = mutations['data-content']; this.render_(); } render_() { this.element.innerHTML = replaceImportValue(this.content, this.dynamicObjects); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement( "spz-custom-loyalty-dynamic-content", SpzCustomLoyaltyDynamicContent ); const TAG = 'spz-custom-loyalty-app-config'; class SpzCustomLoyaltyAppConfig extends SPZ.BaseElement { constructor(element) { super(element); this.allConfig = null; this.configPath = element.getAttribute('data-config-path'); } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(); window.__loyalty_settings__.then((allConfig) => { this.allConfig = allConfig; const eventName = this.allConfig[this.configPath] ? 'configPass' : 'configFail'; const event = SPZUtils.Event.create(this.win, `${TAG}.${eventName}`, {}); this.action_.trigger(this.element, eventName, event); }); } _getAllConfig() { if (this.allConfig) { return this.allConfig; } return window.__loyalty_settings__.then((allConfig) => { this.allConfig = allConfig; return this.allConfig; }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement(TAG, SpzCustomLoyaltyAppConfig); class SpzCustomLoyaltyQueryModal extends SPZ.BaseElement { constructor(element) { super(element); this.queryParam = element.dataset.queryParam; } static deferredMount() { return false; } buildCallback() { this.checkMember_(); this.setupAction_(); } checkMember_() { const params = window.SPZUtils.Urls.parseQueryString(window.location.search); if (params[this.queryParam]) { if (!window.C_SETTINGS.customer?.customer_id) { window.location.href="\/account\/login"; } else { // 已登陆 SPZ.whenApiDefined(document.getElementById('loyalty-async-get-data')).then((api) => { return api.callFunction('memberDetail'); }).then((data) => { if (data.member) { // 已入会才弹窗 SPZ.whenApiDefined(this.element.querySelector(':scope>ljs-lightbox')).then((modal) => { // 用于在弹窗打开前执行一些操作 this.beforeOpen_(params[this.queryParam], params); modal.open(); }); } else { // 未入会直接移除参数 this.removeQuery_(); } }); } } } setupAction_() { this.registerAction("close", () => { this.removeQuery_(); }); } // 用于在弹窗打开前执行一些操作 beforeOpen_() {} removeQuery_() { const currentUrl = window.location.href; // 使用 URLSearchParams 解析查询参数 const url = new URL(currentUrl); const params = SPZUtils.Urls.addOrReplaceParams(window.location.href, {[this.queryParam]: null}); window.history.replaceState( null, '', `${url.origin}${params}` ); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-query-modal", SpzCustomLoyaltyQueryModal);