zz-shopify-components
Version:
Reusable Shopify components for theme projects
402 lines (355 loc) • 11.8 kB
JavaScript
class BuyNowBottomBar extends HTMLElement {
loading = false;
bindProducts = [];
mainProductId = '';
currency = '';
showMore = false;
isEduProduct = false;
isLoggedIn = false;
isEduVerified = false;
eduPageUrl = '';
constructor() {
super();
this.cart =
document.querySelector('cart-notification') ||
document.querySelector('cart-drawer');
this.careGuide = this.dataset.careGuide;
this.hasCare = this.dataset.hasCare || false;
this.init();
this.currency = this.dataset.currency;
this.isocode = this.dataset.isocode;
this.isEduProduct = this.dataset.isEduProduct === 'true';
this.isLoggedIn = this.dataset.isLoggedIn === 'true';
this.isEduVerified = this.dataset.isEduVerified === 'true';
this.eduPageUrl = this.dataset.eduPageUrl;
}
init() {
this.querySelector('button').addEventListener('click', async () => {
this.handleToPay();
});
if (this.querySelector('.show-more-btn')) {
this.querySelector('.show-more-btn').addEventListener(
'click',
async () => {
this.toggleShowMoreDesc();
}
);
}
window.addEventListener('pageshow', () => {
this.updatePrice();
});
}
handleAddToCart() {
this.getMainProduct();
this.getBindProduct();
this.onSubmitHandler();
}
showCareChooseDialog() {
const careDialog = document.querySelector(
'product-detail-dialog-hovercare-choose'
);
if (careDialog) {
careDialog.showModal();
}
}
handleToPay() {
this.getMainProduct();
const cartUrl = `/cart/${this.mainProductId}:${this.mainProductQuantity}?checkout¤cy=${this.isocode}`;
window.location.href = cartUrl;
}
async onSubmitHandler() {
const data = {
items: [
{
id: this.mainProductId,
quantity: 1,
},
...this.bindProducts,
],
sections: 'cart-drawer,cart-icon-bubble',
};
this.toggleLoading();
await fetch(`${routes.cart_add_url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/javascript',
},
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((response) => {
if (response.status) {
publish(PUB_SUB_EVENTS.cartError, {
source: 'product-form',
productVariantId: this.mainProductId,
errors: response.errors || response.description,
message: response.message,
});
} else if (!this.cart) {
window.location = window.routes.cart_url;
return;
}
if (!this.error)
publish(PUB_SUB_EVENTS.cartUpdate, {
source: 'product-form',
productVariantId: this.mainProductId,
cartData: response,
});
this.error = false;
const quickAddModal = this.closest('quick-add-modal');
if (quickAddModal) {
document.body.addEventListener(
'modalClosed',
() => {
setTimeout(() => {
this.cart.renderContents(response);
});
},
{ once: true }
);
quickAddModal.hide(true);
} else {
this.cart.renderContents(response);
}
})
.catch((e) => {
console.error(e);
})
.finally(() => {
if (this.cart && this.cart.classList.contains('is-empty'))
this.cart.classList.remove('is-empty');
this.toggleLoading();
});
}
getMainProduct() {
this.mainProductId = document.querySelector(
'product-buy-now-selector'
).currentVariantId;
this.mainProductQuantity = document.querySelector(
'product-buy-now-counter input'
).value;
}
getBindProduct() {
this.bindProducts = [];
const tags = Array.from(document.querySelectorAll('accessory-product'));
tags.forEach((item) => {
if (item.isSelected) {
this.bindProducts.push({
id: item.currentId,
quantity: item.getNumber(),
});
}
});
const careTags = Array.from(document.querySelectorAll('care-product'));
if (careTags && careTags.length) {
careTags.forEach((item) => {
if (item.isShow && item.isSelected) {
this.bindProducts.push({
id: item.currentId,
quantity: 1,
});
}
});
}
const careDialog = document.querySelector(
'product-detail-dialog-hovercare-choose'
);
if (careDialog && careDialog.chooseId) {
this.bindProducts.push({
id: careDialog.chooseId,
quantity: 1,
});
}
}
toggleLoading() {
this.loading = !this.loading;
this.querySelector('.buy-loading').classList.toggle('tw-hidden');
}
toggleShowMoreDesc() {
const tipEl = this.querySelector('.bar-tip');
const svg = this.querySelector('.show-more-btn');
this.showMore = !this.showMore;
gsap.to(svg, {
rotation: this.showMore ? 180 : 0,
duration: 0.3,
});
tipEl.classList.toggle('show-more-desc', this.showMore);
}
// available: "true" or "false"
toggleAddToCartButton(available) {
const button = this.querySelector('button');
const btnText = this.querySelector('.buy-now-bottom-bar-btn-text');
if (!button) {
return;
}
if (button.disabled && available === 'true') {
button.disabled = false;
btnText.textContent = this.dataset.btnText;
} else if (!button.disabled && available === 'false') {
button.disabled = true;
btnText.textContent = this.dataset.btnOutOfStockText;
}
}
updatePrice() {
if (!window.Decimal) {
return;
}
console.log('this.isocode', this.isocode);
console.log('this.currency', this.currency);
const main = document
.querySelector('.product-version-option input:checked')
?.closest('.product-version-option');
const mainProductQuantity = document.querySelector(
'product-buy-now-counter input'
).value;
const {
price: mainPrice,
before: mainBefore,
available: mainAvailable,
} = main.dataset;
let total_price = new Decimal(this.handlePrice(mainPrice));
let total_before = new Decimal(this.handlePrice(mainBefore));
total_price = total_price.times(mainProductQuantity);
total_before = total_before.times(mainProductQuantity);
console.log('total_price', total_price);
console.log('total_before', total_before);
this.toggleAddToCartButton(mainAvailable);
// 找到被选中的附加产品
const selectedAccessories = Array.from(
document.querySelectorAll('accessory-product')
).filter((item) => item.isSelected);
for (const item of selectedAccessories) {
const count = parseInt(item.getNumber(), 10);
const priceEl = item.querySelector('.current-price');
if (!priceEl) {
console.warn('找不到 .current-price 元素');
continue;
}
const { price, before } = priceEl.dataset;
const currentPrice = new Decimal(this.handlePrice(price));
const originPrice = new Decimal(this.handlePrice(before));
total_price = total_price.plus(currentPrice.times(count));
const actualBefore = currentPrice.greaterThan(originPrice)
? currentPrice.times(count)
: originPrice.times(count);
total_before = total_before.plus(actualBefore);
}
// 找到care 商品
const bindCares = Array.from(document.querySelectorAll('care-product'));
bindCares.forEach((item) => {
const count = parseInt(item.getNumber(), 10) || 0; // 确保 count 是有效数字
if (item.isShow && item.isSelected) {
const priceEl = item.querySelector('.current-price');
if (priceEl) {
const { price, before } = priceEl.dataset;
const currentPrice = new Decimal(this.handlePrice(price));
const originPrice = new Decimal(this.handlePrice(before));
total_price = total_price.plus(currentPrice.times(count));
const actualBefore = currentPrice.lessThan(originPrice)
? originPrice.times(count)
: currentPrice.times(count);
total_before = total_before.plus(actualBefore);
} else {
console.warn('找不到 .current-price 元素');
}
}
});
// 计算加个
const priceText = this.currency + total_price.toString();
const beforeText = this.currency + total_before.toString();
const showBefore = total_before.greaterThan(total_price);
requestAnimationFrame(() => {
const priceEl = this.querySelector('.price');
if (priceEl) {
priceEl.textContent = Shopify.formatMoneyFromDecimal(total_price);
}
const beforeEl = this.querySelector('.before');
if (beforeEl) {
beforeEl.textContent = Shopify.formatMoneyFromDecimal(total_before);
}
const mbPriceEl = this.querySelector('.mb_price');
if (mbPriceEl) {
mbPriceEl.textContent = Shopify.formatMoneyFromDecimal(total_price);
}
const mbBeforeEl = this.querySelector('.mb_before');
if (mbBeforeEl) {
mbBeforeEl.textContent = Shopify.formatMoneyFromDecimal(total_before);
}
// 计算折扣
const discountEl = this.querySelectorAll('.price-discouter');
if (discountEl.length > 0) {
const percent = total_price.times(100).dividedBy(total_before);
const discountPercent = 100 - percent;
const discountValue = total_before.minus(total_price);
discountEl.forEach((el) => {
if (total_before.greaterThan(total_price)) {
el.classList.remove('tw-hidden');
el.textContent =
this.dataset.discountType === 'percent'
? '- ' + Math.round(discountPercent) + '%'
: 'Save ' + this.currency + discountValue;
} else {
el.classList.add('tw-hidden');
}
});
}
});
// const toggleClass = (el, show) => {
// if (!el) return;
// el.classList.toggle('tw-hidden', !show);
// };
// toggleClass(beforeEl, showBefore);
// toggleClass(mbBeforeEl, showBefore);
this.updatePayPal(total_price.toString());
}
handlePrice(price) {
console.log('handlePrice', price);
if (!price) return 0;
if (this.currency === '€') {
return this.handleEuroPrice(price);
}
return price.toString().replace(/,/g, '') || 0;
}
handleEuroPrice(price) {
let result = price.toString();
const priceFormat = this.dataset.priceFormat || '';
if (priceFormat.includes('amount_with_comma_separator')) {
result = result.replace('.', '');
result = result.replace(',', '.');
} else {
result = result.replace(/,/g, '');
}
return result || 0;
}
updatePayPal(price) {
if (!price) return;
clearTimeout(this._paypalTimer);
const paypalEls = document.querySelectorAll('.product-buy-paypal');
if (!this.paypalLoading) {
this.paypalLoading = true;
paypalEls.forEach((el) => {
el.classList.add('tw-daisy-loading');
});
}
paypalEls.forEach((el) => {
el.dataset.ppAmount = price;
});
this._paypalTimer = setTimeout(() => {
paypalEls.forEach((el) => {
el.classList.remove('tw-daisy-loading');
});
this.paypalLoading = false;
}, 800);
}
// 有预期到货时间不展示
toggleShowTitleDesc(available) {
const barTitleDesc = this.querySelector('.bar-title-desc');
if (!available) {
barTitleDesc.classList.add('tw-hidden');
} else {
barTitleDesc.classList.remove('tw-hidden');
}
}
}
customElements.define('buy-now-bottom-bar', BuyNowBottomBar);