zz-shopify-components 0.12.0 → 0.12.1-beta.1
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.
- package/assets/zz-buy-now-product-bar.js +361 -0
- package/assets/zz-components.css +51 -15
- package/assets/zz-components.js +52 -17
- package/assets/zz-photo-swiper.js +85 -0
- package/assets/zz-product-buy-now-select.js +279 -0
- package/assets/zz-version-dialog.js +114 -0
- package/assets/zz-world-video-list.js +9 -0
- package/blocks/snippets-test.liquid +19 -0
- package/blocks/zz-button-v2.liquid +243 -0
- package/blocks/zz-color-title-left.liquid +114 -0
- package/blocks/zz-content-description.liquid +23 -2
- package/blocks/zz-edu-form.liquid +4 -0
- package/blocks/zz-world-video.liquid +49 -3
- package/package.json +1 -1
- package/sections/zz-live-chat.liquid +161 -0
- package/sections/zz-product-buy-now.liquid +329 -0
- package/snippets/zz-button-v2.liquid +77 -0
- package/snippets/zz-buy-now-bottom-bar.liquid +163 -0
- package/snippets/zz-options-selector.liquid +107 -0
- package/snippets/zz-photo-swiper.liquid +134 -0
- package/snippets/zz-price-discouter.liquid +13 -0
- package/snippets/zz-product-buy-now-counter.liquid +38 -0
- package/snippets/zz-product-buy-now-select.liquid +45 -0
- package/snippets/zz-version-detail-dialog.liquid +112 -0
- package/snippets/zz-version-selector-simple.liquid +168 -0
- package/snippets/zz-version-selector.liquid +205 -0
|
@@ -0,0 +1,361 @@
|
|
|
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.isEduProduct = this.dataset.isEduProduct === 'true';
|
|
21
|
+
this.isLoggedIn = this.dataset.isLoggedIn === 'true';
|
|
22
|
+
this.isEduVerified = this.dataset.isEduVerified === 'true';
|
|
23
|
+
this.eduPageUrl = this.dataset.eduPageUrl;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
init() {
|
|
27
|
+
this.querySelector('button').addEventListener('click', async () => {
|
|
28
|
+
this.handleToPay();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (this.querySelector('.show-more-btn')) {
|
|
32
|
+
this.querySelector('.show-more-btn').addEventListener(
|
|
33
|
+
'click',
|
|
34
|
+
async () => {
|
|
35
|
+
this.toggleShowMoreDesc();
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
handleAddToCart() {
|
|
41
|
+
this.getMainProduct();
|
|
42
|
+
this.getBindProduct();
|
|
43
|
+
this.onSubmitHandler();
|
|
44
|
+
}
|
|
45
|
+
showCareChooseDialog() {
|
|
46
|
+
const careDialog = document.querySelector(
|
|
47
|
+
'product-detail-dialog-hovercare-choose'
|
|
48
|
+
);
|
|
49
|
+
if (careDialog) {
|
|
50
|
+
careDialog.showModal();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
handleToPay() {
|
|
55
|
+
this.getMainProduct();
|
|
56
|
+
const cartUrl = `/cart/${this.mainProductId}:${this.mainProductQuantity} `
|
|
57
|
+
window.location.href = cartUrl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async onSubmitHandler() {
|
|
61
|
+
|
|
62
|
+
const data = {
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
id: this.mainProductId,
|
|
66
|
+
quantity: 1,
|
|
67
|
+
},
|
|
68
|
+
...this.bindProducts,
|
|
69
|
+
],
|
|
70
|
+
sections: 'cart-drawer,cart-icon-bubble',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.toggleLoading();
|
|
74
|
+
await fetch(`${routes.cart_add_url}`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
Accept: 'application/javascript',
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify(data),
|
|
81
|
+
})
|
|
82
|
+
.then((response) => response.json())
|
|
83
|
+
.then((response) => {
|
|
84
|
+
if (response.status) {
|
|
85
|
+
publish(PUB_SUB_EVENTS.cartError, {
|
|
86
|
+
source: 'product-form',
|
|
87
|
+
productVariantId: this.mainProductId,
|
|
88
|
+
errors: response.errors || response.description,
|
|
89
|
+
message: response.message,
|
|
90
|
+
});
|
|
91
|
+
} else if (!this.cart) {
|
|
92
|
+
window.location = window.routes.cart_url;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!this.error)
|
|
97
|
+
publish(PUB_SUB_EVENTS.cartUpdate, {
|
|
98
|
+
source: 'product-form',
|
|
99
|
+
productVariantId: this.mainProductId,
|
|
100
|
+
cartData: response,
|
|
101
|
+
});
|
|
102
|
+
this.error = false;
|
|
103
|
+
const quickAddModal = this.closest('quick-add-modal');
|
|
104
|
+
if (quickAddModal) {
|
|
105
|
+
document.body.addEventListener(
|
|
106
|
+
'modalClosed',
|
|
107
|
+
() => {
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
this.cart.renderContents(response);
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
{ once: true }
|
|
113
|
+
);
|
|
114
|
+
quickAddModal.hide(true);
|
|
115
|
+
} else {
|
|
116
|
+
this.cart.renderContents(response);
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.catch((e) => {
|
|
120
|
+
console.error(e);
|
|
121
|
+
})
|
|
122
|
+
.finally(() => {
|
|
123
|
+
if (this.cart && this.cart.classList.contains('is-empty'))
|
|
124
|
+
this.cart.classList.remove('is-empty');
|
|
125
|
+
this.toggleLoading();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getMainProduct() {
|
|
130
|
+
this.mainProductId =
|
|
131
|
+
document.querySelector('product-buy-now-selector').currentVariantId;
|
|
132
|
+
this.mainProductQuantity =
|
|
133
|
+
document.querySelector('product-buy-now-counter input').value;
|
|
134
|
+
}
|
|
135
|
+
getBindProduct() {
|
|
136
|
+
this.bindProducts = [];
|
|
137
|
+
const tags = Array.from(document.querySelectorAll('accessory-product'));
|
|
138
|
+
tags.forEach((item) => {
|
|
139
|
+
if (item.isSelected) {
|
|
140
|
+
this.bindProducts.push({
|
|
141
|
+
id: item.currentId,
|
|
142
|
+
quantity: item.getNumber(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
const careTags = Array.from(document.querySelectorAll('care-product'));
|
|
147
|
+
if (careTags && careTags.length) {
|
|
148
|
+
careTags.forEach((item) => {
|
|
149
|
+
if (item.isShow && item.isSelected) {
|
|
150
|
+
this.bindProducts.push({
|
|
151
|
+
id: item.currentId,
|
|
152
|
+
quantity: 1,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const careDialog = document.querySelector(
|
|
159
|
+
'product-detail-dialog-hovercare-choose'
|
|
160
|
+
);
|
|
161
|
+
if (careDialog && careDialog.chooseId) {
|
|
162
|
+
this.bindProducts.push({
|
|
163
|
+
id: careDialog.chooseId,
|
|
164
|
+
quantity: 1,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
toggleLoading() {
|
|
170
|
+
this.loading = !this.loading;
|
|
171
|
+
this.querySelector('.buy-loading').classList.toggle('tw-hidden');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
toggleShowMoreDesc() {
|
|
175
|
+
const tipEl = this.querySelector('.bar-tip');
|
|
176
|
+
const svg = this.querySelector('.show-more-btn');
|
|
177
|
+
|
|
178
|
+
this.showMore = !this.showMore;
|
|
179
|
+
|
|
180
|
+
gsap.to(svg, {
|
|
181
|
+
rotation: this.showMore ? 180 : 0,
|
|
182
|
+
duration: 0.3,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
tipEl.classList.toggle('show-more-desc', this.showMore);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// available: "true" or "false"
|
|
189
|
+
toggleAddToCartButton(available) {
|
|
190
|
+
const button = this.querySelector('button');
|
|
191
|
+
const btnText = this.querySelector('.buy-now-bottom-bar-btn-text');
|
|
192
|
+
if (!button) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (button.disabled && available === 'true') {
|
|
197
|
+
button.disabled = false;
|
|
198
|
+
btnText.textContent = this.dataset.btnText;
|
|
199
|
+
} else if (!button.disabled && available === 'false') {
|
|
200
|
+
button.disabled = true;
|
|
201
|
+
btnText.textContent = this.dataset.btnOutOfStockText;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
updatePrice() {
|
|
206
|
+
if (!window.Decimal) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const main = document
|
|
210
|
+
.querySelector('.product-version-option input:checked')
|
|
211
|
+
?.closest('.product-version-option');
|
|
212
|
+
|
|
213
|
+
const {
|
|
214
|
+
price: mainPrice,
|
|
215
|
+
before: mainBefore,
|
|
216
|
+
available: mainAvailable,
|
|
217
|
+
} = main.dataset;
|
|
218
|
+
|
|
219
|
+
let total_price = new Decimal(this.handlePrice(mainPrice));
|
|
220
|
+
let total_before = new Decimal(this.handlePrice(mainBefore));
|
|
221
|
+
|
|
222
|
+
this.toggleAddToCartButton(mainAvailable);
|
|
223
|
+
|
|
224
|
+
// 找到被选中的附加产品
|
|
225
|
+
const selectedAccessories = Array.from(
|
|
226
|
+
document.querySelectorAll('accessory-product')
|
|
227
|
+
).filter((item) => item.isSelected);
|
|
228
|
+
|
|
229
|
+
for (const item of selectedAccessories) {
|
|
230
|
+
const count = parseInt(item.getNumber(), 10);
|
|
231
|
+
const priceEl = item.querySelector('.current-price');
|
|
232
|
+
if (!priceEl) {
|
|
233
|
+
console.warn('找不到 .current-price 元素');
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const { price, before } = priceEl.dataset;
|
|
238
|
+
|
|
239
|
+
const currentPrice = new Decimal(this.handlePrice(price));
|
|
240
|
+
const originPrice = new Decimal(this.handlePrice(before));
|
|
241
|
+
|
|
242
|
+
total_price = total_price.plus(currentPrice.times(count));
|
|
243
|
+
|
|
244
|
+
const actualBefore = currentPrice.greaterThan(originPrice)
|
|
245
|
+
? currentPrice.times(count)
|
|
246
|
+
: originPrice.times(count);
|
|
247
|
+
total_before = total_before.plus(actualBefore);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 找到care 商品
|
|
251
|
+
const bindCares = Array.from(document.querySelectorAll('care-product'));
|
|
252
|
+
bindCares.forEach((item) => {
|
|
253
|
+
const count = parseInt(item.getNumber(), 10) || 0; // 确保 count 是有效数字
|
|
254
|
+
|
|
255
|
+
if (item.isShow && item.isSelected) {
|
|
256
|
+
const priceEl = item.querySelector('.current-price');
|
|
257
|
+
if (priceEl) {
|
|
258
|
+
const { price, before } = priceEl.dataset;
|
|
259
|
+
|
|
260
|
+
const currentPrice = new Decimal(this.handlePrice(price));
|
|
261
|
+
const originPrice = new Decimal(this.handlePrice(before));
|
|
262
|
+
|
|
263
|
+
total_price = total_price.plus(currentPrice.times(count));
|
|
264
|
+
|
|
265
|
+
const actualBefore = currentPrice.lessThan(originPrice)
|
|
266
|
+
? originPrice.times(count)
|
|
267
|
+
: currentPrice.times(count);
|
|
268
|
+
total_before = total_before.plus(actualBefore);
|
|
269
|
+
} else {
|
|
270
|
+
console.warn('找不到 .current-price 元素');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// 计算加个
|
|
276
|
+
const priceText = this.currency + total_price.toString();
|
|
277
|
+
const beforeText = this.currency + total_before.toString();
|
|
278
|
+
const showBefore = total_before.greaterThan(total_price);
|
|
279
|
+
requestAnimationFrame(() => {
|
|
280
|
+
const priceEl = this.querySelector('.price');
|
|
281
|
+
const beforeEl = this.querySelector('.before');
|
|
282
|
+
const mbPriceEl = this.querySelector('.mb_price');
|
|
283
|
+
const mbBeforeEl = this.querySelector('.mb_before');
|
|
284
|
+
|
|
285
|
+
if (priceEl) priceEl.textContent = priceText;
|
|
286
|
+
if (beforeEl) beforeEl.textContent = showBefore ? beforeText : '';
|
|
287
|
+
if (mbPriceEl) mbPriceEl.textContent = priceText;
|
|
288
|
+
if (mbBeforeEl) mbBeforeEl.textContent = showBefore ? beforeText : '';
|
|
289
|
+
|
|
290
|
+
// 计算折扣
|
|
291
|
+
const discountEl = this.querySelectorAll('.price-discouter');
|
|
292
|
+
if (discountEl.length > 0) {
|
|
293
|
+
const percent = total_price.times(100).dividedBy(total_before);
|
|
294
|
+
const discountPercent = 100 - percent;
|
|
295
|
+
const discountValue = total_before.minus(total_price);
|
|
296
|
+
|
|
297
|
+
discountEl.forEach((el) => {
|
|
298
|
+
if (total_before.greaterThan(total_price)) {
|
|
299
|
+
el.classList.remove('tw-hidden');
|
|
300
|
+
el.textContent =
|
|
301
|
+
this.dataset.discountType === 'percent'
|
|
302
|
+
? '- ' + Math.round(discountPercent) + '%'
|
|
303
|
+
: 'Save ' + this.currency + discountValue;
|
|
304
|
+
} else {
|
|
305
|
+
el.classList.add('tw-hidden');
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// const toggleClass = (el, show) => {
|
|
312
|
+
// if (!el) return;
|
|
313
|
+
// el.classList.toggle('tw-hidden', !show);
|
|
314
|
+
// };
|
|
315
|
+
|
|
316
|
+
// toggleClass(beforeEl, showBefore);
|
|
317
|
+
// toggleClass(mbBeforeEl, showBefore);
|
|
318
|
+
|
|
319
|
+
this.updatePayPal(total_price.toString());
|
|
320
|
+
}
|
|
321
|
+
handlePrice(price) {
|
|
322
|
+
return price.toString().replace(/,/g, '') || 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
updatePayPal(price) {
|
|
326
|
+
if (!price) return;
|
|
327
|
+
|
|
328
|
+
clearTimeout(this._paypalTimer);
|
|
329
|
+
|
|
330
|
+
const paypalEls = document.querySelectorAll('.product-buy-paypal');
|
|
331
|
+
|
|
332
|
+
if (!this.paypalLoading) {
|
|
333
|
+
this.paypalLoading = true;
|
|
334
|
+
paypalEls.forEach((el) => {
|
|
335
|
+
el.classList.add('tw-daisy-loading');
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
paypalEls.forEach((el) => {
|
|
340
|
+
el.dataset.ppAmount = price;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
this._paypalTimer = setTimeout(() => {
|
|
344
|
+
paypalEls.forEach((el) => {
|
|
345
|
+
el.classList.remove('tw-daisy-loading');
|
|
346
|
+
});
|
|
347
|
+
this.paypalLoading = false;
|
|
348
|
+
}, 800);
|
|
349
|
+
}
|
|
350
|
+
// 有预期到货时间不展示
|
|
351
|
+
toggleShowTitleDesc(available) {
|
|
352
|
+
const barTitleDesc = this.querySelector('.bar-title-desc');
|
|
353
|
+
if (!available) {
|
|
354
|
+
barTitleDesc.classList.add('tw-hidden');
|
|
355
|
+
} else {
|
|
356
|
+
barTitleDesc.classList.remove('tw-hidden');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
customElements.define('buy-now-bottom-bar', BuyNowBottomBar);
|
package/assets/zz-components.css
CHANGED
|
@@ -236,6 +236,14 @@ body.zz-dialog-open {
|
|
|
236
236
|
background: #FC6C0F;
|
|
237
237
|
color: #fff;
|
|
238
238
|
}
|
|
239
|
+
.zz-btn.zz-btn-primary.zz-btn-blue {
|
|
240
|
+
background: #378DDD;
|
|
241
|
+
color: #fff;
|
|
242
|
+
}
|
|
243
|
+
.zz-btn.zz-btn-primary.zz-btn-yellow {
|
|
244
|
+
background: linear-gradient(139.02deg, #FFEB00 0.01%, #FFD400 100.55%);
|
|
245
|
+
color: #000000;
|
|
246
|
+
}
|
|
239
247
|
.zz-btn.zz-btn-primary-round.zz-btn-black {
|
|
240
248
|
background: #000000;
|
|
241
249
|
color: #fff;
|
|
@@ -261,7 +269,16 @@ body.zz-dialog-open {
|
|
|
261
269
|
color: #FC6C0F;
|
|
262
270
|
border: 1.5px solid #FC6C0F;
|
|
263
271
|
}
|
|
264
|
-
|
|
272
|
+
.zz-btn.zz-btn-default.zz-btn-yellow {
|
|
273
|
+
background: transparent;
|
|
274
|
+
color: #000;
|
|
275
|
+
border: 1.5px solid #FFEB00;
|
|
276
|
+
}
|
|
277
|
+
.zz-btn.zz-btn-default.zz-btn-blue {
|
|
278
|
+
background: transparent;
|
|
279
|
+
color: #378DDD;
|
|
280
|
+
border: 1.5px solid #378DDD;
|
|
281
|
+
}
|
|
265
282
|
.zz-btn.zz-btn-round.zz-btn-black {
|
|
266
283
|
background: transparent;
|
|
267
284
|
color: #000000;
|
|
@@ -281,6 +298,18 @@ body.zz-dialog-open {
|
|
|
281
298
|
border-radius: 30px;
|
|
282
299
|
}
|
|
283
300
|
|
|
301
|
+
.zz-btn-link.zz-btn-blue {
|
|
302
|
+
color: #378DDD;
|
|
303
|
+
}
|
|
304
|
+
.zz-btn-link.zz-btn-orange {
|
|
305
|
+
color: #FC6C0F;
|
|
306
|
+
}
|
|
307
|
+
.zz-btn-link.zz-btn-white {
|
|
308
|
+
color: #fff;
|
|
309
|
+
}
|
|
310
|
+
.zz-btn-link.zz-btn-black {
|
|
311
|
+
color: #000000;
|
|
312
|
+
}
|
|
284
313
|
|
|
285
314
|
.zz-btn:hover {
|
|
286
315
|
opacity: 0.8;
|
|
@@ -328,6 +357,9 @@ body.zz-dialog-open {
|
|
|
328
357
|
}
|
|
329
358
|
}
|
|
330
359
|
|
|
360
|
+
.zz-btn-shape-round {
|
|
361
|
+
border-radius: 90px;
|
|
362
|
+
}
|
|
331
363
|
.zz-video {
|
|
332
364
|
border-radius: inherit;
|
|
333
365
|
}
|
|
@@ -338,7 +370,7 @@ body.zz-dialog-open {
|
|
|
338
370
|
display: grid;
|
|
339
371
|
place-items: center;
|
|
340
372
|
pointer-events: none; /* 不阻挡点击 */
|
|
341
|
-
z-index:
|
|
373
|
+
z-index: 9999; /* 最高层 */
|
|
342
374
|
}
|
|
343
375
|
.zz-toast-box{
|
|
344
376
|
pointer-events: auto;
|
|
@@ -347,25 +379,29 @@ body.zz-dialog-open {
|
|
|
347
379
|
padding: 12px 16px;
|
|
348
380
|
border-radius: 12px;
|
|
349
381
|
background: #fff;
|
|
350
|
-
color: #
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
text-align: center;
|
|
382
|
+
color: #161110;
|
|
383
|
+
box-shadow: 0px 2px 16px 0px #00000026;
|
|
384
|
+
font-size: 14px;
|
|
385
|
+
text-align: left;
|
|
355
386
|
word-break: break-word;
|
|
356
387
|
transform: translateZ(0) scale(.96);
|
|
357
388
|
opacity: 0;
|
|
358
389
|
transition: transform .18s ease, opacity .18s ease;
|
|
390
|
+
line-height: 1.2;
|
|
391
|
+
display: inline-flex;
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
.zz-toast-icon {
|
|
395
|
+
margin-right: 4px;
|
|
396
|
+
padding: 0px 2px;
|
|
397
|
+
}
|
|
398
|
+
@media screen and (min-width: 1024px) {
|
|
399
|
+
.zz-toast-msg{
|
|
400
|
+
font-size: 16px;
|
|
401
|
+
}
|
|
402
|
+
|
|
359
403
|
}
|
|
360
404
|
.zz-toast-box.show{
|
|
361
405
|
opacity: 1;
|
|
362
406
|
transform: translateZ(0) scale(1);
|
|
363
407
|
}
|
|
364
|
-
.zz-toast-title{
|
|
365
|
-
display:inline-block;
|
|
366
|
-
margin-bottom: 2px;
|
|
367
|
-
font-weight: 600;
|
|
368
|
-
}
|
|
369
|
-
.zz-toast-msg{
|
|
370
|
-
display:block;
|
|
371
|
-
}
|
package/assets/zz-components.js
CHANGED
|
@@ -172,6 +172,47 @@ if (!customElements.get('zz-video-button')) {
|
|
|
172
172
|
let toastEl = null;
|
|
173
173
|
let hideTimer = null;
|
|
174
174
|
|
|
175
|
+
const toastIcon = {
|
|
176
|
+
error: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
177
|
+
<g clip-path="url(#clip0_19382_20972)">
|
|
178
|
+
<circle cx="9" cy="9" r="9" fill="#FF4D4F"/>
|
|
179
|
+
<path d="M6.24231 6.17188L11.8992 11.8287" stroke="white" stroke-linecap="round"/>
|
|
180
|
+
<path d="M6.24231 11.8281L11.8992 6.17127" stroke="white" stroke-linecap="round"/>
|
|
181
|
+
</g>
|
|
182
|
+
<defs>
|
|
183
|
+
<clipPath id="clip0_19382_20972">
|
|
184
|
+
<rect width="18" height="18" fill="white"/>
|
|
185
|
+
</clipPath>
|
|
186
|
+
</defs>
|
|
187
|
+
</svg>
|
|
188
|
+
`,
|
|
189
|
+
success: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
190
|
+
<g clip-path="url(#clip0_19382_20921)">
|
|
191
|
+
<circle cx="9" cy="9" r="9" fill="#5BC726"/>
|
|
192
|
+
<path d="M5 9L7.82843 11.8284L12.7782 6.87868" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
|
193
|
+
</g>
|
|
194
|
+
<defs>
|
|
195
|
+
<clipPath id="clip0_19382_20921">
|
|
196
|
+
<rect width="18" height="18" fill="white"/>
|
|
197
|
+
</clipPath>
|
|
198
|
+
</defs>
|
|
199
|
+
</svg>
|
|
200
|
+
`,
|
|
201
|
+
warning: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
202
|
+
<g clip-path="url(#clip0_19382_20989)">
|
|
203
|
+
<circle cx="9" cy="9" r="9" fill="#FAAD14"/>
|
|
204
|
+
<path d="M8.99994 5L8.99994 10" stroke="white" stroke-linecap="round"/>
|
|
205
|
+
<circle cx="9.00001" cy="12.8" r="0.8" fill="white"/>
|
|
206
|
+
</g>
|
|
207
|
+
<defs>
|
|
208
|
+
<clipPath id="clip0_19382_20989">
|
|
209
|
+
<rect width="18" height="18" fill="white"/>
|
|
210
|
+
</clipPath>
|
|
211
|
+
</defs>
|
|
212
|
+
</svg>
|
|
213
|
+
`,
|
|
214
|
+
};
|
|
215
|
+
|
|
175
216
|
function createToast() {
|
|
176
217
|
const layer = document.createElement('div');
|
|
177
218
|
layer.className = 'zz-toast-layer';
|
|
@@ -182,51 +223,46 @@ if (!customElements.get('zz-video-button')) {
|
|
|
182
223
|
box.className = 'zz-toast-box';
|
|
183
224
|
box.setAttribute('role', 'alert');
|
|
184
225
|
|
|
185
|
-
// const title = document.createElement('span');
|
|
186
|
-
// title.className = 'zz-toast-title';
|
|
187
|
-
// title.textContent = '错误';
|
|
188
|
-
|
|
189
226
|
const msg = document.createElement('span');
|
|
190
227
|
msg.className = 'zz-toast-msg';
|
|
191
228
|
|
|
192
|
-
|
|
229
|
+
const icon = document.createElement('span');
|
|
230
|
+
icon.className = 'zz-toast-icon';
|
|
231
|
+
|
|
232
|
+
box.appendChild(icon);
|
|
193
233
|
box.appendChild(msg);
|
|
194
234
|
layer.appendChild(box);
|
|
195
235
|
document.body.appendChild(layer);
|
|
196
236
|
|
|
197
|
-
return { layer, box, msg };
|
|
237
|
+
return { layer, box, msg, icon };
|
|
198
238
|
}
|
|
199
239
|
|
|
200
240
|
/**
|
|
201
241
|
* 显示错误 Toast
|
|
202
242
|
* @param {string} message - 要显示的错误文字
|
|
203
|
-
* @param {{duration?:number,
|
|
243
|
+
* @param {{duration?:number,type?:string}} [opts]
|
|
204
244
|
*/
|
|
205
245
|
function zzShowToast(message, opts = {}) {
|
|
206
|
-
|
|
207
246
|
if (!toastEl) {
|
|
208
247
|
toastEl = createToast();
|
|
209
248
|
}
|
|
210
249
|
|
|
211
|
-
// 可选标题(默认“错误”)
|
|
212
|
-
if(opts.title) {
|
|
213
|
-
toastEl.title.textContent = opts.title;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
250
|
// 更新文本
|
|
217
251
|
toastEl.msg.textContent = message ?? '';
|
|
252
|
+
if (opts.type) {
|
|
253
|
+
toastEl.icon.innerHTML = toastIcon[opts.type];
|
|
254
|
+
} else {
|
|
255
|
+
toastEl.icon.innerHTML = '';
|
|
256
|
+
}
|
|
218
257
|
|
|
219
258
|
// 重新插入到 body 末尾,保证在最上层
|
|
220
259
|
document.body.appendChild(toastEl.layer);
|
|
221
|
-
|
|
222
260
|
// 显示动画
|
|
223
261
|
requestAnimationFrame(() => {
|
|
224
262
|
toastEl.box.classList.add('show');
|
|
225
263
|
});
|
|
226
|
-
|
|
227
264
|
// 清理上一次的计时器
|
|
228
265
|
clearTimeout(hideTimer);
|
|
229
|
-
|
|
230
266
|
const duration = Math.max(500, Number(opts.duration || 2000));
|
|
231
267
|
hideTimer = setTimeout(() => {
|
|
232
268
|
toastEl.box.classList.remove('show');
|
|
@@ -241,4 +277,3 @@ if (!customElements.get('zz-video-button')) {
|
|
|
241
277
|
// 暴露到全局
|
|
242
278
|
window.zzShowToast = zzShowToast;
|
|
243
279
|
})();
|
|
244
|
-
|
|
@@ -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
|
+
});
|