- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
NEW COLLECTION
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
${data?.title || 'Product Title'}
From
Sold Out
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
${data?.title || 'Product Title'}
From
Sold Out
${(()=> { const ddValue = dd>999?999:dd==0?0:dd; const ddStr = String(ddValue).padStart(2,'0'); return ddStr.split('').map(digit => '' + digit + '').join(''); })()}Days
: ${(()=>{ let result; if(false){ result = Number(dd)*24+Number(hh); result = result>999?999:result==0?0:result; }else{ result = hh; } const resultStr = String(result).padStart(2,'0'); return resultStr.split('').map(digit => '' + digit + '').join(''); })()} Hrs
: ${(()=> { const mmStr = String(mm).padStart(2,'0'); return mmStr.split('').map(digit => '' + digit + '').join(''); })()} Mins
: ${(()=> { const ssStr = String(ss).padStart(2,'0'); return ssStr.split('').map(digit => '' + digit + '').join(''); })()} Secs
Seasonal Sales
Sale Up to 50%
Seize the season's best with up to 50% off on our exclusive sale items.
- ${Array(data.total || 0).fill().map((val, idx) => idx).map((val, idx) => ` `).join('')}
${data?.title || 'Product Title'}
From
Sold Out
${function(){
let hour = data.d * 24 + data.h;
if(hour < 10){
hour = '0' + hour;
}
return `
`
}()}
${function() {
const selectedVariant = data.variant || data.variants[0];
const image = selectedVariant.image || data.product.image;
const imageWidth = image?.width || 120;
const imageHeight = image?.height || 120;
return `
`
}()}
${function() {
const defaultVariant = data.variants?.find(v => (v.available && v.is_hit_discount == true)) || data.variants?.[0];
const selectedVariant = data.variant || defaultVariant;
const isHasRrice = (selectedVariant.price || selectedVariant.price == 0) ? true : false;
if(selectedVariant.flash_sale_info && selectedVariant.flash_sale_info.discount_price) {
selectedVariant.price = selectedVariant.flash_sale_info.discount_price;
}
if(data.flash_sale_info && data.flash_sale_info.discount_price) {
data.price = data.flash_sale_info.discount_price;
}
return !!selectedVariant ? `
` : `
-
`;
}()}
${function(){
const defaultVariant = data.variants?.find(v => (v.available && v.is_hit_discount == true)) || data.variants?.[0];
const selectedVariant = data.variant || defaultVariant;
let show_flashsale_limit_tip = false;
let limit_user_product_discount;
if(selectedVariant.flash_sale_info && selectedVariant.flash_sale_info?.limit_user_product_type != "LUPT_NO_LIMIT" && selectedVariant.flash_sale_info?.limit_user_product_discount > 0) {
show_flashsale_limit_tip = true;
limit_user_product_discount = selectedVariant.flash_sale_info?.limit_user_product_discount;
}
if(data.flash_sale_info && data.flash_sale_info?.limit_user_product_type != "LUPT_NO_LIMIT" && data.flash_sale_info?.limit_user_product_discount > 0) {
show_flashsale_limit_tip = true;
limit_user_product_discount = data.flash_sale_info?.limit_user_product_discount;
}
return `
`
}()}
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);
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);
class SpzCustomDiscountFlashsale extends SPZ.BaseElement {
constructor(element) {
super(element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.discountInfoApi = "\/api\/storefront\/promotion\/home_page\/campaign?discount_id="
this.product_display = {};
this.model = {
loading: false,
page: 2,
limit: 20,
total: 0, //已展示的商品数量
params: {
count: 0,
has_more: false,
sort: {
by: "recommend",
direction: "asc"
}
}
};
this.discount_id= "";
this.image_size="100%";
this.discount_info = {};
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
async getDiscountList() {
const data = await this.xhr_.fetchJson(this.discountInfoApi + this.discount_id, {
method: "GET",
}).then(res => {
this.model.params = {
count: res.product_info.count,
has_more: res.product_info.has_more,
sort: {
by: "recommend",
direction: "asc"
}
}
if(!res.product_info.product.length) return;
this.model.total = res.product_info.product.length;
this.product_display = res.home_page_info.product_display;
this.discount_info = res.discount_info;
res.product_info.product.forEach((product) => {
product.url = appDiscountUtils.globalizePath(product.url);
})
res.image_domain = this.win.SHOPLAZZA.image_domain;
res.isMobile = appDiscountUtils.judgeMobile();
SPZ.whenApiDefined(document.getElementById("appDiscountIndexFlashsale"))
.then(apis => {
apis.render(res).then(() => {
const hiddenArraw = res.home_page_info.banner.enabled ? res.product_info.product.length < 5 : res.product_info.product.length < 6
if(hiddenArraw) {
document.querySelectorAll('.app_discount_flashsale_arrow').forEach((item) => {
item.style.display="none";
})
}
SPZ.whenApiDefined(document.getElementById("flashsaleProductsRender")).then((api) => {
res.product_info.product_display = this.product_display;
res.product_info.discount_id = this.discount_id;
res.product_info.apply_scenario = 1;
res.product_info.limit_purchase = this.discount_info.limit_user_product_discount;
res.product_info.discount_image_size = this.image_size;
api.render(res.product_info,true).then(() => {this.bindEvent_()});
})
})
})
}).catch(err => {
console.error(err);
})
return data;
};
// 获取加载的商品数据,拼接html模板
async loadData(cb) {
// 请求数据
this.model.loading = true;
//查询活动商品接口
const reqBody = {
discount_id: this.discount_id,
page: this.model.page,
limit: this.model.limit,
apply_scenario: 1,
sort: this.model.params.sort,
sales_channel: {
sale_channel_type: "online",
sale_channel_id: '1248245'
}
}
this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product/list`, {
method: "post",
body: reqBody
}).then(async(res)=>{
const count = res.count;
this.model.params.has_more = res.has_more;
this.model.total = this.model.total + res.products.length;
if (count > 0) {
this.model.page++;
if (res.products && res.products.length > 0) {
res.products.forEach((product) => {
product.url = appDiscountUtils.globalizePath(product.url);
})
res.product_display = this.product_display;
res.discount_id = this.discount_id;
res.apply_scenario = 1;
res.limit_purchase = this.discount_info.limit_user_product_discount;
res.discount_image_size = this.image_size;
// 获取商品列表渲染模板, dom挂载
const $content = document.querySelector(".app_discount_products_list_wrapper");
this.templates_ = SPZServices.templatesForDoc();
this.templates_.renderTemplate(document.querySelector('#apps-discounts_product_list_template'), res).then((el) => {
const childNodes = el.querySelectorAll('.as-render-product-item');
if (childNodes && childNodes.length > 0) {
$content.append(...childNodes);
}
})
// 监听load去掉灰色背景
document.dispatchEvent(new CustomEvent('fire.load.img'));
// 触发懒加载
cb && cb(products);
window.lazyLoadInstance && window.lazyLoadInstance.update();
}
}
this.model.loading = false;
}).catch((err)=>{
console.error(err);
this.model.loading = false;
})
};
setupAction_() {
this.registerAction('shiftMove', (data) => {
const $el = document.querySelector(".app_discount_products_list_wrapper");
const action = data.args.direct === "right";
const scrollwidth = action ? $el.offsetWidth : -$el.offsetWidth;
$el.scrollBy({
left: scrollwidth,
behavior: 'smooth'
});
});
this.registerAction('changeFlashSaleLimitPurchase', (invocation) => {
const data = invocation.args.data;
this.handleFlashsaleTip_(data);
});
};
//切换快速加购弹窗内限时促销限购提示
handleFlashsaleTip_(data) {
const flashsaleEl = document.querySelector('#quick-shop-flashsale-tip');
SPZ.whenApiDefined(flashsaleEl).then((api) => {
api.render(data);
});
}
bindEvent_() {
// 监听滚动,请求数据
const $el = document.querySelector(".app_discount_products_list_wrapper");
if($el) {
$el.addEventListener("scroll", this.win.SPZCore.Types.debounce(
this.win,
() => {
const isRightEnd = $el.scrollLeft + $el.offsetWidth + 10 >= $el.scrollWidth;
const isLeftEnd = Math.abs($el.scrollLeft) + $el.offsetWidth + 10 >= $el.scrollWidth;
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
const isEnd = isRTL ? isLeftEnd : isRightEnd;
if(isEnd && this.model.params.has_more && !this.model.loading && this.model.total < 100) {
this.loadData();
}
},
50
))
};
};
buildCallback() {
this.setupAction_();
};
mountCallback() {
const $el = document.querySelector("#appDiscountFlashsale")
this.discount_id = $el.getAttribute('flashsale-id');
this.image_size = $el.getAttribute('image-size');
if(!this.discount_id) return;
this.getDiscountList();
};
}
SPZ.defineElement('spz-custom-discount-flashsale', SpzCustomDiscountFlashsale);
End in
${data.dd}D
${data.hh}:
${hour}:
${data.mm}:
${data.ss}
.
${data.SSS}
Promo products limited to ${limit_user_product_discount} item per person
${function() {
const currentProduct = data.product;
return (currentProduct.options || []).map((option, index) => {
const optionName = option.name || '';
const optionId = option.id || '';
const position = `option${index + 1}`;
return `
`;
}).join('')
}()}
${function() {
const value = (data.originData && data.originData.value) || data.value;
const isHasValue = value ? true : false;
return `
${value}
`
}()}
The activity inventory has been sold out, flash sale discount will not be applied
