zz-shopify-components 0.12.1-beta.0 → 0.12.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,401 @@
1
+ class BuyNowBottomBar extends HTMLElement {
2
+ loading = false;
3
+ bindProducts = [];
4
+ mainProductId = '';
5
+ currency = '';
6
+ showMore = false;
7
+ isEduProduct = false;
8
+ isLoggedIn = false;
9
+ isEduVerified = false;
10
+ eduPageUrl = '';
11
+ constructor() {
12
+ super();
13
+ this.cart =
14
+ document.querySelector('cart-notification') ||
15
+ document.querySelector('cart-drawer');
16
+ this.careGuide = this.dataset.careGuide;
17
+ this.hasCare = this.dataset.hasCare || false;
18
+ this.init();
19
+ this.currency = this.dataset.currency;
20
+ this.isocode = this.dataset.isocode;
21
+ this.isEduProduct = this.dataset.isEduProduct === 'true';
22
+ this.isLoggedIn = this.dataset.isLoggedIn === 'true';
23
+ this.isEduVerified = this.dataset.isEduVerified === 'true';
24
+ this.eduPageUrl = this.dataset.eduPageUrl;
25
+ }
26
+
27
+ init() {
28
+ this.querySelector('button').addEventListener('click', async () => {
29
+ this.handleToPay();
30
+ });
31
+
32
+ if (this.querySelector('.show-more-btn')) {
33
+ this.querySelector('.show-more-btn').addEventListener(
34
+ 'click',
35
+ async () => {
36
+ this.toggleShowMoreDesc();
37
+ }
38
+ );
39
+ }
40
+ window.addEventListener('pageshow', () => {
41
+ this.updatePrice();
42
+ });
43
+ }
44
+ handleAddToCart() {
45
+ this.getMainProduct();
46
+ this.getBindProduct();
47
+ this.onSubmitHandler();
48
+ }
49
+ showCareChooseDialog() {
50
+ const careDialog = document.querySelector(
51
+ 'product-detail-dialog-hovercare-choose'
52
+ );
53
+ if (careDialog) {
54
+ careDialog.showModal();
55
+ }
56
+ }
57
+
58
+ handleToPay() {
59
+ this.getMainProduct();
60
+ const cartUrl = `/cart/${this.mainProductId}:${this.mainProductQuantity}?checkout&currency=${this.isocode}`;
61
+
62
+ window.location.href = cartUrl;
63
+ }
64
+
65
+ async onSubmitHandler() {
66
+ const data = {
67
+ items: [
68
+ {
69
+ id: this.mainProductId,
70
+ quantity: 1,
71
+ },
72
+ ...this.bindProducts,
73
+ ],
74
+ sections: 'cart-drawer,cart-icon-bubble',
75
+ };
76
+
77
+ this.toggleLoading();
78
+ await fetch(`${routes.cart_add_url}`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Content-Type': 'application/json',
82
+ Accept: 'application/javascript',
83
+ },
84
+ body: JSON.stringify(data),
85
+ })
86
+ .then((response) => response.json())
87
+ .then((response) => {
88
+ if (response.status) {
89
+ publish(PUB_SUB_EVENTS.cartError, {
90
+ source: 'product-form',
91
+ productVariantId: this.mainProductId,
92
+ errors: response.errors || response.description,
93
+ message: response.message,
94
+ });
95
+ } else if (!this.cart) {
96
+ window.location = window.routes.cart_url;
97
+ return;
98
+ }
99
+
100
+ if (!this.error)
101
+ publish(PUB_SUB_EVENTS.cartUpdate, {
102
+ source: 'product-form',
103
+ productVariantId: this.mainProductId,
104
+ cartData: response,
105
+ });
106
+ this.error = false;
107
+ const quickAddModal = this.closest('quick-add-modal');
108
+ if (quickAddModal) {
109
+ document.body.addEventListener(
110
+ 'modalClosed',
111
+ () => {
112
+ setTimeout(() => {
113
+ this.cart.renderContents(response);
114
+ });
115
+ },
116
+ { once: true }
117
+ );
118
+ quickAddModal.hide(true);
119
+ } else {
120
+ this.cart.renderContents(response);
121
+ }
122
+ })
123
+ .catch((e) => {
124
+ console.error(e);
125
+ })
126
+ .finally(() => {
127
+ if (this.cart && this.cart.classList.contains('is-empty'))
128
+ this.cart.classList.remove('is-empty');
129
+ this.toggleLoading();
130
+ });
131
+ }
132
+
133
+ getMainProduct() {
134
+ this.mainProductId = document.querySelector(
135
+ 'product-buy-now-selector'
136
+ ).currentVariantId;
137
+ this.mainProductQuantity = document.querySelector(
138
+ 'product-buy-now-counter input'
139
+ ).value;
140
+ }
141
+ getBindProduct() {
142
+ this.bindProducts = [];
143
+ const tags = Array.from(document.querySelectorAll('accessory-product'));
144
+ tags.forEach((item) => {
145
+ if (item.isSelected) {
146
+ this.bindProducts.push({
147
+ id: item.currentId,
148
+ quantity: item.getNumber(),
149
+ });
150
+ }
151
+ });
152
+ const careTags = Array.from(document.querySelectorAll('care-product'));
153
+ if (careTags && careTags.length) {
154
+ careTags.forEach((item) => {
155
+ if (item.isShow && item.isSelected) {
156
+ this.bindProducts.push({
157
+ id: item.currentId,
158
+ quantity: 1,
159
+ });
160
+ }
161
+ });
162
+ }
163
+
164
+ const careDialog = document.querySelector(
165
+ 'product-detail-dialog-hovercare-choose'
166
+ );
167
+ if (careDialog && careDialog.chooseId) {
168
+ this.bindProducts.push({
169
+ id: careDialog.chooseId,
170
+ quantity: 1,
171
+ });
172
+ }
173
+ }
174
+
175
+ toggleLoading() {
176
+ this.loading = !this.loading;
177
+ this.querySelector('.buy-loading').classList.toggle('tw-hidden');
178
+ }
179
+
180
+ toggleShowMoreDesc() {
181
+ const tipEl = this.querySelector('.bar-tip');
182
+ const svg = this.querySelector('.show-more-btn');
183
+
184
+ this.showMore = !this.showMore;
185
+
186
+ gsap.to(svg, {
187
+ rotation: this.showMore ? 180 : 0,
188
+ duration: 0.3,
189
+ });
190
+
191
+ tipEl.classList.toggle('show-more-desc', this.showMore);
192
+ }
193
+
194
+ // available: "true" or "false"
195
+ toggleAddToCartButton(available) {
196
+ const button = this.querySelector('button');
197
+ const btnText = this.querySelector('.buy-now-bottom-bar-btn-text');
198
+ if (!button) {
199
+ return;
200
+ }
201
+
202
+ if (button.disabled && available === 'true') {
203
+ button.disabled = false;
204
+ btnText.textContent = this.dataset.btnText;
205
+ } else if (!button.disabled && available === 'false') {
206
+ button.disabled = true;
207
+ btnText.textContent = this.dataset.btnOutOfStockText;
208
+ }
209
+ }
210
+
211
+ updatePrice() {
212
+ if (!window.Decimal) {
213
+ return;
214
+ }
215
+ console.log('this.isocode', this.isocode);
216
+ console.log('this.currency', this.currency);
217
+ const main = document
218
+ .querySelector('.product-version-option input:checked')
219
+ ?.closest('.product-version-option');
220
+ const mainProductQuantity = document.querySelector(
221
+ 'product-buy-now-counter input'
222
+ ).value;
223
+ const {
224
+ price: mainPrice,
225
+ before: mainBefore,
226
+ available: mainAvailable,
227
+ } = main.dataset;
228
+
229
+ let total_price = new Decimal(this.handlePrice(mainPrice));
230
+ let total_before = new Decimal(this.handlePrice(mainBefore));
231
+ total_price = total_price.times(mainProductQuantity);
232
+ total_before = total_before.times(mainProductQuantity);
233
+
234
+ console.log('total_price', total_price);
235
+ console.log('total_before', total_before);
236
+ this.toggleAddToCartButton(mainAvailable);
237
+
238
+ // 找到被选中的附加产品
239
+ const selectedAccessories = Array.from(
240
+ document.querySelectorAll('accessory-product')
241
+ ).filter((item) => item.isSelected);
242
+
243
+ for (const item of selectedAccessories) {
244
+ const count = parseInt(item.getNumber(), 10);
245
+ const priceEl = item.querySelector('.current-price');
246
+ if (!priceEl) {
247
+ console.warn('找不到 .current-price 元素');
248
+ continue;
249
+ }
250
+
251
+ const { price, before } = priceEl.dataset;
252
+
253
+ const currentPrice = new Decimal(this.handlePrice(price));
254
+ const originPrice = new Decimal(this.handlePrice(before));
255
+
256
+ total_price = total_price.plus(currentPrice.times(count));
257
+
258
+ const actualBefore = currentPrice.greaterThan(originPrice)
259
+ ? currentPrice.times(count)
260
+ : originPrice.times(count);
261
+ total_before = total_before.plus(actualBefore);
262
+ }
263
+
264
+ // 找到care 商品
265
+ const bindCares = Array.from(document.querySelectorAll('care-product'));
266
+ bindCares.forEach((item) => {
267
+ const count = parseInt(item.getNumber(), 10) || 0; // 确保 count 是有效数字
268
+
269
+ if (item.isShow && item.isSelected) {
270
+ const priceEl = item.querySelector('.current-price');
271
+ if (priceEl) {
272
+ const { price, before } = priceEl.dataset;
273
+
274
+ const currentPrice = new Decimal(this.handlePrice(price));
275
+ const originPrice = new Decimal(this.handlePrice(before));
276
+
277
+ total_price = total_price.plus(currentPrice.times(count));
278
+
279
+ const actualBefore = currentPrice.lessThan(originPrice)
280
+ ? originPrice.times(count)
281
+ : currentPrice.times(count);
282
+ total_before = total_before.plus(actualBefore);
283
+ } else {
284
+ console.warn('找不到 .current-price 元素');
285
+ }
286
+ }
287
+ });
288
+
289
+ // 计算加个
290
+ const priceText = this.currency + total_price.toString();
291
+ const beforeText = this.currency + total_before.toString();
292
+ const showBefore = total_before.greaterThan(total_price);
293
+ requestAnimationFrame(() => {
294
+ const priceEl = this.querySelector('.price');
295
+ if (priceEl) {
296
+ priceEl.textContent = Shopify.formatMoneyFromDecimal(total_price);
297
+ }
298
+
299
+ const beforeEl = this.querySelector('.before');
300
+ if (beforeEl) {
301
+ beforeEl.textContent = Shopify.formatMoneyFromDecimal(total_before);
302
+ }
303
+
304
+ const mbPriceEl = this.querySelector('.mb_price');
305
+ if (mbPriceEl) {
306
+ mbPriceEl.textContent = Shopify.formatMoneyFromDecimal(total_price);
307
+ }
308
+
309
+ const mbBeforeEl = this.querySelector('.mb_before');
310
+ if (mbBeforeEl) {
311
+ mbBeforeEl.textContent = Shopify.formatMoneyFromDecimal(total_before);
312
+ }
313
+
314
+ // 计算折扣
315
+ const discountEl = this.querySelectorAll('.price-discouter');
316
+ if (discountEl.length > 0) {
317
+ const percent = total_price.times(100).dividedBy(total_before);
318
+ const discountPercent = 100 - percent;
319
+ const discountValue = total_before.minus(total_price);
320
+
321
+ discountEl.forEach((el) => {
322
+ if (total_before.greaterThan(total_price)) {
323
+ el.classList.remove('tw-hidden');
324
+ el.textContent =
325
+ this.dataset.discountType === 'percent'
326
+ ? '- ' + Math.round(discountPercent) + '%'
327
+ : 'Save ' + this.currency + discountValue;
328
+ } else {
329
+ el.classList.add('tw-hidden');
330
+ }
331
+ });
332
+ }
333
+ });
334
+
335
+ // const toggleClass = (el, show) => {
336
+ // if (!el) return;
337
+ // el.classList.toggle('tw-hidden', !show);
338
+ // };
339
+
340
+ // toggleClass(beforeEl, showBefore);
341
+ // toggleClass(mbBeforeEl, showBefore);
342
+
343
+ this.updatePayPal(total_price.toString());
344
+ }
345
+ handlePrice(price) {
346
+ console.log('handlePrice', price);
347
+ if (!price) return 0;
348
+ if (this.currency === '€') {
349
+ return this.handleEuroPrice(price);
350
+ }
351
+ return price.toString().replace(/,/g, '') || 0;
352
+ }
353
+ handleEuroPrice(price) {
354
+ let result = price.toString();
355
+ const priceFormat = this.dataset.priceFormat || '';
356
+ if (priceFormat.includes('amount_with_comma_separator')) {
357
+ result = result.replace('.', '');
358
+ result = result.replace(',', '.');
359
+ } else {
360
+ result = result.replace(/,/g, '');
361
+ }
362
+ return result || 0;
363
+ }
364
+
365
+ updatePayPal(price) {
366
+ if (!price) return;
367
+
368
+ clearTimeout(this._paypalTimer);
369
+
370
+ const paypalEls = document.querySelectorAll('.product-buy-paypal');
371
+
372
+ if (!this.paypalLoading) {
373
+ this.paypalLoading = true;
374
+ paypalEls.forEach((el) => {
375
+ el.classList.add('tw-daisy-loading');
376
+ });
377
+ }
378
+
379
+ paypalEls.forEach((el) => {
380
+ el.dataset.ppAmount = price;
381
+ });
382
+
383
+ this._paypalTimer = setTimeout(() => {
384
+ paypalEls.forEach((el) => {
385
+ el.classList.remove('tw-daisy-loading');
386
+ });
387
+ this.paypalLoading = false;
388
+ }, 800);
389
+ }
390
+ // 有预期到货时间不展示
391
+ toggleShowTitleDesc(available) {
392
+ const barTitleDesc = this.querySelector('.bar-title-desc');
393
+ if (!available) {
394
+ barTitleDesc.classList.add('tw-hidden');
395
+ } else {
396
+ barTitleDesc.classList.remove('tw-hidden');
397
+ }
398
+ }
399
+ }
400
+
401
+ customElements.define('buy-now-bottom-bar', BuyNowBottomBar);
@@ -6,7 +6,9 @@
6
6
  border-radius: 32px;
7
7
  background: #F5F5F6;
8
8
  }
9
-
9
+ html, body {
10
+ overflow-x: hidden;
11
+ }
10
12
  .zz-radio-tabs-black.zz-radio-tabs {
11
13
  background: #FFFFFF14;
12
14
  }
@@ -240,6 +242,10 @@ body.zz-dialog-open {
240
242
  background: #378DDD;
241
243
  color: #fff;
242
244
  }
245
+ .zz-btn.zz-btn-primary.zz-btn-yellow {
246
+ background: linear-gradient(139.02deg, #FFEB00 0.01%, #FFD400 100.55%);
247
+ color: #000000;
248
+ }
243
249
  .zz-btn.zz-btn-primary-round.zz-btn-black {
244
250
  background: #000000;
245
251
  color: #fff;
@@ -265,6 +271,11 @@ body.zz-dialog-open {
265
271
  color: #FC6C0F;
266
272
  border: 1.5px solid #FC6C0F;
267
273
  }
274
+ .zz-btn.zz-btn-default.zz-btn-yellow {
275
+ background: transparent;
276
+ color: #000;
277
+ border: 1.5px solid #FFEB00;
278
+ }
268
279
  .zz-btn.zz-btn-default.zz-btn-blue {
269
280
  background: transparent;
270
281
  color: #378DDD;
@@ -0,0 +1,85 @@
1
+ document.addEventListener('DOMContentLoaded', (event) => {
2
+ if (!customElements.get('photo-swiper')) {
3
+ customElements.define(
4
+ 'photo-swiper',
5
+ class PhotoSwiper extends HTMLElement {
6
+ swiper = null; // swiper实例
7
+
8
+ constructor() {
9
+ super();
10
+ // 初始化swiper
11
+ // if (!this.swiper) {
12
+ // this.initSwiper();
13
+ // }
14
+ }
15
+
16
+ // connectedCallback() {
17
+ // this.initSwiper();
18
+ // }
19
+
20
+ disconnectedCallback() {
21
+ if (this.swiper) {
22
+ this.swiper.destroy();
23
+ this.swiper = null;
24
+ }
25
+ }
26
+
27
+ initSwiper() {
28
+ const swiperContainer = this.querySelector('.swiper');
29
+ if (!swiperContainer) return;
30
+
31
+ const slides = swiperContainer.querySelectorAll('.swiper-slide');
32
+ const imageCount = slides.length;
33
+
34
+ this.swiper = new Swiper(swiperContainer, {
35
+ slidesPerView: 1,
36
+ spaceBetween: 0,
37
+ pagination: {
38
+ el: this.querySelector('.swiper-pagination'),
39
+ type: 'bullets',
40
+ clickable: true,
41
+ dynamicBullets: false,
42
+ },
43
+ navigation: {
44
+ nextEl: '.swiper-button-next',
45
+ prevEl: '.swiper-button-prev',
46
+ },
47
+ effect: 'slide',
48
+ fadeEffect: {
49
+ crossFade: true,
50
+ },
51
+ loop: imageCount > 1,
52
+ observer: true,
53
+ observeParents: true,
54
+ speed: 500,
55
+ on: {
56
+ init: function () {
57
+ const paginationEl = this.pagination.el;
58
+ const prevButton = swiperContainer.querySelector(
59
+ '.swiper-button-prev'
60
+ );
61
+ const nextButton = swiperContainer.querySelector(
62
+ '.swiper-button-next'
63
+ );
64
+
65
+ if (imageCount <= 1) {
66
+ if (paginationEl) paginationEl.style.display = 'none';
67
+ if (prevButton) prevButton.style.display = 'none';
68
+ if (nextButton) nextButton.style.display = 'none';
69
+ }
70
+ },
71
+ },
72
+ });
73
+ }
74
+ // 销毁
75
+ destroySwiper() {
76
+ if (this.swiper) {
77
+ // 完全销毁swiper,包括所有事件监听器和DOM元素
78
+ this.swiper.destroy(true, true);
79
+ this.swiper = null;
80
+ }
81
+ }
82
+ }
83
+ );
84
+ }
85
+ });