PageSourceSearch

https://www.patekphilippe.to/dist/front/static/new_default_theme/variant-picker.js?v=641

js patekphilippe.to collected 2026-10-09 00:11:35 UTC 18,027 bytes, 371 lines download raw bytes

1if (!customElements.get("variant-picker")) {
2    class VariantPicker extends HTMLElement {
3        constructor() {
4            super();
5        }
6
7        get selectedOptionValues() {
8            const options = this.querySelectorAll('.f-product-option select, .f-product-option input');
9            const selectedValues = new Map();
10
11            options.forEach((field) => {
12                const fieldName = field.name || field.dataset.fieldName;
13                if (field.tagName.toLowerCase() === "select") {
14                    const selectedOption = Array.from(field.options).find(
15                        (opt) => opt.selected && !opt.disabled
16                    );
17                    if (selectedOption && selectedOption.value) {
18                        selectedValues.set(fieldName, selectedOption.value);
19                        // 更新label显示
20                        const label = this.querySelector(`.f-product-option--label label.option-label[for="${fieldName}"]`);
21                        if (label) {
22                            const selectedSpan = label.querySelector('.option-label--selected');
23                            if (selectedSpan) {
24                                selectedSpan.textContent = selectedOption.value;
25                            }
26                        }
27                    }
28                } else if (field.tagName.toLowerCase() === "input" && field.type === "radio") {
29                    // 原有按钮逻辑 - 保持不变
30                    const checkedInput = field.checked ? field : null;
31                    if (checkedInput) {
32                        selectedValues.set(fieldName, checkedInput.value);
33                        // 更新label显示
34                        const label = this.querySelector(`.f-product-option--label label.o
34ption-label[for="${fieldName}"]`);
35                        if (label) {
36                            const selectedSpan = label.querySelector('.option-label--selected');
37                            if (selectedSpan) {
38                                selectedSpan.textContent = checkedInput.value;
39                            }
40                        }
41                    }
42                }
43            });
44
45            // 补全未选中的字段(原有逻辑保持不变)
46            options.forEach((field) => {
47                const fieldName = field.name || field.dataset.fieldName;
48                if (!selectedValues.has(fieldName)) {
49                    if (field.tagName.toLowerCase() === "select") {
50                        const firstValidOption = Array.from(field.options).find(
51                            (opt) => !opt.disabled && opt.value
52                        );
53                        if (firstValidOption) {
54                            selectedValues.set(fieldName, firstValidOption.value);
55                        }
56                    } else if (field.tagName.toLowerCase() === "input" && field.type === "radio") {
57                        const firstInput = field;
58                        if (firstInput && !firstInput.disabled) {
59                            selectedValues.set(fieldName, firstInput.value);
60                        }
61                    }
62                }
63            });
64
65            const result = [];
66            selectedValues.forEach((value, key) => {
67                // 从name中提取position,格式如 "Color-1"
68                const positionMatch = key.match(/-(\d+)$/);
69                const position = positionMatch ? positionMatch[1] : null;
70                if (position) {
71                    result.push({
72                        name: key,
73                        value: value,
74                        position: 'option' + position
75                    });
76                }
77            });
78
79            if (result.length) {
80                this.setNewSelectedVariant(result, options);
81            }
82
83            return result;
84        }
85
86        // 设置选中的变体对象 - 保持原有逻辑不变
87        setNewSelectedVariant(selectedOptionValues, options) {
88            let allWapVariant = this.querySelector('variant-picker [data-all-variants]')?.innerHTML;
89            if (allWapVariant) {
90                allWapVariant = !!allWapVariant ? JSON.parse(allWapVariant) : null;
91            }
92            let chosenVariant = this.findSelectedVariant(selectedOptionValues, allWapVariant);
93
94            // 如果选中的变体无库存,且存在其他有库存的变体,则强制修正一次
95            if (chosenVariant && chosenVariant.qty <= 0 && allWapVariant && allWapVariant.some(v => v.qty > 0)) {
96                $log('setNewSelectedVariant: 选中的变体无库存,尝试修正为有库存变体');
97                // 构造一个新的 selectedValues 保持原有选项值不变(但需要让 findSelectedVariant 重新计算)
98                // 实际上 findSelectedVariant 已经优先返回有库存的,但可能因为没有完全匹配而返回了无库存,
99                // 这里我们直接调用一次重新匹配,强制要求有库存
100                const availableVariants = allWapVariant.filter(v => v.qty > 0);
101                if (availableVariants.length > 0) {
102                    // 如果当前有 option1 值,优先保留相同的 option1
103                    const currentOption1 = chosenVariant.option1;
104                    let correctedVariant = availableVariants.find(v => v.option1 === currentOption1);
105                    if (!correctedVariant) correctedVariant = availableVariants[0];
106                    if (correctedVariant) {
107                        chosenVariant = correctedVariant;
108                        // 同步更新 UI 选中状态
109                        this.updateOptionSelections(chosenVariant);
110                    }
111                }
112            }
113
114            let selectedVariantNode = this.querySelector('variant-picker [data-selected-variant]');
115            if (chosenVariant && selectedVariantNode) {
116                selectedVariantNode.innerHTML = JSON.stringify(chosenVariant);
117            }
118
119            FecEvents.emit(FecTheme.pubSubEvents.optionValueSelected, {
120                data: chosenVariant
121            });
122
123            // 注意:不再额外触发 optionValueSelectionChange,因为 UI 更新(updateOptionSelections)中会
124            // 自动派发 change 事件,从而触发 product-info 的更新。如果需要确保立即刷新,可以触发一次,
125            // 但需避免死循环。这里我们不额外触发,避免循环。
126        }
127
128        // 查找选中的变体 - 修复下拉菜单的匹配逻辑
129        // 查找选中的变体 - 优先匹配有库存的变体
130        findSelectedVariant(selectedValues, allVariants) {
131            $log('findSelectedVariant', selectedValues, allVariants);
132
133            if (!allVariants || !allVariants.length) return null;
134
135            // 将selectedValues转换为对象格式
136            const selectedMap = {};
137            selectedValues.forEach(item => {
138                selectedMap[item.position] = item.value;
139            });
140
141            const positions = ['option1', 'option2', 'option3'].filter(pos =>
142                selectedMap[pos] !== undefined && selectedMap[pos] !== ''
143            );
144            if (positions.length === 0) return null;
145
146            // 精确匹配所有选中的值
147            let matchedVariants = allVariants.filter(variant =>
148                positions.every(pos => variant[pos] === selectedMap[pos])
149            );
150
151            // 如果精确匹配没有结果,进行逐级模糊匹配(原有逻辑)
152            if (matchedVariants.length === 0) {
153                matchedVariants = [...allVariants];
154                for (let i = 0; i < positions.length; i++) {
155                    const pos = positions[i];
156                    const selectedValue = selectedMap[pos];
157                    // 获取该位置可用的值(基于当前匹配集)
158                    const availableValues = [...new Set(matchedVariants.map(v => v[pos]))];
159                    let matchValue = selectedValue;
160                    if (!availableValues.includes(selectedValue) && availableValues.length > 0) {
161                        matchValue = availableValues[0];
162                        this.updateUISelectedValue(pos, matchValue);
163                    }
164                    if (matchValue) {
165                        matchedVariants = matchedVariants.filter(v => v[pos] === matchValue);
166                    }
167                }
168            }
169
170            // 从匹配结果中优先选有库存的变体(qty > 0)
171            const availableVariants = matchedVariants.filter(v => v.qty > 0);
172            let finalVariant = null;
173            if (availableVariants.length > 0) {
174                // 尽量保持与当前已选 option1 相同(如果有)
175                if (selectedMap.option1) {
176                    finalVariant = availableVariants.find(v => v.option1 === selectedMap.option1);
177                }
178                if (!finalVariant) finalVariant = availableVariants[0];
179            } else {
180                // 没有任何有库存的变体,则选第一个匹配的(即使无库存)
181                finalVariant = matchedVariants[0] || null;
182            }
183
184            return finalVariant;
185        }
186        // 新增:更新UI中选中的值(仅用于下拉菜单的自动修正)
187        updateUISelectedValue(position, value) {
188            const positionNumber = position.replace('option', '');
189
190            // 更新select下拉菜单
191            const selectElement = this.querySelector(`select[data-position="${positionNumber}"]`);
192            if (selectElement) {
193                const option = Array.from(selectElement.options).find(opt => opt.value === value);
194                if (option && !option.selected) {
195                    option.selected = true;
196                    // 更新label显示
197                    const fieldName = selectElement.name;
198                    const label = this.querySelector(`.f-product-option--label label.o
198ption-label[for="${fieldName}"]`);
199                    if (label) {
200                        const selectedSpan = label.querySelector('.option-label--selected');
201                        if (selectedSpan) {
202                            selectedSpan.textContent = value;
203                        }
204                    }
205                }
206            }
207
208            // 更新radio按钮(按钮逻辑)
209            const radioInput = this.querySelector(`input[data-position="${positionNumber}"][value="${value}"]`);
210            if (radioInput && !radioInput.checked) {
211                radioInput.checked = true;
212            }
213        }
214        connectedCallback() {
215            this.addEventListener('change', (event) => {
216                const target = this.getInputForEventTarget(event.target);
217                $log('connectedCallback-change', event)
218
219                // 如果是select下拉菜单,确保trigger变更
220                if (event.target.tagName === 'SELECT') {
221                    const selectedOption = event.target.selectedOptions[0];
222                    if (selectedOption) {
223                        const fieldName = event.target.name;
224                        const label = this.querySelector(`.f-product-option--label label.o
224ption-label[for="${fieldName}"]`);
225                        if (label) {
226                            const selectedSpan = label.querySelector('.option-label--selected');
227                            if (selectedSpan) {
228                                selectedSpan.textContent = selectedOption.value;
229                            }
230                        }
231                    }
232                }
233
234                FecEvents.emit(FecTheme.pubSubEvents.optionValueSelectionChange, {
235                    data: {
236                        selectedOptionValues: this.selectedOptionValues,
237                        event,
238                        target
239                    }
240                });
241            });
242            this.setUrlVariantInfo()
243        }
244
245        setUrlVariantInfo() {
246            const urlParams = new URLSearchParams(window.location.search);
247            const variantValue = urlParams.get('variant');
248
249            if (variantValue) {
250                let allWapVariant = this.querySelector('variant-picker [data-all-variants]')?.innerHTML;
251                if (allWapVariant) {
252                    allWapVariant = JSON.parse(allWapVariant) || [];
253                }
254
255                const currentVariant = allWapVariant.find(variant => String(variant.id) === variantValue);
256
257                if (currentVariant) {
258                    const selectedVariantNode = this.querySelector('variant-picker [data-selected-variant]');
259                    if (selectedVariantNode) {
260                        selectedVariantNode.innerHTML = JSON.stringify(currentVariant);
261                    }
262                    this.updateOptionSelections(currentVariant);
263                    $log('URL匹配的currentVariant', currentVariant);
264                }
265            }
266        }
267
268        // 更新选项选择状态 - 保持原有逻辑不变,增加对select的支持
269        updateOptionSelections(variant) {
270            const changeEvents = [];
271
272            for (let i = 1; i <= 3; i++) {
273                const optionValue = variant[`option${i}`];
274                if (!optionValue) continue;
275
276                const optionName = this.getOptionNameByIndex(i);
277                if (!optionName) continue;
278
279                const escapedValue = CSS.escape(optionValue);
280
281                // 处理select下拉菜单
282                const selectElement = this.querySelector(`select[name="${optionName}"]`);
283                if (selectElement) {
284                    const option = Array.from(selectElement.options).find(opt => opt.value === optionValue);
285                    if (option && !option.selected) {
286                        Array.from(selectElement.options).forEach(opt => opt.selected = false);
287                        option.selected = true;
288                        changeEvents.push({
289                            element: selectElement,
290                            type: 'select'
291                        });
292
293                        // 更新label显示
294                        const label = this.querySelector(`.f-product-option--label label.o
294ption-label[for="${optionName}"]`);
295                        if (label) {
296                            const selectedSpan = label.querySelector('.option-label--selected');
297                            if (selectedSpan) {
298                                selectedSpan.textContent = optionValue;
299                            }
300                        }
301                    }
302                }
303
304                // 处理radio按钮(原有按钮逻辑)
305                const selector = `input[name="${optionName}"][value="${escapedValue}"], 
306                          select[name="${optionName}"] option[data-option-value="${escapedValue}"]`;
307                const optionElements = this.querySelectorAll(selector);
308
309                optionElements.forEach(element => {
310                    if (element.tagName === 'INPUT' && element.type === 'radio') {
311                        if (!element.checked) {
312                            element.checked = true;
313                            changeEvents.push({
314                                element: element,
315                                type: 'input'
316                            });
317
318                            // 更新label显示
319                            const label = this.querySelector(`.f-product-option--label label.o
319ption-label[for="${element.name}"]`);
320                            if (label) {
321                                const selectedSpan = label.querySelector('.option-label--selected');
322                                if (selectedSpan) {
323                                    selectedSpan.textContent = element.value;
324                                }
325                            }
326                        }
327                    }
328                });
329            }
330
331            if (changeEvents.length > 0) {
332                setTimeout(() => {
333                    changeEvents.forEach(event => {
334                        const changeEvent = new Event('change', { bubbles: true });
335                        event.element.dispatchEvent(changeEvent);
336
337                        const target = event.type === 'select' ?
338                            event.element.selectedOptions[0] : event.element;
339
340                        FecEvents.emit(FecTheme.pubSubEvents.optionValueSelectionChange, {
341                            data: {
342                                selectedOptionValues: this.selectedOptionValues,
343                                event: changeEvent,
344                                target: target
345                            }
346                        });
347                    });
348                }, 50);
349            }
350        }
351
352        getOptionNameByIndex(index) {
353            const optionElements = this.querySelectorAll('.f-product-option');
354            if (optionElements.length >= index) {
355                const optionElement = optionElements[index - 1];
356                const inputOrSelect = optionElement.querySelector('input, select');
357                return inputOrSelect ? inputOrSelect.getAttribute('name') || inputOrSelect.getAttribute('data-field-name') : null;
358            }
359            return null;
360        }
361
362        getInputForEventTarget(target) {
363            if (target.tagName === 'SELECT') {
364                return target.selectedOptions[0];
365            }
366            return target;
367        }
368    }
369
370    customElements.define("variant-picker", VariantPicker);
371}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.