function getTotalHeightIncludingMargin(element) {
if (!element) return 0;
const style = window.getComputedStyle(element);
const marginTop = parseFloat(style.marginTop);
const marginBottom = parseFloat(style.marginBottom);
return element.offsetHeight + marginTop + marginBottom;
}
function updateAnnouncementContainerHeight() {
const socialLinksHeight = getTotalHeightIncludingMargin(socialLinks);
const announcementHeight = getTotalHeightIncludingMargin(announcement);
const height = Math.max(socialLinksHeight, announcementHeight);
announcementContainer.style.height = height + 'px';
}
function resetAnnouncementContainerHeight() {
announcementContainer.style.height = 'auto';
}
const socialLinks = document.getElementById('socialLinks');
const announcement = document.querySelector('.pc-announcement');
const announcementContainer = document.querySelector('.announcement-container');
const resizeObserver = new ResizeObserver(() => {
if (window.matchMedia("(min-width: 960px)").matches) {
updateAnnouncementContainerHeight();
}else {
resetAnnouncementContainerHeight();
}
});
if (socialLinks) {
resizeObserver.observe(socialLinks);
}
if (announcement) {
resizeObserver.observe(announcement);
}
1/3
${data.index + 1}/${data.total}
Sku CY-!179768 Material Polyester , Spandex Style Loose , Sleeveless , Wide Leg Feature Printed , Split-joint Neckline Round-neck Occasion Casual , Urban , Leisure Fashion Seasons Spring , Summer , Autumn Type Jumpsuits Color PINK Size S,M,L,XL,2XL
Please consult the size chart we provide for this item's measurements to help you decide which size to buy.Please note: There may be 1-3cm differ due to manual measurement.
CM INCH Bust Shoulder Waist Hips Length Inseam S 95 32 94 109 145 65 M 98 33 98 113 146 65.5 L 102 34 102 117 147 66 XL 106 35 106 121 148 66.5 2XL 110 36 110 125 149 67
Newest
Most liked
Highest ratings
Lowest ratings
Wow you reached the bottom
Most liked
Highest ratings
Lowest ratings
×
${function(){
const limit = typeof data === 'number' ? data : 0;
return `
Pictures/Videos (${limit || 0}/5 )
` }()}
${(function(){
const closeIcon =
'
';
if (item.type === 'image') {
return `
${closeIcon}
`
}
return `
${closeIcon}
`
})()}
Submit Comments Anonymously
class SpzCustomFileUpload extends SPZ.BaseElement {
constructor(element) {
super(element);
this.uploadCount_ = 0;
this.fileList_ = [];
}
buildCallback() {
this.action = SPZServices.actionServiceForDoc(this.element);
this.registerAction('upload', (data) => {
this.handleFileUpload_(data.event?.detail?.data || []);
});
this.registerAction('delete', (data) => {
this.handleFileDelete_(data?.args?.data);
});
this.registerAction('preview', (data) => {
this.handleFilePreview_(data?.args?.data);
});
this.registerAction('limit', (data) => {
this.handleFileLimit_();
});
this.registerAction('sizeLimit', (data) => {
this.handleFileSizeLimit_();
});
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
setData_(count, file) {
this.uploadCount_ = count;
this.fileList_ = file;
}
handleFileUpload_(data) {
data.forEach(i => {
if(this.fileList_.some(j => j.url === i.url)) return;
this.fileList_.push(i);
})
this.uploadCount_++;
sessionStorage.setItem('fileList', JSON.stringify(this.fileList_));
this.triggerEvent_("handleFileUpload", { count: this.uploadCount_, files: this.fileList_});
if(this.fileList_.length >= 5){
document.querySelector('#review_upload').style.display = 'none';
}
if(this.fileList_.length > 0){
document.querySelector('.apps-reviews-write-anonymous-box').style.marginTop = '8px';
}
}
handleFileDelete_(index) {
this.fileList_.splice(index, 1);
this.uploadCount_--;
sessionStorage.setItem('fileList', JSON.stringify(this.fileList_));
this.triggerEvent_("handleFileDelete", { count: this.uploadCount_, files: this.fileList_});
document.querySelector('#review_upload').style.display = 'block';
if(this.fileList_?.length === 0){
document.querySelector('.apps-reviews-write-anonymous-box').style.marginTop = '132px';
}
}
handleFilePreview_(index) {
const finalPreviewData = this.fileList_[index];
const filePreviewModal = document.getElementById('filePreviewModal');
const fullScreenVideo = document.getElementById('fullScreenVideo');
const fullScreenImage = document.getElementById('fullScreenImage');
const previewModalClose = document.getElementById('previewModalClose');
const previewLoading = document.getElementById('previewLoading');
filePreviewModal.style.display = 'block';
previewLoading.style.display = 'flex';
if(finalPreviewData?.type === 'video'){
const media = this.mediaParse_(this.fileList_[index]?.url);
fullScreenVideo.addEventListener('canplaythrough', function() {
previewLoading.style.display = 'none';
});
fullScreenImage.src = '';
fullScreenImage.style.display = 'none';
fullScreenVideo.style.display = 'block';
fullScreenVideo.src = media.mp4 || '';
} else {
fullScreenImage.onload = function() {
previewLoading.style.display = 'none';
};
fullScreenVideo.src = '';
fullScreenVideo.style.display = 'none';
fullScreenImage.style.display = 'block';
fullScreenImage.src = finalPreviewData.url;
}
previewModalClose.addEventListener('click', function() {
filePreviewModal.style.display = 'none';
});
}
handleFileLimit_() {
alert(window.AppReviewsLocale.comment_file_limit || 'please do not upload files more than 5');
this.triggerEvent_("handleFileLimit");
}
handleFileSizeLimit_() {
alert(window.AppReviewsLocale.comment_file_size_limit || 'File size does not exceed 10M');
}
clear(){
this.fileList_ = [];
this.uploadCount_ = 0;
sessionStorage.setItem('fileList', JSON.stringify(this.fileList_));
this.triggerEvent_("handleClear", { count: this.uploadCount_, files: this.fileList_});
document.querySelector('#review_upload').style.display = 'block';
}
mediaParse_(url) {
var result = {};
try {
url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {
try {
result[key] = decodeURIComponent(value);
} catch (e) {
result[key] = value;
}
});
result.preview_image = url.split('?')[0];
} catch (e) {};
return result;
}
triggerEvent_(name, data) {
const event = SPZUtils.Event.create(this.win, name, data);
this.action.trigger(this.element, name, event);
}
}
SPZ.defineElement('spz-custom-file-upload', SpzCustomFileUpload);
The review would not show in product details on storefront since it does not support to.
${function(){
const settings_product_title = "title";
const product_grid_image_size = "natural";
const settings_product_image_hover_on = true;
let settings_product_save_label = true;
const product_sold_out_label = true;
const settings_product_swatches_name = ["color"];
const settings_collection_color_swatches = true;
const variantShowLimit = 2;
const private_id = 'product-tmpl-' + Math.random().toFixed(6).slice(-6)
const product_variants = data.variants || [];
const product_id = data.id;
const images = data.images || [];
const image = data.image || {};
const imageWidth = image.width;
let imageHeight = image.height;
if (product_grid_image_size !== 'natural') {
imageHeight = (imageWidth * parseFloat(product_grid_image_size)) / 100;
}
const price = Number(data.price_min);
let compareAtPrice = Number(data.compare_at_price);
let offRatio = data.off_ratio;
const type = data.type;
const isMock = data.isMock;
let product_image_hover_on = false;
for (let i = 0; i < product_variants.length; i++) {
const item = product_variants[i];
const vcap = Number(item.compare_at_price);
if (item.price == price && vcap > compareAtPrice) {
compareAtPrice = vcap;
offRatio = item.off_ratio;
}
}
let second_image = null;
if (settings_product_image_hover_on) {
for (let i = 1; i < images.length; i++) {
const img = images[i];
if (img.src && img.src.indexOf('video=') === -1) {
second_image = img;
product_image_hover_on = true;
break;
}
}
}
let sold_label_on = false;
let sale_label_on = false;
if (settings_product_save_label == null) {
settings_product_save_label = true;
}
if (!data.available && product_sold_out_label) {
sold_label_on = true;
}
if (settings_product_save_label && compareAtPrice > price && data.available) {
sale_label_on = true;
}
const diffPrice = compareAtPrice - price;
const variantValues = [];
const showVariants = [];
if (data.need_variant_image && settings_collection_color_swatches && settings_product_swatches_name.length > 0) {
for (let i = 0; i < (data.options || []).length; i++) {
const option = data.originData.options[i];
const optionName = option.name && option.name.toLowerCase();
if (settings_product_swatches_name.includes(optionName)) {
for (let j = 0; j < product_variants.length; j++) {
const variant = product_variants[j];
const value = variant.options[i].value;
if (!variantValues.includes(value)) {
variantValues.push(value);
showVariants.push(variant);
}
}
break;
}
}
}
return `
${data.available ?
`
` : ''}
`;
}()}
${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]) || Object.keys(data).length > 1 ? data : null;
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(){
const productData = data.product;
const selectedVariant = productData.variants.find(v => v.available) || productData.variants[0];
const product_options = productData.options.filter(Boolean) || [];
const handleName = (name) => name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
const mouseoutevent = productData.options.map((opt) => `@${handleName(opt.name)}Mouseout="qs-selected-render-${opt.id}.rerender(data=event,redo=true);"`).join(' ');
const mouseoverevent = productData.options.map((opt) => `@${handleName(opt.name)}Mouseover="qs-selected-render-${opt.id}.rerender(data=event,redo=true);"`).join(' ');
return `
Price
${function() {
const origin = "shop"
const product = (origin === 'shop' ? data.product : data) || {};
const selectedVariant = product.variants.find(v => v.available) || product.variants[0];
return !!selectedVariant ? `
-
` : `
-
`;
}()}
${selectedVariant.available ? "Add to cart" : "SOLD OUT"}
Buy it now
Product was out of stock.
Product is unavailable.
` }()}
${function(){
let thumbSwitchName = '';
return (data.product.options || []).map((option, index) => {
const optionName = option.name || '';
const optionId = option.id || '';
const position = `option${index + 1}`;
let isThumbImage = false;
const thumbStyle = "image";
const variantType = "button";
if (data.product.need_variant_image && thumbSwitchName === '') {
const variantNames = ["Color"] || [];
for (let i = 0, len = variantNames.length; i < len; i++) {
const name = variantNames[i].toLowerCase();
if (name === optionName.toLowerCase()) {
isThumbImage = true;
thumbSwitchName = name;
}
}
}
return (variantType == 'dropdown' && !isThumbImage) ? `
${optionName.toLowerCase()}
${option.values.map(value => {
const selected = data.selectedValues[optionName] == value ? 'selected' : '';
return `${value} `
}).join('')}
` : `
${optionName}:
${data.selectedValues[optionName]}
${option.values.map((value,idx) => {
const selected = data.selectedValues[optionName] == value ? 'checked' : '';
let thumbImage = null;
if (isThumbImage) {
const variants = data.product.variants;
for (let i = 0, len = variants.length; i < len; i++) {
const variant = variants[i];
if (variant[position] == value && thumbImage == null) {
thumbImage = variant.image;
break;
}
}
}
return `
${value}
`
}).join('')}
`
}).join('')
}()}
${function(){
const { currentOptName, originData: { selectData, value } } = data;
const optionValue = selectData ? selectData[currentOptName].value[0] : value;
return `${optionValue} `
}()}
${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]) || Object.keys(data).length > 1 ? data : null;
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 ? `
` : ' ';
}
}()}
const carousel = document.getElementById('quick-view-images');
const selecotr = document.getElementById('quick-view-thumb-images');
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const { height } = entry.contentRect;
selecotr.style.height = height + 'px';
}
});
resizeObserver.observe(carousel);
${function(){
const remove_variant_images_on = false;
let product_swatches_name = ["Color"];
product_swatches_name = product_swatches_name.map((name) => name.toLowerCase());
const variantsNamesSet = new Set(data.options.map((opt) => opt.name.toLowerCase()) || []);
const containsSwatches = product_swatches_name.some((name) => variantsNamesSet.has(name));
const handleName = (name) => name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
const mouseoutevent = data.options.map((opt) => `@${handleName(opt.name)}Mouseout="qv-selected-render-${opt.id}.rerender(data=event,redo=true);"`).join(' ');
const mouseoverevent = data.options.map((opt) => `@${handleName(opt.name)}Mouseover="qv-selected-render-${opt.id}.rerender(data=event,redo=true);"`).join(' ');
const variants = data?.variants?.map((item) => item.image.path) || [];
const productData = data;
let images = data.images;
if(remove_variant_images_on && containsSwatches) {
images = data.images.filter((img) => !variants.includes(img.path));
}
const selectedVariant = data.variants.find(v => v.available) || data.variants[0];
const selectedIndex = !!selectedVariant ? images.findIndex(img => img.src === (selectedVariant.image && selectedVariant.image.src)) : 0;
const initialSlide = selectedIndex === -1 ? 0 : selectedIndex;
return `
${images.map((image, index) => `
`).join('')}
${images.map(image => `
`).join('')}
${images.length > 1 ? `
` : ''}
${data.title}
Price
${function() {
const origin = "view"
const product = (origin === 'shop' ? data.product : data) || {};
const selectedVariant = product.variants.find(v => v.available) || product.variants[0];
return !!selectedVariant ? `
-
` : `
-
`;
}()}
${selectedVariant.available ? "Add to cart" : "SOLD OUT"}
Buy it now
Product was out of stock.
Product is unavailable.
`
}()}
${function(){
let thumbSwitchName = '';
return (data.product.options || []).map((option, index) => {
const optionName = option.name || '';
const optionId = option.id || '';
const position = `option${index + 1}`;
let isThumbImage = false;
const thumbStyle = "image";
const variantType = "button";
if (data.product.need_variant_image && thumbSwitchName === '') {
const variantNames = ["Color"] || [];
for (let i = 0, len = variantNames.length; i < len; i++) {
const name = variantNames[i].toLowerCase();
if (name === optionName.toLowerCase()) {
isThumbImage = true;
thumbSwitchName = name;
}
}
}
return (variantType == 'dropdown' && !isThumbImage) ? `
${optionName.toLowerCase()}
${option.values.map(value => {
const selected = data.selectedValues[optionName] == value ? 'selected' : '';
return `${value} `
}).join('')}
` : `
${optionName}:
${data.selectedValues[optionName]}
${option.values.map((value,idx) => {
const selected = data.selectedValues[optionName] == value ? 'checked' : '';
let thumbImage = null;
if (isThumbImage) {
const variants = data.product.variants;
for (let i = 0, len = variants.length; i < len; i++) {
const variant = variants[i];
if (variant[position] == value && thumbImage == null) {
thumbImage = variant.image;
break;
}
}
}
return `
${value}
`
}).join('')}
`
}).join('')
}()}
${function(){
const { currentOptName, originData: { selectData, value } } = data;
const optionValue = selectData ? selectData[currentOptName].value[0] : value;
return `${optionValue} `
}()}
${function() {
const price = data.variant?.price || data.product.selectedVariant?.price;
const compare_at_price = data.variant?.compare_at_price || data.product.selectedVariant?.compare_at_price;
return `
`
}()}
${function() {
return data.product.options.length > 0 ? data.product.options.map((option, index) => {
return `
${function() {
if(data.product.config.isMobile === false) {
return `
`
} else {
return `
`
}
}()}
`
}).join('') : ``;
}()}
${function() {
const value = (data.originData && data.originData.value) || data.value;
const isHasValue = value ? true : false;
return `
${value ? value : "" }
`
}()}
${data.product.selectedSkuText ? data.product.selectedSkuText : ''}
${function() {
return (data.product.options || []).map((option, index) => {
return `
${option.name}:
${option.values.map((value,idx) => {
let selectedOptions = data.product.selectedVariant.options;
let selected = '';
if(selectedOptions.length) {
for(const key in selectedOptions) {
if(selectedOptions[key].value == value) {
selected = 'checked'
}
}
}
return `
${value}
`;
}).join('')}
`;
}).join('')
}()}
${data.value ? data.value : ''}
${function() {
let dropdownCloseEvent = '';
if(!data.config.isMobile && (data.config.style == 'circle' || data.config.style == 'square')) {
data.product.options?.forEach((item, index) => {
dropdownCloseEvent += `app-atc-popover-${index}.close;`;
})
}
return `
`;
}()}
${function(){
let product_change_event = '';
const product_options = data.product.options?.filter(Boolean) || [];
for (let opt of product_options) {
const nameEscape = opt.name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
product_change_event = product_change_event + `add-cart-selected-variant-${opt.id}.rerender(data=event.selectedValues.${opt.name});`;
}
return `
${function() {
const isDarkBg = data.isDarkBg;
const textColor = isDarkBg ? '#ffffff' : '#212B36';
const delPriceColor = isDarkBg ? '#E0E0E2' : '#9CA0B0';
const variantColor = isDarkBg ? '#ffffff' : '#939393';
const config = data.config;
return `
`
}()}
${function(){
if(data.is_button_click_info) {
return `
${data.config.button_text || 'Add to cart'}
${data.config.button_text || 'Add to cart'}
`
} else {
return `
${function() {
if(data.is_button_click_info) {
return `
`
} else {
return `
`
}
}()}
`
}
}()}
${function() {
if(data.is_button_click_info) {
return `
${function() {
if(data.is_button_click_info) {
return `
`
} else {
return `
`
}
}()}
`
} else {
return ``
}
}()}
`
}()}
const TAG = 'spz-custom-add-to-cart';
class SpzCustomAddToCart extends SPZ.BaseElement {
constructor(element) {
super(element);
this.plugin_timestamp = Date.now();
this.defaultColorConfig = {
module_bg: "#FFFFFF",
button_bg: "#E84926",
button_color: "#FFFFFF",
text_color: "#202020",
price_color: "#E84926",
border_color: "#E6E6E6",
border_bg: "#FFFFFF",
round_size: '4'
};
this.config = this.defaultColorConfig;
this.originStickyTop = 0;
this.qty = 1;
this.variantId = this.element.getAttribute('variant-id');
this.trackMap = {
qty: this.trackQty.bind(this),
variant: this.trackVariant.bind(this),
addToCart: this.trackAddtoCart.bind(this),
atcView: this.trackAtcView.bind(this),
}
this.isHasView = false;
this.isFirstUpdateVariant=false;
}
static deferredMount() {
return false;
}
buildCallback() {
this.action_ = SPZServices.actionServiceForDoc(this.element);
this.templates_ = SPZServices.templatesForDoc(this.element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.locale_ = SPZServices.localeFor(this.win);
this.setupAction_();
}
mountCallback() {
// 初始化
this.init();
}
async init() {
// 如果不是详情页,不需要执行后面js
if (window.SHOP_PARAMS.template_type !== '1') return;
await this.setLocale();
this.handleIsRender();
this.bingEvents();
}
bingEvents() {
// 设备切换 重新渲染
window.addEventListener('resize',
SPZCore.Types.debounce(
this.win,
(invocation) => {
// 关闭弹窗, 解决切换屏幕尺寸不能滚动的问题
this.triggerEvent_('closeShopModal');
this.config = {
...this.config,
isMobile: window.innerWidth < 768 ? true : false,
position: window.innerWidth < 768 ? 'down' : this.config.display_position
};
this.renderAddToCart();
},
200
))
}
// 获取多语言
async setLocale() {
let data;
try {
//多语言
data = await this.locale_.i18n(['product', 'products']);
} catch (error) {
console.error(error);
}
this.i18n = {
'sold_out': 'Sold out',
'add_to_cart': 'Add to cart',
'unavailable': 'Unavailable',
'product_unavailable': 'Product is unavailable.',
...data?.product?.product_detail,
...data?.products?.product,
}
}
getProductData() {
let pJson = document.querySelector('#product-json');
if (pJson) {
return JSON.parse(pJson.innerHTML)?.product;
} else if (typeof $ === 'function') {
return $(document).data('djproduct')?.product;
}
return undefined;
}
getAddCartBtn() {
return document.querySelector('[data-section-type="product_detail"] [data-click="addToCart"], [data-section-type="product_detail"] [role="addToCart"], [data-section-type="product_detail"] [data-click="buyNow"], [data-section-type="product_detail"] [role="buyNow"], [data-section-type="product"] [data-click="addToCart"], [data-section-type="product"] [role="addToCart"], [data-section-type="product"] [data-click="buyNow"], [data-section-type="product"] [role="buyNow"]');
}
handleObserver() {
if(this.config.trigger_condition == 'theme_hidden') {
const $addCartBtn = this.getAddCartBtn();
if($addCartBtn) {
// 配置了加购/购买按钮; 设置按钮为observer观察目标
$addCartBtn.setAttribute('id', 'app-atc-need-sticky-buttons')
}
}
}
findAncestor(node, selector) {
while (node) {
if (node.querySelector(selector)) {
return node;
}
node = node.parentElement;
}
return null;
}
getThemeProductInfoForm() {
let $themeForm = '';
const $themeProductInfo = document.querySelector('[data-section-type="product_detail"], [data-section-type="product"]');
$themeForm = $themeProductInfo?.querySelector('form');
return $themeForm;
}
// 获取主题商品加购数量
getThemeQuantity() {
let $themeForm = this.getThemeProductInfoForm();
if($themeForm) {
const formData = new FormData($themeForm);
const quantity = formData.get('quantity');
return quantity;
} else {
return null;
}
}
getThemeInitVariantsData() {
const $themeForm = this.getThemeProductInfoForm();
const formData = new FormData($themeForm);
}
// 获取主题初始表单数据
getThemeProductFormData = () => {
const $themeForm = this.getThemeProductInfoForm();
if($themeForm) {
const form_data = new FormData($themeForm);
const form_data_format = {}
for (const [key, value] of form_data) {
form_data_format[key] = value;
}
return form_data_format;
} else {
return null;
}
}
brightnessByColor(c) {
let color = '' + c,
isHEX = c.indexOf('#') == 0,
isRGB = c.indexOf('rgb') == 0;
let r, g, b;
if (isHEX) {
var m = color.substr(1).match(color.length == 7 ? /(\S{2})/g : /(\S{1})/g);
if (m) {
(r = parseInt(m[0], 16)), (g = parseInt(m[1], 16)), (b = parseInt(m[2], 16));
}
}
if (isRGB) {
const m = color.match(/^rgba\((\d+),\s*(\d+),\s*(\d+),(\d+)\)$/);
if (m) {
(r = m[1]), (g = m[2]), (b = m[3]);
}
}
if (typeof r != 'undefined') return (r * 299 + g * 587 + b * 114) / 1000;
};
getSelectedVariant() {
const product = this.getProductData();
return product.variants.find(item => item.id == this.variantId);
}
setSelectedOption(product) {
const selectedVariant = this.getSelectedVariant();
const productData = Object.assign({}, product);
// 处理下拉选项
productData?.options?.forEach(option => {
option.selectList = [];
option.values?.forEach(value => {
let selectStatus = '';
selectedVariant?.options?.forEach(item => {
if(item.name == option.name && item.value == value) {
selectStatus = 'checked';
}
})
option.selectList.push({name: option.name, value: value, checked: selectStatus});
})
})
return productData;
}
getSelectedSkuText() {
const selectVariant = this.getSelectedVariant();
if(!selectVariant) {
return '';
}
const selectList = selectVariant.options;
for (var i = 0; i < selectList.length; i++) {
selectList[i].value = selectVariant[`option${i + 1}`];
}
const selectedSkuText = selectList
.map( item => {
return item.value;
})
.join('/');
return selectedSkuText;
}
// 是否展示 加购弹窗
getIsButtonClickInfo() {
const product = this.getProductData();
const isMultipleProduct = product.available && !product.has_only_default_variant; // 多款式商品
const isButtonClickInfo = this.config.isMobile && (this.config.style_mobile === "mb_simple" || this.config.style_mobile === "mb_circle") && this.config.button_click_mobile === 'info' && isMultipleProduct;
return isButtonClickInfo;
}
// 是否跟随主题的加购数量 isFollowThemeQty (移动端 simple、circle模版、 PC端 simple模版加购数量从主题获取)
getIsFollowThemeQty() {
const followThemeQtyMobile = (this.config.style_mobile === "mb_simple" || this.config.style_mobile === "mb_circle") && this.config.isMobile;
const followThemeQtyPc = this.config.style === "simple" && !this.config.isMobile;
const isFollowThemeQty = (followThemeQtyPc || followThemeQtyMobile) ? true : false;
return isFollowThemeQty;
}
getBannerRenderData() {
const product = this.getProductData();
// 当前选中变体
const selectedVariant = this.getSelectedVariant();
// 变体options下拉列表数据处理
const productData = this.setSelectedOption(product);
// 选中的sku文案
const selectedSkuText = this.getSelectedSkuText();
// 是否展示 加购弹窗
const is_button_click_info = this.getIsButtonClickInfo();
// 主题是否有配置加购/购买按钮
const isHasAddCartBtn = this.getAddCartBtn() ? true : false;
// 加购数量是否跟随主题
const isFollowThemeQty = this.getIsFollowThemeQty();
// 浅色背景颜色配置
const brightness = this.brightnessByColor(this.config.module_bg);
const isDarkBg = brightness < 115;
const renderData = {
product: {
...productData,
config: {...this.config},
selectedVariant,
selectedSkuText,
},
config: this.config,
qty: this.qty,
variant: selectedVariant,
selectedVariant,
selectedSkuText,
is_button_click_info,
isDarkBg,
isHasAddCartBtn,
isFollowThemeQty,
i18n: this.i18n
};
return renderData;
}
// render add_to_cart
async renderAll() {
const renderData = this.getBannerRenderData();
this.triggerEvent_('renderBanner', renderData);
}
getConfigData() {
return fetch(`/api/add-to-cart-config`).then(res => {
return res.json();
});
}
async getConfig() {
if(!this.config.hasOwnProperty('open_status')) {
const res = await this.getConfigData();
if (res.state === 0 && res.data.open_status) {
// config数据转化
this.transConfigData(res.data);
}
}
return this.config;
}
transConfigData(data) {
const resConfig = data;
this.config = {
...this.config,
...resConfig,
isMobile: window.innerWidth < 768 ? true : false,
position: window.innerWidth < 768 ? 'down' : resConfig.display_position
};
// 颜色跟随系统时
if(resConfig.color_setting === 'default' || resConfig.color_setting === 'theme') {
this.config = {
...this.config,
...this.defaultColorConfig
}
}
}
// 是否应用到商店
async handleIsRender() {
const res = await this.getConfigData();
if (res.state === 0 && res.data.open_status) {
// config数据转化
this.transConfigData(res.data);
this.handleObserver();
// 渲染加购内容
this.renderAddToCart();
}
}
renderAddToCart() {
const product = this.getProductData();
if (product.product_type === 'gift_card') return ;
if (!product.available) return;
this.renderAll();
}
// 选择变体 上报
trackVariant() {
const product = this.getProductData();
window.sa &&
sa.track('plugin_atc_variant_click', {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile
});
}
// 更改数量上报
trackQty() {
const product = this.getProductData();
window.sa &&
sa.track('plugin_atc_qty_click', {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile
});
}
// 加购上报
trackAddtoCart() {
const product = this.getProductData();
let properties = '';
let source = 'atc';
if (this.config.button_action === 'checkout') {
source = 'buy_now';
}
const obj = {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile,
button_action: this.config.button_action,
plugin_timestamp: this.plugin_timestamp
};
const options = {
product_id: product.id,
variant_id: this.variantId,
quantity: this.qty,
source,
variant: this.getSelectedVariant(),
product: product,
process: (window.SHOP_PARAMS.product_settings || {}).add_to_cart_process,
properties: properties
}
// 注册曝光参数到add_to_cart事件
window.spzutm && window.spzutm.registerParams('add_to_cart', { add_to_cart: JSON.stringify(obj) });
window.sa && sa.track('plugin_atc_button_click', obj);
this.trackHookAddTocart();
}
trackAtcView() {
const product = this.getProductData()
if(!this.isHasView) {
sa.track('plugin_atc_view', {
product_id: product.id,
button_action: this.config.button_action,
style: this.config.style,
style_mobile: this.config.style_mobile,
plugin_timestamp: this.plugin_timestamp
});
this.isHasView = true;
}
}
trackHookAddTocart(options) {
//FLASH及之后主题trigger事件上报采用HOOK
window.djInterceptors &&
window.djInterceptors.track &&
window.djInterceptors.track.use({
event: 'dj.addToCart',
params: {
id: options.product_id,
number: options.quantity,
childrenId: options.variant.id,
item_price: options.variant.price,
name: options.product.title,
type: options.variant.type ? options.variant.type : options.product.type,
properties: properties,
quantity: options.quantity,
variant_id: options.variant.id,
product_id: options.product_id,
source: 'atc',
process: options.process
},
once: true
});
}
// 加购弹窗
renderShopModal_() {
const renderData = this.getBannerRenderData();
this.triggerEvent_('showAddToCartModal', renderData);
}
showSuccessToast() {
this.triggerEvent_('showAddToCartToast');
}
updateSelectVariant = async(selectedVariant, product) => {
const configData = await this.getConfig();
const productData = this.setSelectedOption(product);
const selectedSkuText = this.getSelectedSkuText();
const renderData = {
...productData,
selectedVariant,
config: configData,
selectedSkuText
};
this.triggerEvent_('variantChange', renderData);
const renderBannerData = this.getBannerRenderData();
if(renderBannerData.is_button_click_info && this.config.style_mobile === "mb_circle") {
this.triggerEvent_('renderBannerClickInfo', renderBannerData);
}
}
getUrlVariantId() {
const urlParams = new URLSearchParams(window.location.search);
const variantId = urlParams.get('variant');
return variantId;
}
// 商详页变体切换
djVariantChange(detail) {
const product = this.getProductData();
// 是否为商详页当前商品
const isDetailProduct = product?.id === detail?.product?.id;
if(!detail || !isDetailProduct) {
return;
}
const selectVariant = detail.selected;
// 更新变体id
const variantId = selectVariant.id;
const oldVariantId = this.variantId;
if(variantId) {
this.triggerEvent_('renderSelectedVariant');
if(oldVariantId !== variantId || !this.isFirstUpdateVariant) {
this.isFirstUpdateVariant = true;
this.variantId = variantId;
this.updateSelectVariant(selectVariant, detail.product);
}
} else if(!variantId){
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.triggerEvent_('renderBannerNoSelectedVariant', { product: detail.product });
}
}
}
// 不展示变体的模版
isHiddenVariantsTemplate() {
return ((this.config.style_mobile === 'mb_circle' || this.config.style_mobile === 'mb_simple') && this.config.isMobile) || (this.config.style === 'simple' && !this.config.isMobile);
}
// 主题未选中变体
isUnSelectedThemeVariants() {
// 不展示变体的模版
const formData = this.getThemeProductFormData();
return (formData && formData.hasOwnProperty('variant_id') && (!formData.variant_id || formData.variant_id === 'undefined'));
}
// 禁用加购
disabledAtc() {
// 不展示变体的模版
const isHiddenVariantsTemplate = this.isHiddenVariantsTemplate();
const isUnSelectedThemeVariant = this.isUnSelectedThemeVariants();
return isHiddenVariantsTemplate && isUnSelectedThemeVariant;
}
// 滚动至主题变体处
scrollToThemeVariant() {
const themeFormEl = this.getThemeProductInfoForm();
if(themeFormEl) {
this.triggerEvent_('showUnSelectedVariantTips');
setTimeout(() => {
themeFormEl.scrollIntoView({ behavior: 'smooth' });
}, 1000);
}
}
async setupAction_() {
// 上报处理
this.registerAction('saTrack', async(invocation) => {
const data = invocation.args;
// this.trackMap[data.type]?.();
})
// 更新variantId
this.registerAction('updateVriantId', async(invocation) => {
const data = invocation.args.data;
if(data.variant_id && data.variant_id != 'undefined') {
this.variantId = data.variant_id;
}
})
// 渲染弹窗加购&购买按钮
this.registerAction('renderShopModal', async(invocation) => {
this.renderShopModal_();
})
this.registerAction('bannerExtraRender', async(invocation) => {
const renderData = this.getBannerRenderData();
if(renderData.is_button_click_info && this.config.style_mobile === "mb_circle") {
this.triggerEvent_('renderBannerClickInfo', renderData);
}
})
// 加购数量同步
this.registerAction('handleAddToCart', async(invocation) => {
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.scrollToThemeVariant();
return;
}
const quantity = this.getThemeQuantity();
if(this.config.button_action === "checkout" ) {
this.triggerEvent_('updateQty', { quantity: Number(quantity) || this.qty });
this.triggerEvent_('handleBuyNow');
} else {
this.triggerEvent_('updateQty', { quantity: Number(quantity) || this.qty });
this.triggerEvent_('handleAtc');
}
})
// 渲染弹窗加购&购买按钮
this.registerAction('handleFixedBanner', async(invocation) => {
// 固定定位时, 触发ljs-sticky 组件showSticky方法,展示banner
if(this.config.trigger_condition == 'fixed') {
this.triggerEvent_('showBanner');
// this.trackAtcView();
}
})
this.registerAction('handleVariantsRender', async(invocation) => {
const data = invocation.args.data;
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.triggerEvent_('renderBannerNoSelectedVariant', { product: data });
return;
}
this.triggerEvent_('renderSelectedVariant', { product: data });
this.triggerEvent_('variantChange', data);
})
this.registerAction('handleShowErrorToast', async(invocation) => {
const data = invocation.args.data;
if(data?.message) {
this.triggerEvent_('showAtcErrorToast', 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.LOGIC;
}
}
SPZ.defineElement(TAG, SpzCustomAddToCart)
const TAG = 'spz-custom-painter-button-animation';
const MAX_ITERATION_COUNT = 99999999;
const SITE = (window.C_SETTINGS && window.C_SETTINGS.routes && window.C_SETTINGS.routes.root) || '';
const ADD_TO_CART_ANIMATION_SETTING =
`${SITE}/api/marketing_atmosphere_app/add_to_cart_btn_animation/setting`;
class SpzCustomPainterButtonAnimation extends SPZ.BaseElement {
/**@override */
static deferredMount() {
return false;
}
/** @param {!SpzElement} element */
constructor(element) {
super(element);
/** @private {!../../src/service/xhr-impl.Xhr} */
this.xhr_ = SPZServices.xhrFor(this.win);
/** @private {Object} */
this.data_ = null;
/** @private {Element} */
this.addToCartButton_ = null;
/** @private {boolean} */
this.productAvailable_ = true;
/** @private {number} */
this.timerId_ = null;
/** @private {number} */
this.animationExecutionCount_ = 0;
/** @private {boolean} */
this.selectedVariantAvailable_ = true;
/** @private {number} */
this.delay_ = 5000;
/** @private {number} */
this.iterationCount_ = 5;
/** @private {string} */
this.animationClass_ = '';
}
/** @override */
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
/** @override */
buildCallback() {
this.productAvailable_ = this.element.hasAttribute('product-available');
this.selectedVariantAvailable_ = this.element.hasAttribute('selected-variant-available');
}
/** @override */
mountCallback() {
this.render_();
}
/** @private */
render_() {
if (!this.productAvailable_) {
return;
}
this.fetch_().then((data) => {
if (!data) {
return;
}
this.data_ = data;
this.animationClass_ = `painter-${data.animation_name}-animation`;
this.iterationCount_ =
data.animation_iteration_count === 'infinite'
? MAX_ITERATION_COUNT
: data.animation_iteration_count;
const animationDuration = 1;
const animationDelay = data.animation_delay || 5;
this.delay_ = (animationDuration + animationDelay) * 1000;
this.handleButtonEffect_();
});
}
/**
* @param {JsonObject} data
* @return {(null|Object)}
* @private
*/
parseJson_(data) {
try {
return JSON.parse(data);
} catch (e) {
return null;
}
}
/**
* @return {Promise}
* @private
*/
fetch_() {
return this.xhr_.fetchJson(ADD_TO_CART_ANIMATION_SETTING).then((data) => {
if (!data || !data.enabled) {
return null;
}
return this.parseJson_(data.detail);
});
}
/** @private */
getAddToCartButton_() {
this.addToCartButton_ = SPZCore.Dom.scopedQuerySelector(
document.body,
'[data-section-type="product"] [role="addToCart"], [data-section-type="product_detail"] [role="addToCart"], [data-section-type="product_detail"] [data-click="addToCart"], [data-section-type="product"] [data-click="addToCart"]'
);
}
/** @private */
restartAnimation_() {
this.addToCartButton_.classList.remove(this.animationClass_);
this.addToCartButton_./* OK */ offsetWidth;
this.addToCartButton_.classList.add(this.animationClass_);
this.animationExecutionCount_++;
}
/** @private */
clearTimer_() {
this.win.clearInterval(this.timerId_);
this.timerId_ = null;
}
/** @private */
setupTimer_() {
this.timerId_ = this.win.setInterval(() => {
this.restartAnimation_();
if (this.animationExecutionCount_ >= this.iterationCount_) {
this.removeAnimationClass_();
this.clearTimer_();
}
}, this.delay_);
}
/** @private */
restartTimer_() {
if (this.animationExecutionCount_ >= this.iterationCount_) {
this.removeAnimationClass_();
return;
}
this.setupTimer_();
}
/** @private */
listenVariantChange_() {
SPZUtils.Event.listen(self.document, 'dj.variantChange', (e) => {
const selectedVariant = e.detail && e.detail.selected;
if (!selectedVariant) {
return;
}
const {available} = selectedVariant;
if (this.selectedVariantAvailable_ !== available) {
this.selectedVariantAvailable_ = available;
this.clearTimer_();
if (available) {
this.restartTimer_();
}
}
});
}
/** @private */
removeAnimationClass_() {
this.win.setTimeout(() => {
this.addToCartButton_.classList.remove(this.animationClass_);
}, 1000);
}
/** @private */
handleButtonEffect_() {
this.getAddToCartButton_();
if (!this.addToCartButton_) {
return;
}
if (this.selectedVariantAvailable_) {
++this.animationExecutionCount_;
this.addToCartButton_.classList.add(this.animationClass_);
if (this.iterationCount_ === 1) {
this.removeAnimationClass_();
return;
}
this.setupTimer_();
}
this.listenVariantChange_();
}
}
SPZ.defineElement(TAG, SpzCustomPainterButtonAnimation);