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.
@@ -0,0 +1,279 @@
1
+ // 处理产品变体逻辑
2
+ class ProductSelector extends HTMLElement {
3
+ currentVariantId;
4
+ options;
5
+ root;
6
+ loading = false;
7
+ constructor() {
8
+ super();
9
+ }
10
+ connectedCallback() {
11
+ this.currentVariantId = JSON.parse(this.dataset.currentVariantId);
12
+ if (!this.currentVariantId) {
13
+ throw new Error('选择器未传入变体');
14
+ }
15
+ this.options = this.dataset.options;
16
+ this.init();
17
+ console.log(
18
+ 'this.options, this.currentVariantId',
19
+ this.options,
20
+ this.currentVariantId
21
+ );
22
+ }
23
+ // 初始化 change 事件
24
+ init() {
25
+ this.bandOptionsSelector();
26
+ this.bandVersionSelector();
27
+ this.bindVersionEvents();
28
+ this.updatePrice();
29
+ }
30
+ bandOptionsSelector() {
31
+ const options = Array.from(
32
+ this.getElementsByClassName('product-normal-option')
33
+ );
34
+
35
+ options.forEach((el) => {
36
+ el.addEventListener('change', () => {
37
+ const { productUrl, variantId, sectionId } = el.dataset;
38
+ this.updateVersionSelector({ productUrl, variantId, sectionId });
39
+ this.updateURL(productUrl, variantId);
40
+ // 通知care更新
41
+ const careProducts = document.querySelector('care-product-list');
42
+ const careDialog = document.querySelector(
43
+ 'product-detail-dialog-hovercare-choose'
44
+ );
45
+ if (careDialog) {
46
+ careDialog.updateSelectProduct();
47
+ }
48
+ if (careProducts) {
49
+ careProducts.updateSelectProduct();
50
+ }
51
+ });
52
+ });
53
+ }
54
+ bandVersionSelector() {
55
+ const versionOptions = Array.from(
56
+ this.getElementsByClassName('product-version-option')
57
+ );
58
+ versionOptions.forEach((el) => {
59
+ el.addEventListener('change', () => {
60
+ console.log('version change');
61
+ const { productUrl, variantId, sectionId, price, before } = el.dataset;
62
+ // this.currentVariantId = sectionId;
63
+ this.updateURL(productUrl, variantId);
64
+ });
65
+ });
66
+ }
67
+ // Version Selector 更新函数
68
+ updateVersionSelector({ productUrl, variantId, sectionId }) {
69
+ if (!variantId) {
70
+ console.warn('无效的变体 ID:', variantId);
71
+ return;
72
+ }
73
+
74
+ this.fetchAndUpdateDOM(productUrl, variantId, sectionId)
75
+ .then(() => this.updateURL(productUrl, variantId))
76
+ .catch((error) => console.error('更新选择器时出错:', error));
77
+ }
78
+
79
+ // Section rendering api 获取变体信息
80
+ async fetchAndUpdateDOM(productUrl, variantId, sectionId) {
81
+ try {
82
+ this.toggleLoading();
83
+ const currentElement = document.getElementById(
84
+ 'product-version-selector'
85
+ );
86
+
87
+ const response = await fetch(
88
+ `${productUrl}?variant=${variantId}&section_id=${sectionId}`
89
+ );
90
+ if (!response.ok) {
91
+ throw new Error(`网络错误:${response.status}`);
92
+ }
93
+
94
+ // 更新DOM
95
+ const responseText = await response.text();
96
+ const html = new DOMParser().parseFromString(responseText, 'text/html');
97
+ const newElement = html.getElementById('product-version-selector');
98
+
99
+ if (newElement && currentElement) {
100
+ currentElement.parentNode.replaceChild(newElement, currentElement);
101
+ this.bandVersionSelector();
102
+ this.bindVersionEvents();
103
+ } else {
104
+ console.warn('无法找到替换的 DOM 元素');
105
+ }
106
+ } catch (error) {
107
+ console.error('fetchAndUpdateDOM 错误:', error);
108
+ } finally {
109
+ this.toggleLoading();
110
+ }
111
+ }
112
+
113
+ // 更新路由参数和产品信息
114
+ updateURL(productUrl, variantId) {
115
+ window.history.replaceState({}, '', `${productUrl}?variant=${variantId}`);
116
+ this.currentVariantId = variantId;
117
+ this.updatePrice();
118
+ }
119
+
120
+ // 清理事件监听器或其他资源
121
+ disconnectedCallback() {
122
+ // const options = this.querySelectorAll('.product-version-option');
123
+ // options.forEach((el) => {
124
+ // el.removeEventListener('click', this.handleOptionClick);
125
+ // });
126
+ // console.log('ProductSelector 已被移除');
127
+ }
128
+
129
+ // 更新价格
130
+ updatePrice() {
131
+ const bar = document.querySelector('buy-now-bottom-bar');
132
+ if (bar) {
133
+ bar.updatePrice();
134
+ }
135
+ }
136
+
137
+ // loading 状态
138
+ toggleLoading() {
139
+ this.loading = !this.loading;
140
+ // const params = {
141
+ // duration: 0.3,
142
+ // opacity: this.loading ? 0.2 : 1,
143
+ // pointerEvents: this.loading ? 'none' : 'auto',
144
+ // };
145
+ // gsap.to('#product-version-selector-container', params);
146
+ // gsap.set('product-buy-now-selector', {
147
+ // pointerEvents: this.loading ? 'none' : 'auto',
148
+ // });
149
+ }
150
+
151
+ bindVersionEvents() {
152
+ const versionOptions = Array.from(
153
+ this.querySelectorAll(".version-selector-item > button.show-detail")
154
+ );
155
+ versionOptions.forEach((el) => {
156
+ el.addEventListener("click", (event) => {
157
+ // 处理详情按钮点击
158
+ const detailButton = event.target.closest(".show-detail");
159
+ if (detailButton) {
160
+ event.stopPropagation();
161
+ event.preventDefault();
162
+
163
+ const dialog = detailButton
164
+ .closest(".version-selector-item")
165
+ ?.querySelector("dialog");
166
+ if (dialog) dialog.showModal();
167
+ return;
168
+ }
169
+ });
170
+ });
171
+ }
172
+ }
173
+
174
+ class ProductBuyNowCounter extends HTMLElement {
175
+ constructor() {
176
+ super();
177
+ this.input = null;
178
+ this.min = parseInt(this.getAttribute('min')) || 1;
179
+ this.max = parseInt(this.getAttribute('max')) || 99;
180
+ }
181
+
182
+ connectedCallback() {
183
+ this.input = this.querySelector('input');
184
+ if (this.input) {
185
+ this.init();
186
+ }
187
+ }
188
+
189
+ disconnectedCallback() {
190
+ const minusButton = this.querySelector('.minus');
191
+ const addButton = this.querySelector('.add');
192
+ if (minusButton) {
193
+ minusButton.removeEventListener('click', this.decrementValue);
194
+ }
195
+ if (addButton) {
196
+ addButton.removeEventListener('click', this.incrementValue);
197
+ }
198
+ if (this.input) {
199
+ this.input.removeEventListener('input', this.handleInput);
200
+ this.input.removeEventListener('blur', this.handleBlur);
201
+ }
202
+ }
203
+
204
+ init() {
205
+ const minusButton = this.querySelector('.minus');
206
+ const addButton = this.querySelector('.add');
207
+
208
+ if (minusButton) {
209
+ minusButton.addEventListener('click', () => this.decrementValue());
210
+ }
211
+
212
+ if (addButton) {
213
+ addButton.addEventListener('click', () => this.incrementValue());
214
+ }
215
+
216
+ this.input.addEventListener('input', (e) => this.handleInput(e));
217
+ this.input.addEventListener('blur', () => this.handleBlur());
218
+ }
219
+
220
+ handleInput() {
221
+ let value = parseInt(this.input.value);
222
+ if (isNaN(value)) {
223
+ this.input.value = '';
224
+ } else {
225
+ value = Math.min(Math.max(value, this.min), this.max);
226
+ this.input.value = value;
227
+ }
228
+ // this.toggleButtonState();
229
+ }
230
+
231
+ handleBlur() {
232
+ let value = parseInt(this.input.value);
233
+ if (isNaN(value) || value < this.min) {
234
+ this.input.value = this.min;
235
+ } else if (value > this.max) {
236
+ this.input.value = this.max;
237
+ }
238
+ this.updatePrice();
239
+ // this.toggleButtonState();
240
+ }
241
+
242
+ decrementValue() {
243
+ let value = parseInt(this.input.value) || this.min;
244
+ value = Math.max(value - 1, this.min);
245
+ this.input.value = value;
246
+ this.updatePrice();
247
+ // this.toggleButtonState();
248
+ }
249
+
250
+ incrementValue() {
251
+ let value = parseInt(this.input.value) || this.min;
252
+ value = Math.min(value + 1, this.max);
253
+ this.input.value = value;
254
+ this.updatePrice();
255
+ // this.toggleButtonState();
256
+ }
257
+
258
+ toggleButtonState() {
259
+ const minusButton = this.querySelector('.minus');
260
+ const addButton = this.querySelector('.add');
261
+
262
+ if (minusButton) {
263
+ minusButton.disabled = parseInt(this.input.value) <= this.min;
264
+ }
265
+ if (addButton) {
266
+ addButton.disabled = parseInt(this.input.value) >= this.max;
267
+ }
268
+ }
269
+
270
+ updatePrice() {
271
+ const bar = document.querySelector('buy-now-bottom-bar');
272
+ if (bar) {
273
+ bar.updatePrice();
274
+ }
275
+ }
276
+ }
277
+
278
+ customElements.define('product-buy-now-counter', ProductBuyNowCounter);
279
+ customElements.define('product-buy-now-selector', ProductSelector);
@@ -0,0 +1,114 @@
1
+ class VersionDialog extends HTMLElement {
2
+ isOpen = false;
3
+ swiper = null; // swiper实例
4
+
5
+ constructor() {
6
+ super();
7
+ this.init();
8
+ }
9
+
10
+ init() {
11
+ const dialog = this.querySelector('dialog');
12
+ if (dialog) {
13
+ const observer = new MutationObserver(() => {
14
+ if (dialog.open) {
15
+ // 初始化swiper
16
+ if (!this.swiper) {
17
+ this.initSwiper();
18
+ }
19
+ } else {
20
+ // 销毁swiper
21
+ if (this.swiper) {
22
+ this.destroySwiper();
23
+ }
24
+ }
25
+ });
26
+
27
+ // 监听dialog open
28
+ observer.observe(dialog, { attributes: true });
29
+
30
+ // 添加关闭事件监听
31
+ dialog.addEventListener('close', () => {
32
+ // 确保在关闭时销毁swiper
33
+ if (this.swiper) {
34
+ this.destroySwiper();
35
+ }
36
+ });
37
+
38
+ // 添加点击backdrop关闭的事件监听
39
+ dialog.addEventListener('click', (e) => {
40
+ const dialogDimensions = dialog.getBoundingClientRect();
41
+ if (
42
+ e.clientX < dialogDimensions.left ||
43
+ e.clientX > dialogDimensions.right ||
44
+ e.clientY < dialogDimensions.top ||
45
+ e.clientY > dialogDimensions.bottom
46
+ ) {
47
+ dialog.close();
48
+ }
49
+ });
50
+ }
51
+ }
52
+
53
+ // 初始化
54
+ initSwiper() {
55
+ const swiperContainer = this.querySelector('.swiper');
56
+ if (swiperContainer) {
57
+ const slides = swiperContainer.querySelectorAll('.swiper-slide');
58
+ const imageCount = slides.length;
59
+
60
+ this.swiper = new Swiper(swiperContainer, {
61
+ slidesPerView: 1,
62
+ spaceBetween: 0,
63
+ pagination: {
64
+ el: this.querySelector('.swiper-pagination'),
65
+ type: 'bullets',
66
+ clickable: true,
67
+ dynamicBullets: false,
68
+ },
69
+ navigation: {
70
+ nextEl: '.swiper-button-next',
71
+ prevEl: '.swiper-button-prev',
72
+ },
73
+ effect: 'slide',
74
+ fadeEffect: {
75
+ crossFade: true,
76
+ },
77
+ loop: imageCount > 1,
78
+ observer: true,
79
+ observeParents: true,
80
+ speed: 500,
81
+ on: {
82
+ init: function () {
83
+ const paginationEl = this.pagination.el;
84
+ const prevButton = swiperContainer.querySelector(
85
+ '.swiper-button-prev'
86
+ );
87
+ const nextButton = swiperContainer.querySelector(
88
+ '.swiper-button-next'
89
+ );
90
+
91
+ if (imageCount <= 1) {
92
+ if (paginationEl) paginationEl.style.display = 'none';
93
+ if (prevButton) prevButton.style.display = 'none';
94
+ if (nextButton) nextButton.style.display = 'none';
95
+ }
96
+ },
97
+ },
98
+ });
99
+ }
100
+ }
101
+
102
+ // 销毁
103
+ destroySwiper() {
104
+ if (this.swiper) {
105
+ // 完全销毁swiper,包括所有事件监听器和DOM元素
106
+ this.swiper.destroy(true, true);
107
+ this.swiper = null;
108
+ }
109
+ }
110
+ }
111
+
112
+ if (!customElements.get('version-dialog')) {
113
+ customElements.define('version-dialog', VersionDialog);
114
+ }
@@ -51,17 +51,26 @@ class WorldVideoList extends HTMLElement {
51
51
  const videoPageList = this.querySelector('.video-page-list');
52
52
  const videoPageLeftArrow = this.querySelector('.video-page-left-arrow');
53
53
  const videoPageRightArrow = this.querySelector('.video-page-right-arrow');
54
+ let swiperIndex = 0;
54
55
  // 点击 right 按钮 向左滑动
55
56
  videoPageRightArrow.addEventListener('click', () => {
56
57
  videoPageList.scrollBy({ left: 400, behavior: 'smooth' });
58
+ swiperIndex++;
57
59
  dataLayer.push({
58
60
  event: 'world_video_scroll',
59
61
  timestamp: new Date().toISOString(),
60
62
  });
63
+ if (swiperIndex > 0) {
64
+ videoPageLeftArrow.classList.remove('tw-hidden');
65
+ }
61
66
  });
62
67
  // 点击 left 按钮 向右滑动
63
68
  videoPageLeftArrow.addEventListener('click', () => {
64
69
  videoPageList.scrollBy({ left: -400, behavior: 'smooth' });
70
+ swiperIndex--;
71
+ if (swiperIndex === 0) {
72
+ videoPageLeftArrow.classList.add('tw-hidden');
73
+ }
65
74
  });
66
75
  }
67
76
  initSwitch() {
@@ -0,0 +1,19 @@
1
+ {% schema %}
2
+ {
3
+ "name": "Snippets Test",
4
+ "settings": [
5
+ ],
6
+ "presets": [
7
+ {
8
+ "name": "Snippets Test"
9
+ }
10
+ ]
11
+ }
12
+ {% endschema %}
13
+
14
+
15
+ <div class="tw-flex tw-flex-col tw-gap-4 tw-justify-center tw-items-center">
16
+
17
+ <button onclick="zzShowToast('test dfis0o dsjoi sjdoi jsod jsdo dsoi', {type: 'error'})">test</button>
18
+ <button onclick="zzShowToast('test dfis0o dsjoi sjdoi jsod jsdo dsoi josd Judson hjds dsjio dsjo dsj aj ewoi sdjo ds ojds sdjo jaso jo aiji')">test</button>
19
+ </div>
@@ -0,0 +1,243 @@
1
+ {% schema %}
2
+ {
3
+ "name": "Button v2",
4
+ "settings": [
5
+ {
6
+ "type": "text",
7
+ "id": "text",
8
+ "label": "按钮文字",
9
+ "default": "按钮"
10
+ },
11
+
12
+ {
13
+ "type": "select",
14
+ "id": "function_type",
15
+ "label": "功能类型",
16
+ "options": [
17
+ {
18
+ "value": "link",
19
+ "label": "链接"
20
+ },
21
+ {
22
+ "value": "link_map",
23
+ "label": "多国家映射"
24
+ },
25
+ ],
26
+ "default": "link"
27
+ },
28
+ {
29
+ "type": "url",
30
+ "id": "url",
31
+ "label": "按钮链接",
32
+ "visible_if": "{{ block.settings.function_type == 'link' }}"
33
+ },
34
+ {
35
+ "type": "textarea",
36
+ "id": "links",
37
+ "label": "Sites Link Map",
38
+ "info": "国家对应链接的表,国家即是国家选择器上显示的字段,国家和路由之间用冒号隔开( 冒号后要加空格)。国家之间换行,“default”为其他未写的默认的链接 EG: Canada: https://hoverair.com/ default: https://hoverair.com/ ",
39
+ "visible_if": "{{ block.settings.function_type == 'link_map' }}"
40
+ },
41
+ {
42
+ "type": "select",
43
+ "id": "type",
44
+ "label": "按钮类型",
45
+ "options": [
46
+ {
47
+ "value": "primary",
48
+ "label": "primary"
49
+ },
50
+ {
51
+ "value": "default",
52
+ "label": "default"
53
+ },
54
+ {
55
+ "value": "link",
56
+ "label": "link"
57
+ },
58
+ ],
59
+ "default": "primary"
60
+ },
61
+ {
62
+ "type": "select",
63
+ "id": "shape",
64
+ "label": "按钮形状",
65
+ "options": [
66
+ {
67
+ "value": "default",
68
+ "label": "default"
69
+ },
70
+ {
71
+ "value": "round",
72
+ "label": "round"
73
+ },
74
+ ],
75
+ "default": "default"
76
+ },
77
+ {
78
+ "type": "select",
79
+ "id": "color",
80
+ "label": "按钮颜色",
81
+ "options": [
82
+ {
83
+ "value": "black",
84
+ "label": "black"
85
+ },
86
+ {
87
+ "value": "white",
88
+ "label": "white"
89
+ },
90
+ {
91
+ "value": "orange",
92
+ "label": "orange"
93
+ },
94
+ {
95
+ "value": "blue",
96
+ "label": "blue"
97
+ },
98
+ {
99
+ "value": "yellow",
100
+ "label": "yellow"
101
+ }
102
+ ],
103
+ "default": "black",
104
+ },
105
+ {
106
+ "type": "select",
107
+ "id": "size",
108
+ "label": "按钮大小",
109
+ "options": [
110
+ {
111
+ "value": "large",
112
+ "label": "large"
113
+ },
114
+ {
115
+ "value": "medium",
116
+ "label": "medium"
117
+ },
118
+ {
119
+ "value": "small",
120
+ "label": "small"
121
+ }
122
+ ],
123
+ "default": "medium"
124
+ },
125
+ {
126
+ "type": "select",
127
+ "id": "mobile_width",
128
+ "label": "移动端按钮宽度",
129
+ "options": [
130
+ { "value": "full", "label": "全宽" },
131
+ { "value": "auto", "label": "自动" }
132
+ ],
133
+ "default": "auto"
134
+ },
135
+ {
136
+ "type": "select",
137
+ "id": "postfix_icon",
138
+ "label": "后缀图标",
139
+ "options": [
140
+ { "value": "none", "label": "不展示" },
141
+ { "value": "download", "label": "下载" },
142
+ { "value": "arrow-right", "label": "向右箭头" }
143
+ ],
144
+ "default": "none"
145
+ },
146
+ {
147
+ "type": "number",
148
+ "id": "icon_size",
149
+ "label": "图标大小(px)",
150
+ "default": 15
151
+ },
152
+ {
153
+ "type": "number",
154
+ "id": "icon_left_margin",
155
+ "label": "图标左边距(px)",
156
+ "default": 0
157
+ },
158
+ {
159
+ "type": "number",
160
+ "id": "icon_right_margin",
161
+ "label": "图标右边距(px)",
162
+ "default": 0
163
+ },
164
+ {
165
+ "type": "text",
166
+ "id": "btn_id",
167
+ "label": "按钮id属性值",
168
+ },
169
+ {
170
+ "type": "text",
171
+ "id": "modal_id",
172
+ "label": "按钮触发modal的id",
173
+ },
174
+ {
175
+ "type": "text",
176
+ "id": "click_event_class_name",
177
+ "label": "点击事件统计 classname",
178
+ },
179
+ ],
180
+ "presets": [
181
+ {
182
+ "name": "Button v2"
183
+ },
184
+ {
185
+ "name": "按钮 v2"
186
+ }
187
+ ]
188
+ }
189
+ {% endschema %}
190
+
191
+
192
+ {% assign btn_class = 'zz-button' | append: block.id %}
193
+
194
+ <style>
195
+
196
+ {% if block.settings.mobile_width == 'full' %}
197
+ @media (max-width: 1024px) {
198
+ #shopify-block-{{ block.id }} {
199
+ width: 100%;
200
+ box-sizing: border-box;
201
+ }
202
+ }
203
+ {% endif %}
204
+ </style>
205
+
206
+ {% render 'zz-button-v2',
207
+ href: block.settings.url,
208
+ type: block.settings.type,
209
+ color: block.settings.color,
210
+ text: block.settings.text,
211
+ size: block.settings.size,
212
+ postfix_icon: block.settings.postfix_icon,
213
+ icon_size: block.settings.icon_size,
214
+ icon_left_margin: block.settings.icon_left_margin,
215
+ icon_right_margin: block.settings.icon_right_margin,
216
+ width: block.settings.mobile_width,
217
+ shape: block.settings.shape,
218
+ class_name: btn_class,
219
+ btn_id: block.settings.btn_id,
220
+ modal_id: block.settings.modal_id
221
+ %}
222
+
223
+
224
+
225
+ <script>
226
+ document.addEventListener('DOMContentLoaded', (event) => {
227
+ const btn = document.getElementsByClassName('{{ btn_class }}')
228
+ if(btn && btn[0]) {
229
+ btn[0].addEventListener('click', (event) => {
230
+ dataLayer.push({
231
+ event: "{{ block.settings.click_event_class_name }}",
232
+ timestamp: new Date().toISOString(),
233
+ });
234
+ });
235
+ {% if block.settings.function_type == 'link_map' %}
236
+ if(window.bindSiteJump) {
237
+ bindSiteJump(btn[0], {{ block.settings.links | json }})
238
+ }
239
+ {% endif %}
240
+ }
241
+
242
+ })
243
+ </script>