Welcome to our store

Welcome to our store

RY
RY
  • Home
  • 已上架商品
  • Album
  • New Arrivals
  • Product
  • 查询订单
  • 1111
  • 22222
  • 33333
  • 44444
  • 55555
  • 66666
  • 666666
  • 33333
  • More links

Paid benefits

Ways to earn points

Ways to redeem points

HELP

Wireless chargers

Power banks

Shipping

Returns and exchanges

HELP

Wireless chargers

Power banks

Shipping

Returns and exchanges

SHOP

Wireless chargers

Power banks

This is your heading text.

ABOUT US

Wireless chargers

Power banks

This is your heading text.

JOIN US

We send great emails!

Please enter your email address.
Please enter a valid email address.
Thank you being one of us!
© 2026. RY
RETURN POLICY PRIVACY POLICY SHIPPING PRIVACY ABOUT US CONTACT US 查询订单 尺码表

Cart

Your cart is empty

Summary

const TAG = 'spz-custom-cart-coupon-code-utils'; class SpzCustomCartCouponCodeUtils extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.keepOpenGuardBound_ = false; this.registerAction('setSideBarHeight', this.setSideBarHeight_.bind(this)); this.onResize_ = this.setSideBarHeight_.bind(this); window.addEventListener('resize', this.onResize_); } detachedCallback() { if (this.onResize_) { window.removeEventListener('resize', this.onResize_); this.onResize_ = null; } } ensureKeepOpenGuard_($sideBar) { if (this.keepOpenGuardBound_) return; this.keepOpenGuardBound_ = true; const observer = new MutationObserver(() => { if ($sideBar.hasAttribute('open') && $sideBar.hasAttribute('hidden')) { $sideBar.removeAttribute('hidden'); this.setSideBarHeight_(); } }); observer.observe($sideBar, { attributes: true, attributeFilter: ['hidden', 'open'] }); } getOpenDrawer_() { return document.querySelector('[data-section-type="cart_drawer"] spz-sidebar[open]'); } setSideBarHeight_() { const $sideBar = document.querySelector('#cart-coupon-code-modal'); if (!$sideBar) return; this.ensureKeepOpenGuard_($sideBar); const windowHeight = window.innerHeight; const $drawer = this.getOpenDrawer_(); if (!$drawer) { $sideBar.style.setProperty('max-height', windowHeight * 0.9 + 'px', 'important'); $sideBar.style.removeProperty('height'); $sideBar.style.removeProperty('width'); $sideBar.classList.remove('cart-coupon-code-modal--in-drawer'); return; } const drawerHeight = (windowHeight - 40) + 'px'; $sideBar.style.setProperty('max-height', drawerHeight, 'important'); $sideBar.style.setProperty('height', drawerHeight, 'important'); $sideBar.classList.add('cart-coupon-code-modal--in-drawer'); const rect = $drawer.getBoundingClientRect(); if (rect.width > 0) { $sideBar.style.setProperty('width', rect.width + 'px', 'important'); } } } SPZ.defineElement(TAG, SpzCustomCartCouponCodeUtils); const TAG = 'spz-custom-marketing-utils'; class SpzCustomMarketingUtils extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } mountCallback() { this.ensureToastInBody_('coupon-code-toast-wrap'); } showCouponCodeToast_(message) { this.ensureToastInBody_('coupon-code-toast-wrap'); const toastEl = document.getElementById('coupon-code-toast'); if (!toastEl) return; SPZ.whenApiDefined(toastEl).then((apis) => { apis.showToast(message); }); } ensureToastInBody_(id) { const wrapEl = document.getElementById(id); if (!wrapEl) return; if (!wrapEl.closest('[data-section-type="cart_drawer"]')) return; if (wrapEl.parentNode !== document.body) { document.body.appendChild(wrapEl); } } buildCallback() { this.registerAction('addToCartTrigger', (invocation) => { window.$ && $(document).trigger('dj.common.cart.change'); window.$ && $(document).trigger('dj.addToCart', invocation.event.detail); }); this.registerAction('showAddToCartToast', () => { this.showCouponCodeToast_("Added successfully"); }); } } SPZ.defineElement(TAG, SpzCustomMarketingUtils);
Taxes and shipping calculated at checkout
Payment method
class SpzCustomDiscountBundle extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } mountCallback() { // 同一页可能有多个 block 各自渲染本 snippet,导致逻辑元素 spz-custom-discount-toast(#appsAddCartToastFunc) 重复。 // 仅保留 DOM 中第一个实例,其余重复实例移除自身,保证逻辑元素及其 action 引用(appsAddCartToastFunc)唯一。 const funcs = document.querySelectorAll('#appsAddCartToastFunc'); if (funcs.length > 1 && this.element !== funcs[0]) { this.element.remove(); return; } // 全局折扣 toast 的弹出点分散在多个模板/组件里(含声明式 @atcError),无法逐一在弹出前前置, // 故挂载时幂等地去重并挂到 body;加购 toast 改为弹出前按需去重(见 showAddToCartToast) this.ensureSingleToastInBody_('cart_match_discount_toast_wrap'); } unmountCallback() {} // 同一页面可能有多个 block 各自渲染本 toast snippet(如商详的 discount_automatic + 购物车抽屉里的折扣横幅), // 导致 toast 外层出现重复 id。重复 id 下 getElementById/querySelector 及 SPZ 动作引用都只命中第一个, // 且被关进抽屉的那份会被抽屉的 transform/overflow 裁剪/错位。这里在挂载时幂等去重: // 只保留一份、移除多余的,并把保留的挂到 body(body 是 position:fixed 最安全的落点,避免被任何祖先裁剪)。可反复调用。 ensureSingleToastInBody_(id) { const els = Array.from(document.querySelectorAll('#' + id)); if (!els.length) return; els.slice(1).forEach((el) => el.remove()); const keep = els[0]; if (keep.parentNode !== document.body) { document.body.appendChild(keep); } } // 主题购物车抽屉是否打开 isInCartDrawer_() { return !!document.querySelector('[data-section-type="cart_drawer"] spz-sidebar[open]'); } setupAction_() { this.registerAction('showAddToCartToast', () => { // 抽屉内加购:主题本就不会有加购效果,统一弹插件自己的 toast // 非抽屉:主题有加购代理则沿用主题加购效果,否则插件兜底 const proxyEl = document.getElementById('add-cart-event-proxy'); const inDrawer = this.isInCartDrawer_(); if (!inDrawer && proxyEl) { return; } // 弹出前按需去重并挂到 body(幂等),避免重复 id 命中错对象或被抽屉裁剪/错位 this.ensureSingleToastInBody_('apps_add_cart_toast_wrap'); const toastEl = document.querySelector('#apps-match-drawer-add_to_cart_toast') SPZ.whenApiDefined(toastEl).then((apis) => { apis.showToast("Added successfully"); }); }); } buildCallback() { this.setupAction_(); }; } SPZ.defineElement('spz-custom-discount-toast', SpzCustomDiscountBundle);
const TAG = 'spz-custom-discount-default'; const API = { CAMPAIGN: "\/api\/storefront\/promotion\/landing_page\/campaign?discount_id=", PRODUCT_LIST: "\/api\/storefront\/promotion\/landing_page\/product\/list", DISCOUNT_I18N: "\/api\/discount-i18n" }; const SHOP_ID = "1248245"; const E_TAB_MAP = { scenario_buy: { value: "1", domId: "cart_product_list_buy_products" }, scenario_obtain: { value: "2", domId: "cart_product_list_obtain_products" } }; const SORT_DICT = { recommend_asc: { by: 'recommend', direction: 'asc' }, title_asc: { by: 'title', direction: 'asc' }, title_desc: { by: 'title', direction: 'desc' }, price_asc: { by: 'price', direction: 'asc' }, price_desc: { by: 'price', direction: 'desc' }, created_at_desc: { by: 'created_at', direction: 'desc' }, sales_desc: { by: 'sales', direction: 'desc' }, add_to_cart_count_desc: { by: 'add_to_cart_count', direction: 'desc' }, views_desc: { by: 'views', direction: 'desc' } }; const SORT_OPTIONS = [ { value: 'recommend_asc', text: "Recommend" }, { value: 'price_asc', text: "Price, low to high" }, { value: 'price_desc', text: "Price, high to low" }, { value: 'title_asc', text: "Name, A to Z" }, { value: 'title_desc', text: "Name, Z to A" }, { value: 'created_at_desc', text: "Newest in" }, { value: 'sales_desc', text: "Total sales, high to low" }, { value: 'add_to_cart_count_desc', text: "Purchases, high to low" }, { value: 'views_desc', text: "Page views, high to low" } ]; // 凑单弹窗与首页限时促销卡片(flashsale_collection)共用样式 id "discount-match-drawer",会产生重复 id。 // 故 JS 不按 id 定位弹窗,改用弹窗根节点上专用的 js- 前缀钩子类唯一定位——与样式解耦, // 天然避免命中首页卡片,也不受开弹窗时 appendChild 重排 DOM 顺序的影响(此前正是它导致加购后第一次关不掉)。 const getDiscountMatchPopupEl = () => document.querySelector('.js-discount-match-popup'); class SpzCustomDiscountDefault extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.discountId = ''; // 活动 id this.discountType = ""; this.discountI18n = {}; this.bundleProducts = []; this.tabContentIdMap = {}; this.currentTab = E_TAB_MAP.scenario_buy.value; this.modelMap = { [E_TAB_MAP.scenario_buy.value]: {}, [E_TAB_MAP.scenario_obtain.value]: {} } } static deferredMount() { return false; } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); this.initTabContentMap_(); SPZUtils.Event.listen(document.getElementById('discount-match-mask'), 'click', (e) => { e.stopPropagation(); this.handleCloseDiscountDrawer_(); }); } initTabContentMap_() { Object.entries(E_TAB_MAP).forEach(([key, valueObj]) => { this.tabContentIdMap[valueObj.value] = valueObj.domId; }); } // 初始化 initData_(data) { this.discountId = data.discount_info.id; this.discountType = data.discount_info.discount_type; const model_buy_map = { discount_id: this.discountId, //活动id scenario: 1, // 枚举值,1:购买商品,2:获得商品 sort: { by :"recommend", direction :"asc"}, page: 2, //分页码 limit: 20, // 每页数量 loading: false, // 请求数据标示 has_more: true // 是否还有数据 }; const model_get_map = { discount_id: this.discountId, scenario: 2, sort: { by :"recommend", direction :"asc"}, page: 2, limit: 20, loading: false, has_more: true }; this.modelMap = { [E_TAB_MAP.scenario_buy.value]: model_buy_map, [E_TAB_MAP.scenario_obtain.value]: model_get_map } this.renderSort_(); } getDiscountI18n_() { this.xhr_.fetchJson(API.DISCOUNT_I18N, { method: "GET", }).then((res)=>{ this.discountI18n = res; this.bindEvent(); }); } renderSort_() { const renderSort = (sortId, model) => { const $sortElement = document.getElementById(sortId); if ($sortElement) { SPZ.whenApiDefined($sortElement).then((api) => { const defaultValue = `${model.sort.by}_${model.sort.direction}` || "title_asc"; api.doRender_({ options: SORT_OPTIONS, defaultValue, id: sortId }); }); } }; renderSort('spz_custom_sort_products_x', this.modelMap[E_TAB_MAP.scenario_buy.value]); renderSort('spz_custom_sort_products_y', this.modelMap[E_TAB_MAP.scenario_obtain.value]); } // 获取数据,拼接html模板 async getData(cb) { // 请求数据 const model = this.modelMap[this.currentTab]; if (!model.has_more || model.loading) return; model.loading = true; this.handleLoading_({type: 'product', action: 'show'}); const $root = getDiscountMatchPopupEl() || document; const $content = $root.querySelector(`#${this.tabContentIdMap[this.currentTab]} .discount-default__productlist-wrap`) || $root.querySelector(`.discount-default__productlist-wrap`); //查询活动商品接口 const reqBody = { discount_id: model.discount_id, page: model.page, limit: model.limit, apply_scenario: model.scenario, sort: model.sort, sales_channel: { sale_channel_type: "online", sale_channel_id: SHOP_ID } } this.xhr_.fetchJson(API.PRODUCT_LIST, { method: "POST", body: reqBody }).then(async(res)=>{ this.handleLoading_({type: 'product', action: 'close'}); const count = res.count; model.has_more = res.has_more; if (count > 0) { model.page++; if (res.products && res.products.length > 0) { const products = res.products.map((product) => { return { ...product, discount_id: model.discount_id, apply_scenario: model.scenario, url: appDiscountUtils.globalizePath(product.url), image_padding_bottom: appDiscountUtils.image_padding_bottom(product.image.width, product.image.height,'no-limit'), discount_type: this.discountType } }); // 获取商品列表渲染模板, dom挂载 const renderApi = await SPZ.whenApiDefined(document.querySelector('#app-discount_match_drawer_products_render')); const el = await renderApi.getRenderTemplate({ products: products, discountI18n: this.discountI18n }); const childNodes = el.querySelectorAll('.as-render-product-item'); if (childNodes && childNodes.length > 0) { $content.append(...el.childNodes); } this.bundleProducts = [...this.bundleProducts, ...res.products]; SPZ.whenApiDefined(document.getElementById("appDiscountBundleProductsFunc")).then((api) => { api.setBundleData(this.bundleProducts); }) if(this.discountType === 'DT_MIX_MATCH_BUNDLE') { const productSelector = SPZCore.Dom.scopedQuerySelector(getDiscountMatchPopupEl() || document.body, `#promotionProductSelector`); productSelector && SPZ.whenApiDefined(productSelector).then((api) => { api.init(); }); } // 监听load去掉灰色背景 document.dispatchEvent(new CustomEvent('fire.load.img')); // 触发懒加载 cb && cb(products); window.lazyLoadInstance && window.lazyLoadInstance.update(); } else { } } model.loading = false; }).catch((err)=>{ console.error(err); model.loading = false; this.handleLoading_({type: 'product', action: 'close'}); }).finally(() => { if(this.discountType === 'DT_MIX_MATCH_BUNDLE') { SPZ.whenApiDefined(document.getElementById("appDiscountBundleProductsFunc")).then((api) => { api.handleChangeSort(); }) } }) } bindEvent() { // 监听滚动,请求数据 const $el = document.querySelector(".discount-match-drawer-content"); if ($el) { $el.addEventListener("scroll", this.win.SPZCore.Types.debounce(this.win, () => { const model = this.modelMap[this.currentTab]; if (!model.loading && model.has_more && appDiscountUtils.isToPageEnd(`[data-drawer-id='${this.discountId}']`)) { this.getData(); } }, 50)); } window.addEventListener("resize", this.setMatchDrawerHeight_); } // 商品排序 handleSort_(data) { const sortKey = data.value; this.modelMap[this.currentTab].sort = SORT_DICT[sortKey || 'title_asc']; this.modelMap[this.currentTab].page = 1; this.modelMap[this.currentTab].has_more = true; this.clearProductListDom_(); this.getData((products) => {}); } // tab 切换 tabChange_(value) { this.currentTab = value || E_TAB_MAP.scenario_buy.value; } setMatchDrawerHeight_() { //设置凑单弹窗高度,一些移动端浏览器上方帮助栏会影响视口大小,因此使用window.innerHeight //注意:resize 监听未 bind this,此函数不能依赖 this const modalContent = document.querySelector(".discount-match-drawer"); if (!modalContent) return; const wrapper = getDiscountMatchPopupEl(); const windowHeight = window.innerHeight; //运行时检测购物车抽屉是否仍打开 //不能写死 side="right":不同主题抽屉弹出方向不同(右侧 / 底部等),只按 cart_drawer //section 下是否有 open 的 sidebar 判定;宽度/定位后续统一用 getBoundingClientRect() 同步 const $drawer = document.querySelector('[data-section-type="cart_drawer"] spz-sidebar[open]'); if (!$drawer) { //购物车页场景:全屏底部弹层,默认 90vh 全宽 modalContent.style.maxHeight = windowHeight * 0.9 + "px"; modalContent.style.removeProperty("height"); modalContent.style.removeProperty("width"); wrapper && wrapper.classList.remove("in-cart-drawer"); return; } //抽屉场景:距顶 40px,并匹配抽屉宽度(靠右/提层级/2列由 in-cart-drawer 的 CSS 处理) const h = (windowHeight - 40) + "px"; modalContent.style.maxHeight = h; modalContent.style.height = h; //class 绑定抽屉存在性(保证靠右/提层级/2列生效),width 仅在可测量时再设, //避免 drawer 开启动画期间 rect.width 暂为 0 时弹窗以全宽浮在错误位置 wrapper && wrapper.classList.add("in-cart-drawer"); const rect = $drawer.getBoundingClientRect(); if (rect.width > 0) { modalContent.style.setProperty("width", rect.width + "px", "important"); } } showDiscountToast(message) { SPZ.whenApiDefined(document.getElementById("cart_match_discount_toast")).then((apis) => { apis.showToast(message); }); } handleLoading_(event) { const { type, action } = event; const loadingElementId = type === 'product' ? '#discount-match-drawer-products_loading' : '#apps-discount-whole-loading'; const loadingElement = document.querySelector(loadingElementId); if (loadingElement) { SPZ.whenApiDefined(loadingElement).then((api) => { if (action === 'show') { api.show_(); } else { api.close_(); } }); } } setupAction_() { // Buy X Get Y Tab 切换 this.registerAction('handleBuyXGetYTabChange', (invocation) => { const { panelId } = invocation.args.data; this.tabChange_(panelId); }); // 监听排序组件 选中选项 this.registerAction('handleSort', (invocation) => { const data = invocation.args.data; if (this.discountType === "DT_MIX_MATCH_BUNDLE") { const bundleFun = document.getElementById("appDiscountBundleProductsFunc"); SPZ.whenApiDefined(bundleFun).then((api) => { const oldProductInfo = api.productStyleInfo; const newData = oldProductInfo.filter(item => { const currentProductOption = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionSelectOption-${item.product_id}`); return currentProductOption && currentProductOption.hasAttribute('selected') }) api.init(newData); }) .then(() => { this.handleSort_(data); }) } else { this.handleSort_(data); } }); //初始化弹窗数据 this.registerAction('showDiscountDrawer', async (data) => { const discountId = data.args.discountId || ''; const activeTab = data.args.activeTab || E_TAB_MAP.scenario_buy.value; this.tabChange_(activeTab); this.triggerEvent_('setTab', { activeTab }); this.getDiscountI18n_(); this.getCampaignDetail_(discountId).then(() => { this.handleShowDiscountDrawer_(); }); }); // 关闭弹窗 this.registerAction('removeDiscountElement', () => { this.handleCloseDiscountDrawer_(); }); // 切换多个活动 this.registerAction('discountSelectorChange', (invocation) => { const { targetOption } = invocation.args.data; this.handleDiscountSelectorChange_(targetOption); }); } handleDiscountSelectorChange_(targetOption) { this.modelMap[this.currentTab].sort = SORT_DICT['title_asc']; this.modelMap[this.currentTab].page = 1; this.modelMap[this.currentTab].has_more = true; this.clearProductListDom_(); this.handleLoading_({ type: 'product', action: 'show' }); this.getCampaignDetail_(targetOption).finally(() => { this.handleLoading_({ type: 'product', action: 'close' }); }); } // 清空商品列表dom, 重新请求排序数据渲染 clearProductListDom_() { const $root = getDiscountMatchPopupEl() || document; const $productList = $root.querySelector(`#${this.tabContentIdMap[this.currentTab]} .discount-default__productlist-wrap`) || $root.querySelector(`.discount-default__productlist-wrap`); if ($productList) $productList.innerHTML = ''; } handleShowDiscountDrawer_() { this.setMatchDrawerHeight_(); const $el = getDiscountMatchPopupEl(); if (!$el) return; document.body.appendChild($el); $el.style.display="block"; document.documentElement.classList.add("i-spzhtml-scroll-disabled"); } handleCloseDiscountDrawer_() { const $drawer = getDiscountMatchPopupEl(); if ($drawer) $drawer.style.display = "none"; document.documentElement.classList.remove("i-spzhtml-scroll-disabled"); this.clearProductListDom_(); const bannerEL = document.querySelector(".discount_match_drawer_banner_inner"); const productEL = ($drawer || document).querySelector(".discount-default"); bannerEL && SPZCore.Dom.removeChildren(bannerEL); productEL && SPZCore.Dom.removeChildren(productEL); const bundleFun = document.getElementById("appDiscountBundleProductsFunc"); SPZ.whenApiDefined(bundleFun).then((api) => { api.init(); }) const bundleButtonEL = document.querySelector(".app-bundle-discount_bottom_container"); bundleButtonEL && SPZCore.Dom.removeChildren(bundleButtonEL); } getCampaignDetail_(discount_id) { return this.xhr_.fetchJson(API.CAMPAIGN + discount_id, { method: "GET" }).then((data) => { this.bundleProducts = data.buy_product_info.product; if(data.discount_info.progress == "PROGRESS_FINISHED") { this.showDiscountToast("The discount has finished"); return Promise.reject({}); } else { SPZ.whenApiDefined(document.getElementById("discount-match-drawer-container")).then(apis => { apis.render(data, true).then(() => { this.initData_(data); const type = data.discount_info.discount_type; document.querySelector(".discount-match-drawer").dataset.discountType = type; //给捆绑组件传值 if(type === "DT_MIX_MATCH_BUNDLE" || type === "DT_CLASSIC_BUNDLE") { // 重置 const bundleButtonEL = document.querySelector(".app-bundle-discount_bottom_container"); bundleButtonEL && SPZCore.Dom.removeChildren(bundleButtonEL); //购物车默认加购 data.landing_page_info.button.redirect_page = "cart"; SPZ.whenApiDefined(document.getElementById("appDiscountBundleProductsFunc")).then((api) => { api.init(); // 切换 Tab 重置数据 api.setBundleData(data.buy_product_info.product, data.landing_page_info, data.discount_info.id, data.discount_info.discount_type, data.discount_info); }); if(type === "DT_CLASSIC_BUNDLE") { SPZ.whenApiDefined((getDiscountMatchPopupEl() || document).querySelector("#promotionBottomContainer")).then((api) => { const buttonData = { setting: data.landing_page_info, ...data.buy_product_info.total_price } api.render(buttonData, true); }) } } }); }); } }).catch(e => { e.then((data) => { if(data.code == 10003001) { this.showDiscountToast("The discount has finished"); } }); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomDiscountDefault) (function () { const TAG = "spz-custom-render-products"; class SpzCustomProducts extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } doRender_(data) { return this.templates_ .findAndRenderTemplate(this.element, data) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } getRenderTemplate(data) { const renderData = data || {}; return this.templates_ .findAndRenderTemplate(this.element, renderData) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); return el; }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, '${ TAG }.${ name }', data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomProducts) })() const TAG = 'spz-custom-sort'; class SpzCustomSort extends SPZ.BaseElement { constructor(element) { super(element); this.spz_custom_id = ''; } static deferredMount() { return false; } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } init() { this.bindEvent(); } bindEvent() { const $selectList = SPZCore.Dom.scopedQuerySelectorAll( this.element, ".sort_custom_content li" ); const $customerSelect = SPZCore.Dom.scopedQuerySelector( this.element, ".sort_custom_select" ); // 选择下拉选项 Array.from($selectList).forEach((node) => { SPZUtils.Event.listen(node, 'click', ()=> { let value = node.getAttribute('value'); let text = node.getAttribute('text'); // 触发selectChange 事件 this.triggerEvent_('selectChange', { value: value, name: value }) $customerSelect.innerHTML = text; const panelChilds = this.element.querySelectorAll(".sort_custom_panel li"); // 清空其他选项的勾选状态 Array.from(panelChilds).forEach((el) => { if(el.getAttribute('value') == value) { el.classList.add("active") } else { el.classList.remove('active'); } }) }); }) } // 渲染界面 async doRender_(data) { // 操作该组件的dom id this.spz_custom_id = data.id; return this.templates_ .findAndRenderTemplate(this.element, data) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { this.init(); }); } setupAction_() { this.registerAction('render', async(invocation) => { const data = invocation.args.data; this.doRender_(data) }); this.registerAction('handleSelect', async(invocation) => { const data = invocation.args.data; }); this.registerAction('handleDropdownOpen', async(invocation) => { const $selectDropDown = SPZCore.Dom.scopedQuerySelector( this.element, ".select_drop_down" ); $selectDropDown.classList.add('select_drop_down_rotate'); }); this.registerAction('handleDropdownClose', async(invocation) => { const $selectDropDown = SPZCore.Dom.scopedQuerySelector( this.element, ".select_drop_down" ); $selectDropDown.classList.remove('select_drop_down_rotate'); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomSort) // 凑单弹窗与首页限时促销卡片共用样式 id "discount-match-drawer",会产生重复 id。 // 用弹窗根节点上专用的 js- 前缀钩子类唯一定位作用域根,避免命中首页卡片导致弹窗内元素查询失败。 const getDiscountMatchPopupEl = () => document.querySelector('.js-discount-match-popup'); class SpzCustomDiscountBundleProducts extends SPZ.BaseElement { constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); this.getDiscountPriceApi = "\/api\/storefront\/promotion\/calculate\/discounted_price"; this.buyNowApi = "\/api\/checkout\/order"; this.batchAtcApi = "\/api\/cart\/batch"; // 款式信息集合 this.productStyleInfo = []; // 弹窗内选择款式集合 this.modalVariantInfo = []; this.show_classic_bundle_spu_style = false; this.bundleProducts = []; //捆绑商品 this.bundleConfig = {}; //下方按钮配置 this.discountId = ""; this.discountType = ""; this.discountInfo = ""; this.lineItems = []; this.tempCss = {}; this.renderQuickShop_ = this.win.SPZCore.Types.debounce(this.win, this.renderQuickShopModal.bind(this), 500); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.setupAction_(); }; init(data = []) { this.productStyleInfo = data; } handleRequestError_(data) { this.showToast(data?.message || data?.errors?.[0] || 'Unknown error'); }; showToast(message) { SPZ.whenApiDefined(document.getElementById("cart_match_discount_toast")).then((apis) => { apis.showToast(message); }); } //外部组件调用传值 setBundleData(products, config = "", id = "", type = "", info = {}) { this.bundleProducts = products; if(config) { this.bundleConfig = config; this.discountId = id; this.discountType = type; this.discountInfo = info; this.show_classic_bundle_spu_style = type === 'DT_CLASSIC_BUNDLE' && info.enable_min_purchase_qty && info.min_purchase_qty_type == 'spu'; // 经典捆绑初始化商品数据 if(type == 'DT_CLASSIC_BUNDLE') { this.productStyleInfo = products.map((item) => { return this.getFilteredVariants_(item, 'single'); }); } } } handleChangeSort() { const result = this.productStyleInfo.reduce((map, item) => { if (!map[item.product_id]) { map[item.product_id] = []; } map[item.product_id].push(item); return map; }, {}); Object.values(result).forEach((item) => { this.handleSpzVariantRender_(item, item[0].product_id); this.handleProductOption_(item[0].product_id, true); }); } // 调用spz-tag组件的doRender方法 handleSpzVariantRender_(data, id) { const spzVariantTag = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionSpzVariantTags-${id}`); spzVariantTag && SPZ.whenApiDefined(spzVariantTag).then((api) => { api.render(data, true); }); } // 执行经典捆绑最低购买数量更新 handleMinPurchaseQtyUpdate_(data, id) { const minPruchaseQty = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionMinPurchaseQty-${id}`); minPruchaseQty && SPZ.whenApiDefined(minPruchaseQty).then((api) => { api.render(data, true); }); } // 更新价格 updateProductPrice_(data) { const bottomBtnContainer = SPZCore.Dom.scopedQuerySelector(getDiscountMatchPopupEl() || document.body, `#promotionBottomContainer`); if (data.length == 0) { bottomBtnContainer && SPZ.whenApiDefined(bottomBtnContainer).then((api) => { const renderInfo = { setting: this.bundleConfig, ...{ original_price: 0, received_discounts: 0, picked_qty: 0 } } api.render({original_price: 0, received_discounts: 0}, true); }); return; } const reqBody = { discount_id: this.discountId, customer: { customer_id: '', email: '', }, sales_channel: { sale_channel_type: "online", sale_channel_id: '1248245' }, line_items: data } // 如果已经有一个请求在等待,那么取消这个请求 if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(() => { this.xhr_.fetchJson(this.getDiscountPriceApi, { method: "post", body: reqBody }).then((res)=>{ // 更新商品列表价格 Object.keys(res.line_items).forEach((key) => { const currentProductPrice = SPZCore.Dom.scopedQuerySelector(document.body, `#appDiscountProductPrice-${key}`); currentProductPrice && SPZ.whenApiDefined(currentProductPrice).then((api) => { api.render(res.line_items[key], true); }); }); // 更新底部按钮总价/总折扣价 const picked_qty = data.reduce((acc, item) => { return acc + item.quantity; }, 0); bottomBtnContainer && SPZ.whenApiDefined(bottomBtnContainer).then((api) => { const data = { setting: this.bundleConfig, ...{ ...res.total_price, picked_qty } } api.render(data, true); }); }).catch((err)=>{ this.handleRequestError_(err); }).finally(()=>{ }) }, 100); } // 还原商品价格 resetProductPrice_(data) { const {price, compare_at_price, id} = data; const currentProductPrice = SPZCore.Dom.scopedQuerySelector(document.body, `#appDiscountProductPrice-${id}`); currentProductPrice && SPZ.whenApiDefined(currentProductPrice).then((api) => { api.render({total_received_discounts: price, total_price: compare_at_price}, true); }); } //处理与selector组件的交互 handleProductOption_(productId, show) { const currentProductOption = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionSelectOption-${productId}`); if (!currentProductOption) { return; }; currentProductOption.toggleAttribute('show', show); const isSelected = currentProductOption.hasAttribute('selected'); // !show 取消选中 // !isSelected 选中商品 if (!show || !isSelected) { const productSelector = SPZCore.Dom.scopedQuerySelector(getDiscountMatchPopupEl() || document.body, `#promotionProductSelector`); productSelector && SPZ.whenApiDefined(productSelector).then((api) => { api.toggle_({ option: productId, value: show }); }); } } // 混搭弹窗内的前端库存校验 handleModalInventoryCheck_(data) { if(this.discountType == 'DT_MIX_MATCH_BUNDLE' || this.discountType == 'DT_CLASSIC_BUNDLE') { const currentVariantAddNum = this.modalVariantInfo.find((item) => item.variant_id == data.variant_id)?.quantity || 0; const quickShopBody = SPZCore.Dom.scopedQuerySelector(document.body, '#apps-discount-quick-shop-body'); if(!!data.variant && currentVariantAddNum == Number(data.variant.available_quantity)) { quickShopBody && quickShopBody.setAttribute('status', 'soldout'); } else { quickShopBody && quickShopBody.setAttribute('status', 'available'); } } else { return; } } // 添加商品子款式 renderVariantTag() { let variantInfo; const quickShopBody = SPZCore.Dom.scopedQuerySelector(document.body, '#apps-discount-quick-shop-body'); quickShopBody && SPZ.whenApiDefined(quickShopBody).then((api) => { variantInfo = api.getVariantsData(); const productId = variantInfo.product_id; const variantId = variantInfo.variant_id; const minPruchaseQtyRender = variantInfo.product.discount_min_purchase_qty || variantInfo.variant.discount_info.discount_min_purchase_qty; if(this.discountType === 'DT_MIX_MATCH_BUNDLE') { const index = this.productStyleInfo.findIndex((item) => item.variant_id == variantInfo.variant_id); if (index != -1) { this.productStyleInfo[index].quantity = Number(this.productStyleInfo[index].quantity) + Number(variantInfo.quantity); this.updateProductPrice_(this.productStyleInfo); } else { this.productStyleInfo.push(this.getFilteredVariants_(variantInfo)); // 若当前商品已选中,更新商品价格 const currentProductOption = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionSelectOption-${productId}`); const isSelected = currentProductOption && currentProductOption.hasAttribute('selected'); isSelected && this.updateProductPrice_(this.productStyleInfo); } const selectedVariantsFilter = this.productStyleInfo.filter((item) => item.product_id == productId); this.handleSpzVariantRender_(selectedVariantsFilter, productId); this.handleProductOption_(productId, true); } else { if(this.discountInfo.enable_min_purchase_qty == true && this.discountInfo.min_purchase_qty_type == 'spu' && minPruchaseQtyRender > 1) { const index = this.modalVariantInfo.findIndex((item) => item.variant_id == variantId); if (index != -1) { this.modalVariantInfo[index].quantity = Number(this.modalVariantInfo[index].quantity) + 1; } else { this.modalVariantInfo.push(this.getFilteredVariants_(variantInfo, 'classic_spu')); } const modalVariantTag = SPZCore.Dom.scopedQuerySelector(getDiscountMatchPopupEl() || document.body, '#promotionModalVariantTagRender'); modalVariantTag && SPZ.whenApiDefined(modalVariantTag).then((api) => { api.render(this.modalVariantInfo, true); }); this.handleModalInventoryCheck_(variantInfo); const selectedVariantsNum = this.modalVariantInfo.reduce((acc, item) => { return acc + item.quantity; }, 0); if(selectedVariantsNum == minPruchaseQtyRender) { this.handleSpzVariantRender_([this.getFilteredVariants_(variantInfo)], productId); this.productStyleInfo = this.productStyleInfo.filter((item) => item.product_id != productId).concat(this.modalVariantInfo); const renderData = this.productStyleInfo.filter((item) => item.product_id == productId).map((item) => { return { ...item, is_classic_bundle_product_list_variant_tag: true } }); const classicSpuTag = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionClassicSpuTags-${productId}`); classicSpuTag && SPZ.whenApiDefined(classicSpuTag).then((api) => { api.render(renderData, true); }); this.updateProductPrice_(this.productStyleInfo); const quickView = SPZCore.Dom.scopedQuerySelector(document.body, '#apps-discount-quick-view'); quickView && SPZ.whenApiDefined(quickView).then((api)=>{ api.close(); }); this.modalVariantInfo = []; } else { return; } } // this.productStyleInfo 中已存在与productId, variantId都相同的商品 则直接return 关闭弹窗 const isExist = this.productStyleInfo.some((item) => item.product_id == productId && item.variant_id == variantId); if (isExist) { const quickView = SPZCore.Dom.scopedQuerySelector(document.body, '#apps-discount-quick-view'); quickView && SPZ.whenApiDefined(quickView).then((api)=>{ api.close(); }); return; } // 若 this.productStyleInfo 中已存在与productId相同的商品,则不再添加 否则替换 const index = this.productStyleInfo.findIndex((item) => item.product_id == productId); if (index != -1) { this.productStyleInfo[index] = this.getFilteredVariants_(variantInfo); } else { this.productStyleInfo.push(this.getFilteredVariants_(variantInfo)); } const selectedVariantsFilter = this.productStyleInfo.filter((item) => item.product_id == productId); this.handleSpzVariantRender_(selectedVariantsFilter, productId); this.handleMinPurchaseQtyUpdate_({discount_min_purchase_qty: minPruchaseQtyRender}, productId); this.updateProductPrice_(this.productStyleInfo); } const quickView = SPZCore.Dom.scopedQuerySelector(document.body, '#apps-discount-quick-view'); quickView && SPZ.whenApiDefined(quickView).then((api)=>{ api.close(); }); }); } // 单变体点击添加按钮 renderSingleVariant(data) { const { product_id } = data; const currentProduct = this.bundleProducts.find((product) => product.id == product_id); // 若当前商品已存在,则不再添加 而是更新数量 const index = this.productStyleInfo.findIndex((item) => item.product_id == product_id); if (index != -1) { this.productStyleInfo[index].quantity = Number(this.productStyleInfo[index].quantity) + 1; this.updateProductPrice_(this.productStyleInfo); } else { this.productStyleInfo.push(this.getFilteredVariants_(currentProduct, 'single')); } const renderProductArr = this.productStyleInfo.filter((item) => item.product_id == product_id); this.handleSpzVariantRender_(renderProductArr, product_id); this.handleProductOption_(product_id, true); } // 过滤选中商品的子款式 获取有用的信息 product_id,variant_id,price,compare_at_price,quantity,title,variant_title getFilteredVariants_(data, type = '') { const { id, title, variants, inventory_tracking, inventory_policy, inventory_quantity, product_type } = data; const { product_id, variant_id, variant, quantity, product, discount_min_purchase_qty } = data; const isSingle = type == 'single'; const variantData = isSingle ? (variants[0] || data) : variant; const productData = isSingle ? data : product; let item_quantity = 0; if (this.discountType === 'DT_MIX_MATCH_BUNDLE') { item_quantity = isSingle ? 1 : Number(quantity); } else if (type === 'classic_spu') { item_quantity = 1; } else { item_quantity = discount_min_purchase_qty || productData.discount_min_purchase_qty || variantData.discount_info.discount_min_purchase_qty || 1; } return { product_id: isSingle ? id : product_id, variant_id: variantData?.id || '', price: variantData?.price || '0.00', compare_at_price: variantData?.compare_at_price || '0.00', quantity: item_quantity, inventory_tracking: productData.inventory_tracking, inventory_policy: productData.inventory_policy, inventory_quantity: productData.inventory_quantity, product_type: productData.product_type || this.bundleProducts.find((item) => item.id == product_id)?.product_type || this.bundleProducts.find((item) => item.id == id)?.product_type || '', title: productData.title, variant_title: variantData?.options.map((option) => option.value).join('/') || '', is_multi_style: productData.variants.length > 1, } } handleLoading_ (event) { const { type, action } = event; const loadingElementId = type === 'product' ? '#discount-match-drawer-products_loading' : '#apps-discount-whole-loading'; const loadingElement = document.querySelector(loadingElementId); if (loadingElement) { SPZ.whenApiDefined(loadingElement).then((api) => { if (action === 'show') { api.show_(); } else { api.close_(); } }); } } handleSelectProduct(productArr) { // 从this.productStyleInfo 过滤出选中的商品 const selectedProducts = this.productStyleInfo.filter((item) => productArr.includes(item.product_id)); this.updateProductPrice_(selectedProducts); } // 渲染加购弹窗内容 async renderQuickShopModal(data){ this.handleLoading_({type: 'whole', action: 'show'}); this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product?product_id=${data.product_id}&discount_id=${this.discountId}&apply_scenario=1`, { method: "get", }).then(async(res)=>{ //flash主题放block有层级问题 if(/Flash/.test(window.C_SETTINGS.theme.merchant_theme_name) && document.querySelector(".productInfoSection")) { this.tempCss.zIndex = document.querySelector(".product-info-body").style.zIndex; document.querySelector('.product-info-body').style.zIndex="1048"; } this.handleLoading_({type: 'whole', action: 'close'}); const $quickShop = await SPZ.whenApiDefined(document.querySelector('#apps-discount-quick-view-render')); // 定义默认渲染的子款式 const selectedVariant = res.product.variants.find((v)=> (v.available && v.is_hit_discount)) || res.product.variants[0]; let selectedValues = {}; selectedVariant.options.length && selectedVariant.options.forEach(item => { selectedValues[item.name] = item.value; }) // 默认选中的 子款式、 options res.product.defaultSelectValues = selectedValues; let data = {...res.product, product:res.product, selectedVariant, show_classic_bundle_spu_style: this.show_classic_bundle_spu_style, discountType: this.discountType}; $quickShop.render(data); // 打开加购弹窗 SPZ.whenApiDefined(document.querySelector(`#apps-discount-quick-view`)).then((api)=>{ api.open(); }); }).catch((err)=>{ this.handleLoading_({type: 'whole', action: 'close'}); }) } // 获取选中的商品ids getDefaultSelectorOptions_() { try { const selectedOptions = SPZCore.Dom.scopedQuerySelectorAll(document.body, '[id^="promotionSelectOption-"][selected]'); return SPZCore.Types.toArray(selectedOptions).map((item) => item.getAttribute('option')); } catch (error) { return []; } } // 删除商品子款式 deleteVariantTag(data) { const { product_id, variant_id } = data; if(this.discountInfo.enable_min_purchase_qty == true && this.discountInfo.min_purchase_qty_type == 'spu') { const modalProductVariants = this.modalVariantInfo.filter((item) => item.product_id == product_id && item.variant_id != variant_id); const modalVariantTag = SPZCore.Dom.scopedQuerySelector(getDiscountMatchPopupEl() || document.body, '#promotionModalVariantTagRender'); modalVariantTag && SPZ.whenApiDefined(modalVariantTag).then((api) => { api.render(modalProductVariants, true); }); this.handleModalInventoryCheck_(data); this.modalVariantInfo = modalProductVariants; return; } const currentProductVariants = this.productStyleInfo.filter((item) => item.product_id == product_id && item.variant_id != variant_id); this.handleSpzVariantRender_(currentProductVariants, product_id); // 更新selectedVariants this.productStyleInfo = this.productStyleInfo.filter((item) => item.variant_id != variant_id); if(currentProductVariants.length > 0) { // currentProductVariants 中只要有一项是多款式商品,就更新价格 const isMultiStyle = currentProductVariants.some((item) => item.is_multi_style); if (!isMultiStyle) return; this.handleProductOption_(product_id, true); const selected = this.getDefaultSelectorOptions_(); this.updateProductPrice_(this.productStyleInfo.filter((item) => selected.includes(item.product_id))); } else { this.handleProductOption_(product_id, false); this.resetProductPrice_(this.bundleProducts.find((item) => item.id == product_id)); } } // 加购弹窗未参与活动 加购按钮不可点击 TODO 拆出来 handleNotHitDiscount_(data) { const $quickShopBody = document.querySelector('#apps-discount-quick-shop-body'); //当前子框式未命中活动 if(data.variant.is_hit_discount == false) { $quickShopBody.setAttribute('variantstatus', 'notHitDiscount') } else { $quickShopBody.setAttribute('variantstatus', '') } } setupAction_() { // 子款式 未参与活动 this.registerAction('handleNotHitDiscount', (invocation) => { const data = invocation.args.data; this.handleNotHitDiscount_(data); }); // 渲染加购弹窗 this.registerAction('renderQuickShop', (invocation) => { const data = invocation.args; this.renderQuickShop_(data); }); this.registerAction('renderSingleVariant', (invocation) => { const data = invocation.args; this.renderSingleVariant(data); }); this.registerAction('getVariantInfo', (invocation) => { this.renderVariantTag(); }); this.registerAction('deleteVariantTag', (invocation) => { const data = invocation.args; this.deleteVariantTag(data); }); this.registerAction('getSelectedProduct', (invocation) => { const data = invocation.args.data; this.handleSelectProduct(data); }); //TODO 加购下单逻辑单独拆组件 this.registerAction('handleClick', (data) => { if(this.discountType == 'DT_CLASSIC_BUNDLE') { this.lineItems = this.productStyleInfo; } else { const selectedOptions = SPZCore.Dom.scopedQuerySelectorAll(document.body, '[id^="promotionSelectOption-"]'); const idArr = [...selectedOptions].reduce((acc, item) => { if (item.hasAttribute('selected')) { const optionValue = item.getAttribute('option'); if (optionValue) { acc.push(optionValue); } } return acc; }, []); this.lineItems = this.productStyleInfo.filter((item) => idArr.includes(item.product_id)); } const action = data.args.action === "cart"; if(action) { //add to cart this.xhr_ .fetchJson(this.batchAtcApi, { method: 'POST', body: { line_items: this.lineItems.map((item) => { return { product_id: item.product_id, variant_id: item.variant_id, quantity: Number(item.quantity) } }) } }) .then((data) => { setTimeout(() => { window.location.href = '/cart'; }); }) .catch(async (error) => { await error.then((data) => { this.handleRequestError_(data); }); }); } else { //checkout this.xhr_ .fetchJson(this.buyNowApi, { method: 'POST', body: { line_items: (this.lineItems || []).map((product) => { return { quantity: Number(product.quantity), variant_id: product.variant_id, note: product.note || '', properties: product.properties || {} } }), refer_info: { source: 'buy_now' } } }) .then(async (data) => { if (data.state === 'success') { window.location.href = data.data?.checkout_url; } this.handleRequestError_(data); }) .catch(async (error) => { await error.then((data) => { this.handleRequestError_(data); }); }); } }); this.registerAction('resetModalVariantInfo', () => { //flash主题放block有层级问题 if(/Flash/.test(window.C_SETTINGS.theme.merchant_theme_name) && document.querySelector(".productInfoSection")) { document.querySelector('.product-info-body').style.zIndex = this.tempCss.zIndex; } this.modalVariantInfo = []; }); this.registerAction('handleModalInventoryCheck', (invocation) => { const data = invocation.args.data; this.handleModalInventoryCheck_(data); }); }; }; SPZ.defineElement('spz-custom-discount-bundle-products', SpzCustomDiscountBundleProducts); class SpzCustomDiscountAtcAction extends SPZ.BaseElement { constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); this.product_title = ""; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } async getProductTitle_ (data){ this.product_title = data.product_title; } handleLoading_ (event) { const { type, action } = event; const loadingElementId = '#apps-discount-whole-loading'; const loadingElement = document.querySelector(loadingElementId); if (loadingElement) { SPZ.whenApiDefined(loadingElement).then((api) => { if (action === 'show') { api.show_(); } else { api.close_(); } }); } } // 渲染加购弹窗内容 async renderQuickShop_ (data) { this.handleLoading_({type: 'whole', action: 'show'}); const apply_scenario = data.apply_scenario; const discount_id = data.discount_id; const product_id = data.product_id; const limit_purchase = data.limit_purchase; this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product?product_id=${product_id}&discount_id=${discount_id}&apply_scenario=${apply_scenario}`, { method: "get", }).then(async(res)=>{ this.handleLoading_({type: 'whole', action: 'close'}); const $quickShop = await SPZ.whenApiDefined(document.querySelector('#apps-discount-quick-view-render')); // 定义默认渲染的子款式 const selectedVariant = res.product.variants.find((v)=> (v.available && v.is_hit_discount)) || res.product.variants[0]; let selectedValues = {}; selectedVariant.options.length && selectedVariant.options.forEach(item => { selectedValues[item.name] = item.value; }) // 默认选中的 子款式、 options res.product.defaultSelectValues = selectedValues; let data = {...res.product, product:res.product, selectedVariant}; data.need_atc = true; data.limit_purchase = limit_purchase; $quickShop.render(data); // 打开加购弹窗 SPZ.whenApiDefined(document.querySelector(`#apps-discount-quick-view`)).then((api)=>{ api.open(); }); }).catch((err)=>{ this.handleLoading_({type: 'whole', action: 'close'}); }) } // 加入购物车 addToCart_(data) { const apply_scenario = data.apply_scenario; const discount_id = data.discount_id; const product_id = data.product_id; this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product?product_id=${data.product_id}&discount_id=${discount_id}&apply_scenario=${apply_scenario}`, { method: "get", }).then(async(res)=>{ // 单款式加购 const variant_id = res.product.variants[0]?.id; const $productFormInput = document.querySelector(`#apps-discount-product-form-${data.product_id} input[name='variant_id'`); $productFormInput.value = variant_id; SPZ.whenApiDefined(document.querySelector(`#apps-discount-product-form-${data.product_id}`)).then((api)=>{ api.handleAddToCart_(); }); }).catch((err)=> { this.handleLoading_({type: 'whole', action: 'close'}); }) } // 加购弹窗未参与活动 加购按钮不可点击 handleNotHitDiscount_(data) { const $quickShopBody = document.querySelector('#apps-discount-quick-shop-body'); const $limitTip = document.querySelector('.apps_discount_limit_purchase_tip'); //当前子框式未命中活动 if(data.variant.is_hit_discount == false) { $quickShopBody.setAttribute('variantstatus', 'notHitDiscount'); $limitTip && $limitTip.setAttribute('selectedvariantstatus', 'notHitDiscount'); } else { $quickShopBody.setAttribute('variantstatus', ''); $limitTip && $limitTip.setAttribute('selectedvariantstatus', '') } } setupAction_() { this.registerAction('renderQuickShop', (invocation) => { const data = invocation.args; this.renderQuickShop_(data); }); // 加购 this.registerAction('addToCart', (invocation) => { const data = invocation.args; this.addToCart_(data); }); // 子款式 未参与活动 this.registerAction('handleNotHitDiscount', (invocation) => { const data = invocation.args.data; this.handleNotHitDiscount_(data); }); this.registerAction('getCartCount', (invocation) => { //一些老主题还未用spz重构,需要手动触发一次老方法以更新购物车图标的数量 window.$ && $(document).trigger('dj.common.cart.change'); }); this.registerAction('getProductTitle', (invocation) => { const data = invocation.args; this.getProductTitle_(data); }); this.registerAction('handleButton', (invocation) => { const data = invocation.args; window.location.href = this.product_title; }); }; buildCallback() { this.setupAction_(); }; // 同一页里 flashsale_collection(首页限时促销卡片) 与凑单弹窗容器都渲染了本快速加购栈, // 会产生重复 fixed id——尤其加购弹窗 #apps-discount-quick-view。它“打开”走 querySelector, // “关闭”走声明式动作 apps-discount-quick-view.close(按 id 解析),重复时二者可能命中不同副本, // 导致加购成功后弹窗第一次关不掉、第二次才关。参考 toast 的处理,运行时保证这些单例只保留一份。 mountCallback() { const funcs = document.querySelectorAll('#appDiscountRenderQuickShopFunc'); // 逻辑元件重复:仅保留第一个,其余移除自身,保证按 id 解析到的动作实例唯一。 if (funcs.length > 1 && this.element !== funcs[0]) { this.element.remove(); return; } // 加购弹窗重复:保留第一个,移除多余(移除弹窗即连带移除其内部 render/表单等子节点)。 const modals = document.querySelectorAll('#apps-discount-quick-view'); Array.from(modals).slice(1).forEach((el) => el.remove()); } } SPZ.defineElement('spz-custom-discount-atc-action', SpzCustomDiscountAtcAction);
const TAG = 'spz-custom-discount-cart-banner-info'; const API = { DISPLAY_TEXT: "\/api\/storefront\/promotion\/cart_page\/display_text", CART_SELECT: "\/api\/cart\/cart-select?r=", } class SpzCustomDiscountCartBannerInfo extends SPZ.BaseElement { constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); this.displayTextData = {}; // 活动数据文案数据 this.selectedCategory = ''; // 当前选中的分类 this.selectedDiscountId = ''; // 当前选中的折扣 id this.codes = []; // 优惠码数组 } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } mountCallback() { this.getDisplayText_(); SPZUtils.Event.listen(document, 'dj.cartChange', (e) => { this.getDisplayText_(); }); //该事件又购物车优惠码组件触发,传参为当前应用的优惠码数组 SPZUtils.Event.listen(document, 'dj.applyDiscountCode', (e) => { this.codes = e.detail.codes; this.getDisplayText_(); }); this.registerAction('renderDiscountSelectors', (invocation) => { const selected = invocation.args?.data; const groups = this.displayTextData.groups || []; const discounts = groups.find(v => v.category == selected)?.discounts || []; // 当前分类下的折扣 const defaultDiscountId = discounts[0]?.discount_id; // 默认选中第一个折扣 this.selectedCategory = selected; this.selectedDiscountId = defaultDiscountId; this.renderDiscountSelectors_(); // 渲染 Tab this.renderDiscountText_(); // 渲染活动进度文案 }); this.registerAction('discountSelectorChange', (invocation) => { const { targetOption } = invocation?.args?.data || {}; this.selectedDiscountId = targetOption; this.renderDiscountText_(); // 切换活动更新活动进度文案 }); } getLineItems_(line_items) { return line_items.map((item) => { return { id: item.id, product_id: item.product_id, variant_id: item.variant_id, quantity: item.quantity, price: item.price, origin_price: item.origin_price, compare_at_price: item.compare_at_price, trunk_price: item.trunk_price, total_price: item.total_price, inventory_tracking: item.inventory_tracking, inventory_policy: item.inventory_policy, inventory_quantity: item.inventory_quantity, custom: item.custom, product_type: item.product_type, properties: item.properties }; }); } getDisplayText_(redo = true) { this.xhr_.fetchJson(API.CART_SELECT + Math.random().toString(36).slice(-4), { method: "GET" } ).then(async (cartData) => { const cartObj = cartData.cart || {}; if(!cartObj || cartObj.line_items?.length <= 0) { // 清空购物车时,清空活动数据 this.displayTextData = {}; this.selectedCategory = ''; this.selectedDiscountId = ''; this.renderDiscountBanner_(); return; } this.xhr_.fetchJson(API.DISPLAY_TEXT, { method: "POST", body: { line_items: this.getLineItems_(cartObj.line_items), sales_channel: { sale_channel_type: "online", sale_channel_id: window.C_SETTINGS.shop.shop_id }, codes: this.codes || [] } }).then(data => { this.displayTextData = data; this.renderDiscountBanner_(redo); // 凑单弹窗加购商品后,重新获取活动进度文案 this.renderDiscountText_(redo); }).catch(err => { }); }); } renderDiscountBanner_(redo = true) { const bannerEl = document.getElementById("app-discount-cart-banner"); if (bannerEl) { SPZ.whenApiDefined(bannerEl).then(apis => { apis.render(this.displayTextData, redo); }); } } renderDiscountSelectors_(redo = true) { const groups = this.displayTextData.groups || []; const discounts = groups.find(v => v.category == this.selectedCategory)?.discounts || []; if (discounts.length <= 0) { return; } const selectorsEl = document.getElementById("app-discount-selectors-render"); if (selectorsEl) { SPZ.whenApiDefined(selectorsEl).then(apis => { apis.render(discounts, redo); }); } } renderDiscountText_(redo = true) { if (!this.selectedCategory || !this.selectedDiscountId) { return; } const groups = this.displayTextData.groups || []; const discounts = groups.find(v => v.category == this.selectedCategory)?.discounts || []; const discount = discounts.find(v => v.discount_id == this.selectedDiscountId) || {}; const textEl = document.getElementById("app-discount-text-render"); if (textEl) { SPZ.whenApiDefined(textEl).then(apis => { apis.render(discount, redo); }); } } } SPZ.defineElement(TAG, SpzCustomDiscountCartBannerInfo);

View cart