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.