${(function(){
const get_random_six_digits = () => {
return Math.random().toString().slice(-6)
};
const wholesale_enabled = false;
const setting_product_image_display = "133.33%";
const product_image = data.image;
const secondary_image = data.secondImage;
const image_width = product_image.width;
let image_height = product_image.height;
if(setting_product_image_display == '100%'){
image_height = image_width
}else if(setting_product_image_display == '133.33%'){
image_height = image_width * 1.3333;
};
const product_image_hover_on = true && !!secondary_image.src;
const has_save_label = false && ((+data.compare_at_price) > (+data.price));
const is_single_variant = data.variants.length == 1;
const min_price_variant_href = (data.min_price_variant && data.min_price_variant.available) ? data.min_price_variant.withinUrl : data.withinUrl;
const retail_price_max = data.retail_price_max || data.compare_at_price_max;
const THUMBNAILS_MAX_SIZE = 3;
const thumbnails = data.thumbVariants.slice(0, THUMBNAILS_MAX_SIZE);
const image_wrap_id = 'image_wrap_' + get_random_six_digits();
const image_carousel_id = 'image_carousel_' + get_random_six_digits();
const thumbnails_selector_id = 'thumbnails_selector_' + get_random_six_digits();
const form_id = 'form_' + get_random_six_digits();
const mixed_wholesale = data.mixed_wholesale;
return `
${
data.price_min != data.price_max
? ` - `
: `
`
}
+${data.remainInvisibleThumbCount}
`
})()}
Free Ground Shipping on All Domestic Orders $59 or More.
Free Ground Shipping on All Domestic Orders $59 or More.
const TAG = 'spz-custom-utils';
const DEFAULT_DELAY_TIME = 100;
class SpzCustomUtils extends SPZ.BaseElement {
constructor(element) {
super(element);
this.templates_ = SPZServices.templatesForDoc();
}
buildCallback() {
this.action_ = SPZServices.actionServiceForDoc(this.element);
this.templates_ = SPZServices.templatesForDoc(this.element);
this.xhr_ = SPZServices.xhrFor(this.win);
}
static deferredMount() {
return false;
}
mountCallback() {
}
//判断是否为移动端
isMobile() {
/* 判断机型与处理 */
const u = navigator.userAgent
const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; // android终端
const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // ios终端
return (isAndroid || isiOS);
};
/**
* url query param to object
* @param {string} url
* @returns {object} query object
*/
params(url) {
url = url || window.location.href;
let params = {};
url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {
try {
params[key] = decodeURIComponent(value);
} catch (e) {
params[key] = value;
}
});
return params;
};
/**
* @param fn {Function} 实际要执行的函数
* @param delay {Number} 延迟时间,单位是毫秒(ms)
* @return {Function} 返回一个“防反跳”了的函数
*/
debounce(fn, delay) {
// 定时器,用来 setTimeout
let timer;
// 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数
return function () {
// 保存函数调用时的上下文和参数,传递给 fn
const context = this;
const args = arguments;
// 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
clearTimeout(timer);
// 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),
// 再过 delay 毫秒就执行 fn
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
};
/* 节流防抖 */
throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function () {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if (mustRun && curTime - startTime >= mustRun) {
func.apply(context, args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
} else {
timeout = setTimeout(func, wait);
}
};
};
//滚动加载方法
isToPageEnd(id) {
const $el = document.querySelector(`[data-section-id='${id}']`);
const scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop; //滚动条距离顶部的高度
const clientHeight = window.innerHeight; //当前可视的页面高度
const scrollHeight = document.body.scrollHeight; //当前页面的总高度
const elOffsetTop = $el.getBoundingClientRect().top + window.pageYOffset - document.documentElement.clientTop; // 元素距离文档顶部距离
// 如果改卡片下面还有卡片或者dom,计算滚动加载需要考虑这个高度
const toBottom = scrollHeight - ($el.offsetHeight + elOffsetTop); //元素到浏览器底部的高度
if (scrollTop + clientHeight + toBottom + 100 >= scrollHeight) {
return true;
}
return false;
};
/**
* url 添加前缀
* @param {string} path , 必须是前面有斜杠前缀的路径
* @returns string
*/
prefixionPath(prefix,urlPath) {
if(typeof prefix !== 'string') return ;
if(typeof urlPath !== 'string') return ;
if(urlPath.indexOf('/') !== 0){
throw new Error('prefixPath: urlPath must be start with /');
}
if(prefix.indexOf('/') !== 0){
throw new Error('prefixPath: prefix must be start with /');
}
return prefix+urlPath;
}
/**
* @param {string} urlPath
* @returns {string}
* @example globalizePath('/path_a/path_b')// => '/en/path_a/path_b'
*/
globalizePath(urlPath) {
if(typeof urlPath !== 'string') return ;
if(urlPath.indexOf('/') !== 0){
urlPath = '/'+urlPath;
}
let prefix = ((SHOPLAZZA && SHOPLAZZA.routes && SHOPLAZZA.routes.root) || '');
if(prefix.length>0){
if(prefix.indexOf('/') !== 0){
prefix = '/'+prefix;
}
return this.prefixionPath(prefix,urlPath);
}else{
return urlPath;
}
}
image_padding_bottom(width, height, origin) {
origin = origin || 'limit';
if (width && height) {
const hw_ratio = height / width;
if (origin == 'limit') {
if (hw_ratio < 0.62) {
return '62%';
} else if (hw_ratio > 1.6) {
return '160%';
}
}
return parseInt(hw_ratio * 100) + '%';
}
return '100%';
}
getNumber(str) {
str = str + '';
return str.match(/\d+(\.\d+)?/g) ? Number(str.match(/\d+(\.\d+)?/g)[0]) : str;
};
// 处理货币符号
finance_money_with_shop_symbol(price, onlyNumber) {
const symbol = onlyNumber ? '' : window.SHOPLAZZA.currency_symbol;
const position = window.SHOPLAZZA ? window.SHOPLAZZA.currency_symbol_pos : 'left';
const format = window.SHOPLAZZA ? window.SHOPLAZZA.money_format : 'amount';
if (position == 'right') {
return Number(Number(price) * 1).format(format) + symbol;
}
return symbol + Number(Number(price) * 1).format(format);
};
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, SpzCustomUtils)
${function() {
const options = data.options;
const defaultValue = data.defaultValue || options[0].value;
let defaultText = options[0].text;
const hasOption = Array.apply(null, options).find((option) => {
return option.value === defaultValue;
});
if(hasOption){
defaultText = hasOption.text;
}
return `
`
}()}
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)
${(function() {
const products = data.products;
const discountType = data.discount_info.discount_type;
const progress = 'PROGRESS_FINISHED';
const product_grid_image_size = null || '100%';
const product_display = originData?.product_display;
const show_atc = product_display ? product_display?.show_add_to_cart : true;
const show_discount_label = product_display?.show_discount_label || false;
const show_sales_progress = product_display?.sales_progress?.enabled || false;
const sales_progress_format = product_display?.sales_progress?.format || '';
const limit_purchase_tip = data.limit_purchase_tip || "";
const productConfig = product_display?.config && JSON.parse(product_display?.config).color;
const sale_progress_bg = productConfig.sale_bar_background_color;
const sale_progress_bg_start = productConfig.progress_sale_bar_background_color_start;
const sale_progress_bg_end = productConfig.progress_sale_bar_background_color_end;
const sale_count_text = productConfig.sale_bar_atc_color;
const discount_label_start = productConfig.discount_sign_background_color_start;
const discount_label_end = productConfig.discount_sign_background_color_end;
const discount_label_text = productConfig.discount_sign_color;
if(!products.length) return '';
return products.map(product => {
let price = product.price || 0;
let priceMin = product.price_min || 0;
let priceMax = product.price_max || 0;
let compareAtPriceMax = product.compare_at_price_max || 0;
let compareAtPrice = product.compare_at_price || 0;
let title = product.title || '';
let id = product.id || '';
let url = product.url || '';
let type = product.type || '';
let is_sold_out = false;
if (product.available == false && product.inventory_policy != 'continue') {
is_sold_out = true;
}
const soldOutLang1 = "Sold out";
const soldOutLang2 = "Sold out";
const noNeedBtn = discountType !== "DT_MIX_MATCH_BUNDLE" && discountType !== "DT_CLASSIC_BUNDLE";
let imageWidth;
if (product.image.width) {
imageWidth = product.image.width;
} else {
imageWidth = "300px";
}
let imageHeight;
if (product.image.height) {
imageHeight= product.image.height;
} else {
imageHeight = "300px";
}
if (product_grid_image_size !== 'natural') {
imageHeight = (imageWidth * parseFloat(product_grid_image_size)) / 100;
}
const flash_sale_info = product?.flash_sale_info;
const is_flashsale_sold_out = flash_sale_info?.discount_sales_rate == "100";
const off_ratio = flash_sale_info?.off_ratio;
price = flash_sale_info?.discount_price || price;
const defaultVariantTitle = product.variants[0].title.replace('-', '/') || '';
const variantDiscountInfo = product.variants[0].discount_info || {};
const minPurchaseQty = product.discount_min_purchase_qty || product.variants[0].discount_info.discount_min_purchase_qty || 0;
if (product.published) {
return `
x ${minPurchaseQty}
${product.title}
Almost sold out
${flash_sale_info?.discount_sales}
${flash_sale_info?.discount_sales_rate}%
sold
${function(){
if (soldOutLang1){
return `
${soldOutLang1}
`
}else{
return `
${soldOutLang2}
`
}
}()}
`
} else {
return ``;
}
}).join('');
})()}
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);
this.setupAction_();
}
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;
});
}
setupAction_() {
this.registerAction('test', (invocation) => {
});
}
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)
${function() {
return `
`;
}()}
const TAG = 'spz-custom-discount-default';
const E_DISCOUNT_PROGRESS = {
ProgressFinished : "PROGRESS_FINISHED",
ProgressNotStarted : "PROGRESS_NOT_STARTED",
ProgressOngoing : "PROGRESS_ONGOING"
};
class SpzCustomDiscountDefault extends SPZ.BaseElement {
constructor(element) {
super(element);
this.templates_ = null;
this.section_id = 15890337540001 || 1;
this.discountObj = window.discountObj;
this.discount_info = this.discountObj.discount_info;
this.product_display = this.discountObj.landing_page_info.product_display;
this.buy_product_info = this.discountObj.buy_product_info;
this.obtain_product_info = this.discountObj.obtain_product_info;
this.discount_id = this.discount_info.id;// 活动id
this.discountI18n = {};
const THEME_NAME = window.SHOPLAZZA.theme.merchant_theme_name || window.SHOP_PARAMS.theme_name;
this.isHero = /Hero/.test(THEME_NAME);
// PROGRESS_ONGOING: 进行中 PROGRESS_NOT_STARTED: 未开始 PROGRESS_FINISHED: 已结束
this.E_DISCOUNT_PROGRESS = {
ProgressFinished : "PROGRESS_FINISHED",
ProgressNotStarted : "PROGRESS_NOT_STARTED",
ProgressOngoing : "PROGRESS_ONGOING"
};
this.E_TAB_MAP = {
scenario_buy : {
value: "1",
domId: "product_list_buy_products"
},
scenario_obtain : {
value: "2",
domId: "product_list_obtain_products"
}
}
this.tabContentIdMap = {};
this.currentTab = this.E_TAB_MAP.scenario_buy.value;
this.model_buy = {
discount_id: this.discount_id, //活动id
scenario: 1, // 枚举值,1:购买商品,2:获得商品
sort: { by: 'recommend', direction: 'asc' },
page: 2, //分页码
limit: 20, // 每页数量
loading: false, // 请求数据标示
has_more: this.buy_product_info.has_more // 是否还有数据
};
this.model_get = {
discount_id: this.discount_id, //活动id
scenario: 2, // 枚举值,1:购买商品,2:获得商品
sort: { by: 'recommend', direction: 'asc' },
page: 2,
limit: 20,
loading: false,
has_more: this.obtain_product_info?.has_more
};
this.modelMap = {
[this.E_TAB_MAP.scenario_buy.value]: this.model_buy,
[this.E_TAB_MAP.scenario_obtain.value]: this.model_get,
}
this.sortDict = {
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' }
};
this.sortOptions = [
{
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"
}
];
// 直出商品数据 + 异步请求商品数据
this.products = this.buy_product_info.product;
this.productUrl = "";
// 款式信息集合
this.productStyleInfo = [];
// 弹窗内选择款式集合
this.modalVariantInfo = [];
// 加购商品列表
this.lineItems = [];
this.buyNowApi = "\/api\/checkout\/order";
this.batchAtcApi = "\/api\/cart\/batch";
this.debounceTimer = null;
this.discount_type = this.discount_info.discount_type;
}
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_();
Object.entries(this.E_TAB_MAP).forEach(([key, valueObj]) => {
this.tabContentIdMap[valueObj.value] = valueObj.domId;
})
}
async mountCallback() {
this.utilsApi_ = await SPZ.whenApiDefined(document.querySelector('#spz_custom_utils'));
this.init();
this.handleRenderSort();
}
init() {
this.xhr_.fetchJson(`/api/discount-i18n`, {
method: "get",
}).then((res)=>{
this.discountI18n = res;
this.bindEvent();
})
// url 携带 sort_by参数
var queryParams = this.utilsApi_.params();
var sortValue = queryParams.sort_by;
if (sortValue) {
this.model_buy.sort = this.sortDict[sortValue];
}
// 经典捆绑初始化商品数据
if(this.discount_type == 'DT_CLASSIC_BUNDLE') {
this.productStyleInfo.push(...this.buy_product_info.product.map((item) => {
return this.getFilteredVariants_(item, 'single');
}));
}
}
handleRenderSort() {
// 渲染排序
const sort_x_id = 'promotionSortProductsX';
const $sortX = document.getElementById(sort_x_id)
$sortX && SPZ.whenApiDefined($sortX).then((api) => {
// 渲染排序列表
api.doRender_({options: this.sortOptions, defaultValue: 'recommend_asc', id: sort_x_id });
})
const sort_y_id = 'promotionSortProductsY';
const $sortY = document.getElementById(sort_y_id)
$sortY && SPZ.whenApiDefined($sortY).then((api) => {
// 渲染排序列表
api.doRender_({options: this.sortOptions, defaultValue: 'recommend_asc' , id: sort_y_id});
})
}
// 获取数据,拼接html模板
async getData() {
// 请求数据
let model = this.modelMap[this.currentTab];
if (!model.has_more || model.loading) {
return;
}
model.loading = true;
this.handleLoading_({type: 'product', action: 'show'});
let $content = document.querySelector(`#${this.tabContentIdMap[this.currentTab]} .discount-default__productlist-wrap`) || document.querySelector(`.discount-default__productlist-wrap`);
let $defaultEmpty = $content && $content.querySelector('.discount_default_empty');
//查询活动商品接口
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: '1199785'
}
}
this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product/list`, {
method: "post",
body: reqBody
}).then(async(res)=>{
// 更新参与活动的商品数量
const productCount = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionEventProductCount`);
productCount && SPZ.whenApiDefined(productCount).then((api) => {
api.render(res.count, true);
});
this.products.push(...res.products);
this.handleLoading_({type: 'product', action: 'close'});
const count = res.count;
model.has_more = res.has_more;
if (count > 0) {
$defaultEmpty && ($defaultEmpty.style.display = 'none');
model.page++;
if (res.products && res.products.length > 0) {
let products = res.products.map((product) => {
return {
...product,
url: this.utilsApi_.globalizePath(product.url),
image_padding_bottom: this.utilsApi_.image_padding_bottom(product.image.width, product.image.height,'no-limit')
}
});
// 获取商品列表渲染模板, dom挂载
const renderApi = await SPZ.whenApiDefined(document.querySelector('#discounts_products_render'));
const el = await renderApi.getRenderTemplate({
products: products,
discountI18n: this.discountI18n,
discount_info: this.discount_info,
product_display: this.product_display
});
const childNodes = el.querySelectorAll('.as-render-product-item');
if (childNodes && childNodes.length > 0) {
$content.append(...el.childNodes);
}
if(this.discount_type == 'DT_CLASSIC_BUNDLE') {
// 遍历$content 插入商品垂直虚线分割
const productListAsync = $content.querySelectorAll('.as-render-product-item');
if (productListAsync.length > 0) {
productListAsync.forEach((item, index) => {
const htmlStr = `<span class="promotion_dotted_line"></span>
<div class="promotion_plus_bundle">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M1 6H11M6 1L6 11" stroke="black" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</div>
<span class="promotion_dotted_line"></span>`;
this.createAndInsertSeparator_('promotion_separator md:hidden', (index + 1) % 4 !== 0 && index !== productListAsync.length - 1, htmlStr, $content, item);
this.createAndInsertSeparator_('promotion_separator lg:hidden', (index + 1) % 2 !== 0 && index !== productListAsync.length - 1, htmlStr, $content, item);
});
}
}
if(this.discount_type == 'DT_MIX_MATCH_BUNDLE') {
const productSelector = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionProductSelector`);
productSelector && SPZ.whenApiDefined(productSelector).then((api) => {
api.init();
});
const currentPageSelectedProducts = res.products.filter(item => this.productStyleInfo.map(item => item.product_id).includes(item.id));
this.updateProductPrice_(currentPageSelectedProducts);
}
}
} else {
// 空列表
const $emptyTemplate = document.querySelector('#promotionDiscountEmpty .discount_default_empty');
const $cloneEmptyTemplate = $emptyTemplate.cloneNode(true);
$content.innerHTML = '';
$content.append($cloneEmptyTemplate);
$defaultEmpty && ($defaultEmpty.style.display = 'flex');
}
model.loading = false;
}).catch((err)=>{
this.handleRequestError_(err);
}).finally(()=>{
model.loading = false;
this.handleLoading_({type: 'product', action: 'close'});
// 经典spu纬度需要该商品信息: is_classic_bundle_product_list_variant_tag
if(this.discount_type == 'DT_CLASSIC_BUNDLE' && this.discount_info.enable_min_purchase_qty == true && this.discount_info.min_purchase_qty_type == 'spu') {
this.productStyleInfo = this.productStyleInfo.map((item) => {
return {
...item,
is_classic_bundle_product_list_variant_tag: true,
}
});
}
const result = this.productStyleInfo.reduce((map, item) => {
if (!map[item.product_id]) {
map[item.product_id] = [];
}
map[item.product_id].push(item);
return map;
}, {});
// 渲染变体tags
if(this.discount_type == 'DT_MIX_MATCH_BUNDLE' || this.discount_type == 'DT_CLASSIC_BUNDLE') {
Object.values(result).forEach((item) => {
this.handleSpzVariantRender_(item, item[0].product_id);
this.handleProductOption_(item[0].product_id, true);
});
}
// 渲染经典额外变体
if(this.discount_type == 'DT_CLASSIC_BUNDLE' && this.discount_info.enable_min_purchase_qty == true && this.discount_info.min_purchase_qty_type == 'spu') {
Object.values(result).forEach((item) => {
if(item[0].is_multi_style && item[0].discount_min_purchase_qty > 1) {
const classicSpuTag = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionClassicSpuTags-${item[0].product_id}`);
classicSpuTag && SPZ.whenApiDefined(classicSpuTag).then((api) => {
api.render(item, true);
});
}
});
}
// 渲染经典捆绑商品最低购买数量
if(this.discount_type == 'DT_CLASSIC_BUNDLE') {
Object.values(result).forEach((item) => {
this.handleMinPurchaseQtyUpdate_({discount_min_purchase_qty: item[0].discount_min_purchase_qty}, item[0].product_id);
});
}
})
}
createAndInsertSeparator_(className, condition, htmlStr, $content, item) {
if (condition) {
const separator = document.createElement('div');
separator.className = className;
separator.innerHTML = htmlStr;
$content.insertBefore(separator, item.nextSibling);
}
}
bindEvent() {
// 监听滚动,请求数据
window.addEventListener("scroll", this.utilsApi_.debounce(
() => {
// 判断是否到底
const model = this.modelMap[this.currentTab];
if (!model.loading && model.has_more && this.utilsApi_.isToPageEnd(this.section_id)) {
this.getData();
}
},
10,
50
))
}
// 商品排序
handleSort_(data) {
let sortKey = data.value;
this.modelMap[this.currentTab].sort = this.sortDict[sortKey || 'recommend_asc'];
this.modelMap[this.currentTab].page = 1;
this.modelMap[this.currentTab].has_more = true;
this.productStyleInfo = this.handleMixMatchBundleFilterSelected_(this.productStyleInfo);
// 清空商品列表dom, 重新请求排序数据渲染
let $productList = document.querySelector(`#${this.tabContentIdMap[this.currentTab]} .discount-default__productlist-wrap`) || document.querySelector(`.discount-default__productlist-wrap`);;
$productList && ($productList.innerHTML = '');
this.getData();
}
// tab 切换
tabChange_(value) {
this.currentTab = value || this.E_TAB_MAP.scenario_buy.value;
}
// 渲染界面
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);
});
}
// 捆绑商品加购/立即购买
handleBundleAddToCart_(data) {
const { action } = data;
if(this.discount_type == 'DT_CLASSIC_BUNDLE') {
this.lineItems = this.productStyleInfo;
} else {
this.lineItems = this.handleMixMatchBundleFilterSelected_(this.productStyleInfo);
}
if(action == 'cart') {
//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: 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: 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;
} else {
this.handleRequestError_(data);
}
})
.catch(async (error) => {
await error.then((data) => {
this.handleRequestError_(data);
});
});
}
}
handleRequestError_(data) {
const message = data?.message || data?.errors?.[0] || 'Unknown error';
const toast = SPZCore.Dom.scopedQuerySelector(document.body, '#discount_toast');
toast && SPZ.whenApiDefined(toast).then((api) => {
api.showToast(message);
});
};
// 渲染加购弹窗内容
async renderQuickShop(data) {
this.handleLoading_({type: 'whole', action: 'show'});
const apply_scenario = this.modelMap[this.currentTab].scenario;
this.xhr_.fetchJson(`/api/storefront/promotion/landing_page/product?product_id=${data.product_id}&discount_id=${this.discount_id}&apply_scenario=${apply_scenario}`, {
method: "get",
}).then(async(res)=>{
this.handleLoading_({type: 'whole', action: 'close'});
const $quickShop = await SPZ.whenApiDefined(document.querySelector('#promotion-quick-view-render'));
// 定义默认渲染的子款式
const selectedVariant = res.product.variants.find((v)=> (v.available && v.is_hit_discount == true)) || 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};
$quickShop.render(data);
// 打开加购弹窗
SPZ.whenApiDefined(document.querySelector(`#promotion-quick-view`)).then((api)=>{
api.open();
});
}).catch((err)=>{
this.handleLoading_({type: 'whole', action: 'close'});
})
}
// 单变体点击添加按钮
renderSingleVariant(data) {
const { product_id } = data;
const currentProduct = this.products.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, discount_min_purchase_qty } = data;
const { product_id, variant_id, variant, quantity, product } = data;
const isSingle = type == 'single';
const variantData = isSingle ? (variants[0] || data) : variant;
const productData = isSingle ? data : product;
let item_quantity = 0;
if (this.discount_type === '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.products.find((item) => item.id == product_id)?.product_type || this.products.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,
discount_min_purchase_qty: discount_min_purchase_qty || productData.discount_min_purchase_qty || variantData.discount_info.discount_min_purchase_qty || 0,
}
}
// 更新价格方法
updateProductPrice_(data) {
const bottomBtnContainer = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionBottomContainer`);
if (data.length == 0) {
bottomBtnContainer && SPZ.whenApiDefined(bottomBtnContainer).then((api) => {
api.render({original_price: 0, received_discounts: 0}, true);
});
return;
}
data = this.handleMixMatchBundleFilterSelected_(data);
const reqBody = {
discount_id: this.discount_id,
customer: {
customer_id: '',
email: '',
},
sales_channel: {
sale_channel_type: "online",
sale_channel_id: '1199785'
},
line_items: data
}
// 如果已经有一个请求在等待,那么取消这个请求
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.handleLoading_({type: 'whole', action: 'show'});
this.debounceTimer = setTimeout(() => {
this.xhr_.fetchJson(`/api/storefront/promotion/calculate/discounted_price`, {
method: "post",
body: reqBody
}).then((res)=>{
// 更新商品列表价格
Object.keys(res.line_items).forEach((key) => {
const currentProductPrice = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionProductPrice-${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) => {
api.render({...res.total_price, picked_qty}, true);
});
}).catch(async (err)=>{
await err.then((data) => {
this.handleRequestError_(data);
});
}).finally(()=>{
this.handleLoading_({type: 'whole', action: 'close'});
})
}, 100);
}
// 还原商品价格
resetProductPrice_(data) {
const {price, compare_at_price, id} = data;
const currentProductPrice = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionProductPrice-${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 productSelector = SPZCore.Dom.scopedQuerySelector(document.body, `#promotionProductSelector`);
productSelector && SPZ.whenApiDefined(productSelector).then((api) => {
api.toggle_({option: productId, value: show});
});
}
// 调用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);
});
}
// 添加商品子款式
renderVariantTag() {
let variantInfo;
const quickShopBody = SPZCore.Dom.scopedQuerySelector(document.body, '#promotion-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.discount_type == '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.discount_info.enable_min_purchase_qty == true && this.discount_info.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(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, '#promotion-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, '#promotion-quick-view');
quickView && SPZ.whenApiDefined(quickView).then((api)=>{
api.close();
});
return;
}
// 更新this.productStyleInfo中的商品款式信息
const index = this.productStyleInfo.findIndex((item) => item.product_id == productId);
if (index != -1) {
this.productStyleInfo[index] = 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, '#promotion-quick-view');
quickView && SPZ.whenApiDefined(quickView).then((api)=>{
api.close();
});
});
}
// 混搭弹窗内的前端库存校验
handleModalInventoryCheck_(data) {
if(this.discount_type == 'DT_CLASSIC_BUNDLE') {
const currentVariantAddNum = this.modalVariantInfo.find((item) => item.variant_id == data.variant_id)?.quantity || 0;
const quickShopBody = SPZCore.Dom.scopedQuerySelector(document.body, '#promotion-quick-shop-body');
if(!!data.variant && currentVariantAddNum == Number(data.variant.available_quantity)) {
quickShopBody && quickShopBody.setAttribute('status', 'soldout');
} else {
quickShopBody && quickShopBody.setAttribute('status', 'available');
}
}
}
// 删除商品子款式
deleteVariantTag(data) {
const { product_id, variant_id } = data;
if(this.discount_info.enable_min_purchase_qty == true && this.discount_info.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(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);
isMultiStyle && this.updateProductPrice_(this.productStyleInfo);
this.handleProductOption_(product_id, true);
} else {
this.handleProductOption_(product_id, false);
this.resetProductPrice_(this.products.find((item) => item.id == product_id));
}
}
// 加购弹窗未参与活动 加购按钮不可点击
handleNotHitDiscount_(data) {
const $quickShopBody = document.querySelector('#promotion-quick-shop-body');
const $limitTip = document.querySelector('.promotion_flashsale_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', '')
}
}
//切换快速加购弹窗内限时促销限购提示
handleFlashsaleTip_(data) {
const flashsaleEl = document.querySelector('#quick-shop-flashsale-tip');
SPZ.whenApiDefined(flashsaleEl).then((api) => {
api.render(data);
});
}
// loading
handleLoading_(event) {
const { type, action } = event;
const loadingElementId = type == 'product' ? '#promotionProductsLoading' : '#promotionWholeLoading';
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);
}
handleMixMatchBundleFilterSelected_(data) {
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;
}, []);
if(this.discount_type == 'DT_MIX_MATCH_BUNDLE') {
return data.filter((item) => idArr.includes(item.product_id));
}
return data;
}
handleCopyDiscountCode_(data) {
sessionStorage.setItem('other-copied-coupon', data);
const message = 'Discount code copied !';
const toast = SPZCore.Dom.scopedQuerySelector(document.body, '#discount_toast');
toast && SPZ.whenApiDefined(toast).then((api) => {
api.showToast(message);
});
}
setupAction_() {
this.registerAction('handleTabChange', (invocation) => {
const { panelId } = invocation.args.data;
this.tabChange_(panelId);
});
// 监听排序组件 选中选项
this.registerAction('handleSort', (invocation) => {
const data = invocation.args.data;
this.handleSort_(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('getCartCount', (invocation) => {
//一些老主题还未用spz重构,需要手动触发一次老方法以更新购物车图标的数量
window.$ && $(document).trigger('dj.common.cart.change');
});
// 捆绑商品加购/立即购买
this.registerAction('handleBundleAddToCart', (invocation) => {
const data = invocation.args;
this.handleBundleAddToCart_(data);
});
// 子款式 未参与活动
this.registerAction('handleNotHitDiscount', (invocation) => {
const data = invocation.args.data;
this.handleNotHitDiscount_(data);
});
// 限时促销限购提示
this.registerAction('handleFlashsaleTip', (invocation) => {
const data = invocation.args.data;
this.handleFlashsaleTip_(data);
});
// 加购提示
this.registerAction('handleAddToCartToast', (invocation) => {
const langValue = 'Added successfully';
this.triggerEvent_("addToCartToast", langValue);
});
this.registerAction('handleGoShopping', () => {
window.location.href = this.productUrl;
});
this.registerAction('getProductUrl', (invocation) => {
const data = invocation.args;
this.productUrl = data.product_url;
});
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);
});
this.registerAction('pageReload', () => {
//活动进行中 长期活动 = 虚拟倒计时
const VirtualCountdown = this.discount_info.progress === "PROGRESS_ONGOING" && this.discount_info.ends_at == -1;
if(VirtualCountdown) return;
window.location.reload();
});
this.registerAction('resetModalVariantInfo', () => {
this.modalVariantInfo = [];
});
this.registerAction('handleModalInventoryCheck', (invocation) => {
const data = invocation.args.data;
this.handleModalInventoryCheck_(data);
});
this.registerAction('copyDiscountCode', (invocation) => {
const data = invocation.args.data;
this.handleCopyDiscountCode_(data);
});
}
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)
The activity inventory has been sold out, flash sale discount will not be applied.
Continue Shopping
${function() {
const productData = data;
let product_change_event = '';
let mouse_over_event = '';
let mouse_out_event = '';
const product_options = productData.options.filter(Boolean) || [];
for (let opt of product_options) {
const nameEscape = opt.name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
product_change_event = product_change_event + `quick-shop-selected-variant-${opt.id}.rerender(data=event.selectedValues.${opt.name});`;
mouse_out_event = mouse_out_event + `quick-shop-selected-variant-${opt.id}.rerender(data=event.selectData.${opt.name});`;
mouse_over_event = mouse_over_event + `@${nameEscape}Mouseover="quick-shop-selected-variant-${opt.id}.rerender(data=event);"`;
}
const selectedVariant = productData.variants.find(v => (v.available && v.is_hit_discount == true)) || productData.variants[0];
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 `
Promo products limited to ${limit_user_product_discount} item per person.
`
}()}
${function() {
const value = (data.originData && data.originData.value) || data.value;
const isHasValue = value ? true : false;
return `
${value}
`
}()}
${function(){
const productData = data.product;
let product_change_event = '',
mouse_over_event = ' ';
mouse_out_event = '';
const product_options = productData.options.filter(Boolean) || [];
for (let opt of product_options) {
product_change_event = product_change_event + `quick-shop-selected-variant-${opt.name}.rerender(data=event.selectedValues.${opt.name});`;
mouse_out_event = mouse_out_event + `quick-shop-selected-variant-${opt.name}.rerender(data=event.selectData.${opt.name});`;
mouse_over_event = mouse_over_event + `@${opt.name}Mouseover="quick-shop-selected-variant-${opt.name}.rerender(data=event);"`;
}
const selectedVariant = productData.variants.find(v => v.available) || productData.variants[0];
const statusLan = ((selectedVariant && !selectedVariant.available) || (!selectedVariant && !productData.available)) ?
"Sold out" :
"Add to cart";
return `
`
}()}
${function(){
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const variantData = currentSelectVariant || defaultVariant || data;
const retail_price = variantData.retail_price || 0;
return `
`
}()}
${function(){
const wholesale_enabled = false;
const qty = data.quantity || 1;
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const productVariant = null;
const variantData = currentSelectVariant || defaultVariant || productVariant;
const wholesale_price = variantData.wholesale_price || [];
if(wholesale_enabled && wholesale_price.length > 0) {
let wholesaleIndex = wholesale_price.findIndex(item => {
return item.min_quantity > qty;
});
if(wholesaleIndex < 0){
wholesaleIndex = wholesale_price.length - 1;
}else if(wholesaleIndex > 0){
wholesaleIndex = wholesaleIndex - 1;
}
const wholesalePrice = wholesale_price[wholesaleIndex] || '';
return `
`
}else {
const price = variantData && variantData.price;
return price != undefined ? `
` : '';
}
}()}
${function() {
let variantImageShowed = false;
const currentProduct = data.product;
return (currentProduct.options || []).map((option, index) => {
const optionName = option.name || '';
const position = `option${index + 1}`;
let isThumbImage = false;
if (currentProduct.need_variant_image && !variantImageShowed) {
const variantNames = ["color"] || [];
for (let i = 0, len = variantNames.length; i < len; i++) {
const name = variantNames[i].toLowerCase();
if (name === optionName.toLowerCase()) {
isThumbImage = true;
variantImageShowed = true;
}
}
}
const variantType = "button";
const thumbStyle = "image_with_text";
return `
${optionName}
`
}).join('');
}()}
${data.originData && data.originData.value || data.value}
${function(){
const productData = data.product;
let product_change_event = '',
mouse_over_event = ' ';
mouse_out_event = '';
const product_options = productData.options.filter(Boolean) || [];
for (let opt of product_options) {
product_change_event = product_change_event + `quick-shop-selected-variant-${opt.name}.rerender(data=event.selectedValues.${opt.name});`;
mouse_out_event = mouse_out_event + `quick-shop-selected-variant-${opt.name}.rerender(data=event.selectData.${opt.name});`;
mouse_over_event = mouse_over_event + `@${opt.name}Mouseover="quick-shop-selected-variant-${opt.name}.rerender(data=event);"`;
}
const selectedVariant = productData.variants.find(v => v.available) || productData.variants[0];
const statusLan = ((selectedVariant && !selectedVariant.available) || (!selectedVariant && !productData.available)) ?
"Sold out" :
"Add to cart";
return `
`
}()}
${function(){
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const variantData = currentSelectVariant || defaultVariant || data;
const retail_price = variantData.retail_price || 0;
return `
`
}()}
${function(){
const wholesale_enabled = false;
const qty = data.quantity || 1;
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const productVariant = null;
const variantData = currentSelectVariant || defaultVariant || productVariant;
const wholesale_price = variantData.wholesale_price || [];
if(wholesale_enabled && wholesale_price.length > 0) {
let wholesaleIndex = wholesale_price.findIndex(item => {
return item.min_quantity > qty;
});
if(wholesaleIndex < 0){
wholesaleIndex = wholesale_price.length - 1;
}else if(wholesaleIndex > 0){
wholesaleIndex = wholesaleIndex - 1;
}
const wholesalePrice = wholesale_price[wholesaleIndex] || '';
return `
`
}else {
const price = variantData && variantData.price;
return price != undefined ? `
` : '';
}
}()}
${function() {
let variantImageShowed = false;
const currentProduct = data.product;
return (currentProduct.options || []).map((option, index) => {
const optionName = option.name || '';
const position = `option${index + 1}`;
let isThumbImage = false;
if (currentProduct.need_variant_image && !variantImageShowed) {
const variantNames = ["color"] || [];
for (let i = 0, len = variantNames.length; i < len; i++) {
const name = variantNames[i].toLowerCase();
if (name === optionName.toLowerCase()) {
isThumbImage = true;
variantImageShowed = true;
}
}
}
const variantType = "button";
const thumbStyle = "image_with_text";
return `
${optionName}
`
}).join('');
}()}
${data.originData && data.originData.value || data.value}
const TAG = "spz-custom-popup";
const DISPLAY_TYPE = {
POPUP: "PTT_POPUP" // 弹窗
};
const API = {
LIST: `/api/storefront/promotion/placement/list`, // 获取弹窗列表
REPORT: `/api/storefront/promotion/placement/data/report` // 上报数据
};
const DISPLAY_DEVICE = {
PC_AND_MOBILE: "PD_PC_MOBILE", // PC和移动端
PC: "PD_PC", // PC
MOBILE: "PD_MOBILE" // 移动端
};
const REPORT_EVENT = {
CLICK: "PE_CLICK", // 点击事件
IMPRESSION: "PE_IMPRESSION" // 曝光事件
};
class SpzCustomPopup extends SPZ.BaseElement {
constructor(element) {
super(element);
this.popupList_ = []; // 弹窗数据
this.popupZIndex = 1050; // 弹窗层级
// 节流处理 每5s内多次点击 算一次点击上报
this.throttleReport = this.win.SPZCore.Types.throttle(
this.win,
(data) => {
this.reportData(data)
},
5000
)
}
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.viewport_ = this.getViewport();
}
mountCallback() {
this.fetchData_();
}
// 接口请求,获取数据
fetchData_() {
const id = window.SHOPLAZZA.meta.page.template_type === 51 ? window.SHOPLAZZA.meta.page.resource_id : 0;
return this.xhr_.fetchJson(API.LIST, {
method: 'POST',
body: {
page_id: window.SHOPLAZZA.meta.page.template_type,
placement_type: DISPLAY_TYPE.POPUP,
discount_id: id
}
}).then((res) => {
// 请求成功 执行render
this.doRender_(res.list);
}).catch((err) => {
console.error(err);
});
}
// 渲染dom
doRender_(data) {
this.popupList_ = data || [];
if (this.popupList_.length > 0) {
this.popupList_.forEach((item) => {
item.config = JSON.parse(item.config);
})
}
return this.templates_
.findAndRenderTemplate(this.element, { list: this.popupList_ })
.then((el) => {
const children = this.element.querySelector('*:not(template)');
children && SPZCore.Dom.removeElement(children);
this.element.appendChild(el);
})
.then(() => {
// 遍历显示弹窗
this.popupList_.forEach((item) => {
this.showPopup_(item);
});
})
}
showPopup_(popup) {
// 展示弹窗 符合展示条件的弹窗
const $popup = document.querySelector(`#popup-${popup.id}`);
$popup && SPZ.whenApiDefined($popup).then((api)=> {
const isPC = this.viewport_.getWidth() >= 960;
const isMobile = this.viewport_.getWidth() < 960;
const isMatchPCDevice = popup.device === DISPLAY_DEVICE.PC_AND_MOBILE || popup.device === DISPLAY_DEVICE.PC;
const isMatchMobileDevice = popup.device == DISPLAY_DEVICE.PC_AND_MOBILE || popup.device === DISPLAY_DEVICE.MOBILE;
if((isPC && isMatchPCDevice) || (isMobile && isMatchMobileDevice)) {
// 根据推送时间 延迟展示弹窗
setTimeout(() => {
api.open();
}, popup.delay_seconds * 1000);
}
})
}
// 上报数据
async reportData(data) {
this.xhr_.fetchJson(API.REPORT, {
method: "POST",
body: {
placement_id: data.placement_id,
event: data.event
}
});
}
setupAction_() {
this.registerAction('handleTrack', async(invocation) => {
// 如果是主题编辑器则不用处理
if(window.top !== window.self) {
return;
}
const data = invocation.args;
const event = data.event;
// 点击上报 节流处理
if(event === REPORT_EVENT.CLICK) {
await this.throttleReport(data);
} else {
this.reportData(data);
}
});
}
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, SpzCustomPopup);