1/* 2 åè½ï¼æ¹éè·åååä¼å价并渲æå°é¡µé¢ 3 ä¼åï¼é²æå并请æ±ãå»éãé¿å éå¤è¯·æ±ç¸åååIDå表 4* */ 5if (!customElements.get('f-customer-vip-price')) { 6 class FCustomerVipPrice extends HTMLElement { 7 constructor() { 8 super(); 9 this.apiUrl = '/api/apps/customervip/product/get-product-prices'; 10 this.productApiUrl = '/api/apps/customervip/product/get-product-variant-prices'; 11 // å¾ å¤ççååéå: productId => [domèç¹æ°ç»] 12 this.queue = new Map(); // productId => [nodes] 13 this.requestedIds = new Set(); // 已请æ±è¿ç productId 14 this.initialized = false; 15 this.scheduled = false; 16 this.lastRequestedKey = ''; // è®°å½ä¸ä¸æ¬¡è¯·æ±ç keyï¼productIds.join(',')ï¼ 17 this.isVipPriceShow = false 18 this.productInfoId = '' // åå详æ 页æè å¿«éè´ä¹°çåå ID 19 this.productInfoNode = null // åå详æ 页æå¿«éè´ä¹°çä»·æ ¼èç¹ 20 this.product_variant_prices = []//åå详æ 页åä½éæ©åçä»·æ ¼ 21 // æ£æ¥ç»å½ç¶æåä¼åç级 22 let loginStatus = localStorage.getItem('loginStatus') 23 if (loginStatus) loginStatus = JSON.parse(loginStatus) 24 let customervip_id = loginStatus?.customer?.customervip_id || 0 25 if ($getLoginInfo() && customervip_id) { 26 this.isVipPriceShow = true 27 } else { 28 this.isVipPriceShow = false 29 } 30 } 31 32 connectedCallback() { 33 if (!this.isVipPriceShow) { // 没ææä»¶æè 䏿¯ä¼å 34 return; 35 } 36 $log('ä¼åä»·æ ¼å¼å§åå§å') 37 if (this.initialized) return; 38 this.initialized = true; 39 40 this.collectNewNodes(); // åå§å 41 this.initObserver(); // ç嬿°å¢åå 42 if (window?.baseData?.page_type === 'product') { 43 // ä¸ä¸»äº§å表å忥åä½åå 主产å表åéä¸äºäº§åï¼é£ä¹è¿é忥è·çåå 44 this.syncWithMainProductForm()//çå¬åä½éæ© 45 } 46 } 47 48 // åå§åMutationObserverï¼çå¬DOMæ°å¢èç¹ 49 initObserver() { 50 const observer = new MutationObserver(mutations => { 51 for (const mutation of mutations) { 52 mutation.addedNodes.forEach(node => { 53 // nodeType èç¹ç±»å => 1 表示å ç´ èç¹ 3 è¡¨ç¤ºææ¬èç¹ 8 表示注éèç¹ 54 if (node.nodeType !== 1) return; 55 if (node.querySelectorAll) this.collectNewNodes(node); 56 }); 57 } 58 }); 59 observer.observe(document.body, {childList: true, subtree: true}); 60 } 61 62 /** 63 * æ¶éæ°åºç°çååèç¹ åå详æ 页 + ååå表 64 * @param {Document|Element} root - æç´¢æ ¹èç¹ 65 */ 66 collectNewNodes(root = document) { 67 $log('collectNewNodes'); 68 69 // 1. å¤çåå详æ 页æå¿«éè´ä¹°å¼¹çªçä»·æ ¼èç¹ï¼ä¼å å¤çï¼ 70 const productInfoNode = root.querySelector('product-info .product-block-group--price f-product-prices'); 71 72 if (productInfoNode) { 73 try { 74 const productInfo = root.querySelector('product-info'); 75 if (productInfo && productInfo.dataset.productId) { 76 this.productInfoId = productInfo.dataset.productId; 77 this.productInfoNode = productInfoNode; 78 79 $log('æ£æµå°åå详æ 页æå¿«éè´ä¹°å¼¹çª', { 80 productInfoId: this.productInfoId, 81 node: productInfoNode 82 }); 83 84 // 妿è¿ä¸ªååIDè¿æ²¡æè¢«è¯·æ±è¿ï¼ç«å³åç¬å¤ç 85 this.fetchSingleProductPrice(this.productInfoId, productInfoNode); 86 } 87 } catch (e) { 88 console.error('f-customer-vip-price: è·ååå详æ 页信æ¯å¤±è´¥', e); 89 } 90 } 91 92 // 2. å¤çååå表ä¸çä»·æ ¼èç¹ï¼ååå¡çå ï¼ 93 const pricesNodes = root.querySelectorAll('f-product-prices'); 94
95 pricesNodes.forEach(node => { 96 // è·³è¿å·²ç»å¤çè¿çåå详æ 页èç¹ 97 if (node === this.productInfoNode) return; 98 99 const card = node.closest('.f-product-card'); 100 if (!card) return; 101 102 // æ¥æ¾æè¿çç¶çº§ data-product-id å ç´ è·åååID 103 const productId = card.getAttribute('data-product-id'); 104 if (!productId || this.requestedIds.has(productId)) return; 105 106 // å°èç¹å å ¥éåï¼åä¸ä¸ªååIDå¯è½å¯¹åºå¤ä¸ªèç¹ 107 if (!this.queue.has(productId)) this.queue.set(productId, []); 108 if (!this.queue.get(productId).includes(node)) { 109 this.queue.get(productId).push(node); 110 $log(`æ·»å ååå¡çä»·æ ¼èç¹: ååID ${productId}`); 111 } 112 }); 113 114 // 妿æååå¡çèç¹ï¼å®æå¾®ä»»å¡æ¹éå¤ç 115 if (this.queue.size > 0) { 116 // $log('å¾ å¤çååå¡çéå:', Array.from(this.queue.keys())); 117 this.scheduleFetch(); 118 } 119 } 120 121 /** 122 * æå¨å·æ°ä¼åä»·æ ¼ 123 * å¨åååè¡¨æ´æ°åæå¨è°ç¨æ¤æ¹æ³ 124 * @param {HTMLElement} container - å¯éï¼æå®å·æ°ç容å¨èå´ï¼ä¸ä¼ åå·æ°æ´ä¸ªé¡µé¢ 125 */ 126 refresh(container = document) { 127 $log('æå¨å·æ°ä¼åä»·æ ¼', container); 128 129 if (!this.isVipPriceShow) { 130 $log('ä¼åä»·æ ¼åè½æªå¯ç¨'); 131 return; 132 } 133 134 // æ¸ ç©ºå·²è¯·æ±çååIDè®°å½ï¼å è®¸éæ°è¯·æ± 135 this.requestedIds.clear(); 136 137 // æ¸ ç©ºéå 138 this.queue.clear(); 139 140 // æ¸ ç©ºä¸æ¬¡è¯·æ±è®°å½ 141 this.lastRequestedKey = ''; 142 143 // éæ°æ¶éèç¹ 144 this.collectNewNodes(container); 145 146 // 妿æ¯åå详æ 页ï¼éæ°è·ååä½ä»·æ ¼ 147 if (window?.baseData?.page_type === 'product' && this.productInfoId) { 148 this.fetchSingleProductPrice(this.productInfoId, this.productInfoNode); 149 } 150 } 151 152 153 /** 154 * åç¬è·åå个ååçä»·æ ¼å¹¶æ¸²æï¼ç¨äºåå详æ 页æå¿«éè´ä¹°å¼¹çªï¼ 155 * @param {string} productId - ååID 156 * @param {Element} node - ä»·æ ¼èç¹ 157 */ 158 async fetchSingleProductPrice(productId, node) { 159 if (!productId || !node) return; 160 161 $log('åç¬å¤çåå详æ 页/å¿«éè´ä¹°ä»·æ ¼', {productId, node}); 162 163 try { 164 // æ 记为已请æ±ï¼é¿å éå¤è¯·æ± 165 this.requestedIds.add(productId); 166 167 const data = await api.post(this.productApiUrl, { 168 product_id: productId 169 }); 170 171 let product_prices = data?.variant_prices || {}; 172 $log('åå详æ 页åä½ä»·æ ¼', product_prices) 173 if (Object.keys(product_prices).length > 0) { 174 this.product_variant_prices = product_prices; 175 this.renderProductVipPrice(this.getSelectedVariant()); 176 } else { 177 $log('åå详æ 页/å¿«éè´ä¹°æ ä¼åä»·æ°æ®', productId); 178 } 179 } catch (err) { 180 console.error('f-customer-vip-price: è·ååå详æ 页ä¼å价失败', err); 181 // å¦æå¤±è´¥ï¼ä»requestedIdsä¸ç§»é¤ï¼å 许éè¯ 182 this.requestedIds.delete(productId); 183 } 184 } 185 186 syncWithMainProductForm() { 187 // 订é åä½ååäºä»¶ 188 FecEvents.on(FecTheme.pubSubEvents.optionValueSelected, (payload) => { 189 $log('ä¼åä»·æ ¼-订é äºä»¶-å½åéä¸çåä½:', payload.data); 190 this.renderProductVipPrice(payload.data) 191 }); 192 193 } 194 195 renderProductVipPrice(selectedVariant) { 196 let variantPrice = this.product_variant_prices[selectedVariant?.id] 197 $log('renderProductVipPrice', selectedVariant, variantPrice); 198 199 if (variantPrice && variantPrice.price) { 200 $log('å¼å§æ´æ°',this.product_variant_prices,variantPrice,selectedVariant) 201 if (this.productInfoNode) { 202 this.renderVipPrice(this.productInfoNode, variantPrice); 203 204 } 205 // æ¬æµ®è´ç©æ 206 let stickyAtc = document.querySelector('sticky-atc f-product-prices') 207 if (stickyAtc) { 208 this.renderVipPrice(stickyAtc, variantPrice); 209 210 } 211 } 212 213 } 214 215 getSelectedVariant() {//è·ååå详æ 页å½åéä¸åä½ 216 let productId = this.productInfoId; 217 let selectedVariant = document.querySelector(`variant-picker [data-selected-variant="${productId}"]`)?.innerHTML 218 if (selectedVariant) { 219 return JSON.parse(selectedVariant); 220 } else { 221 return null; 222 } 223 } 224 225 scheduleFetch() { 226 if (this.scheduled) return; 227 this.scheduled = true; 228 // å¨å½ååæ¥ä»£ç æ§è¡å®æ¯åï¼å¾®ä»»å¡ä¸æ§è¡å®é è¯·æ± 229 Promise.resolve().then(() => { 230 this.scheduled = false; 231 this.fetchAndRender(); 232 }); 233 } 234 235 /**
236 * æ¹éè·åä¼å价并渲æå°å¯¹åºèç¹ï¼ç¨äºåååè¡¨ï¼ 237 */ 238 async fetchAndRender() { 239 const productIds = Array.from(this.queue.keys()); 240 if (!productIds.length) return; 241 242 // 夿æ¯å¦éå¤è¯·æ±ï¼å¦ææ¬æ¬¡å¾ 请æ±çååIDç»åä¸ä¸æ¬¡å®å ¨ç¸åï¼åè·³è¿ 243 const currentKey = productIds.sort().join(','); 244 if (currentKey === this.lastRequestedKey) { 245 $log('â ï¸ f-customer-vip-price: è·³è¿éå¤è¯·æ±', currentKey); 246 return; 247 } 248 this.lastRequestedKey = currentKey; 249 250 try { 251 $log('æ¹éè·åååå表ä¼åä»·', productIds); 252 253 const data = await api.post(this.apiUrl, { 254 product_ids: productIds 255 }); 256 257 let product_prices = data.product_prices; 258 259 productIds.forEach(productId => { 260 const nodes = this.queue.get(productId) || []; 261 262 if (product_prices[productId] && 263 product_prices[productId].price && 264 product_prices[productId].price > 0) { 265 266 nodes.forEach(node => { 267 this.renderVipPrice(node, product_prices[productId]); 268 }); 269 $log(`ååå表ä¼å价渲ææå: ååID ${productId}`); 270 } else { 271 $log(`ååå表æ ä¼åä»·æ°æ®: ååID ${productId}`); 272 } 273 274 this.requestedIds.add(productId); 275 this.queue.delete(productId); 276 }); 277 } catch (err) { 278 console.error('f-customer-vip-price: æ¹éè·åä¼å价失败', err); 279 } 280 } 281 282 /** 283 * 渲æä¼åä»·æ ¼å ç´ 284 * @param {Element} node - ä»·æ ¼èç¹(f-product-prices) 285 * @param {Object} priceData - ä»·æ ¼æ°æ® 286 */ 287 renderVipPrice(node, priceData) { 288 $log('渲æä¼åä»·æ ¼å ç´ ', node, priceData); 289 if (!node || !node.parentNode) return; 290
291 // æ£æ¥ node æ¯å¦æå 容ï¼éç©ºç½ææ¬ï¼å 为æä»·æ ¼å±è½ï¼æä»¥å¯è½å 鍿²¡ä»·æ ¼ï¼vipä»·æ ¼ä¹ä¸éè¦æ¾ç¤ºäº 292 const hasContent = node.textContent && node.textContent.trim().length > 0; 293 if (!hasContent) { 294 $log('node æ å 容ï¼è·³è¿æ¸²æä¼åä»·'); 295 return; 296 } 297 // æ£æ¥æ¯å¦å·²ç»æå ¥è¿ä¼åä»·å ç´ 298 const nextElement = node.nextElementSibling; 299 if (nextElement && nextElement.classList && 300 nextElement.classList.contains('customer-vip-price-box')) { 301 // 妿已åå¨ï¼æ´æ°ä»·æ ¼ 302 const priceTextNode = Array.from(nextElement.childNodes).find( 303 child => child.nodeType === 3 && child.textContent.trim() 304 ); 305 if (priceTextNode) { 306 priceTextNode.textContent = $formatMoney(priceData?.price); 307 $log('æ´æ°å·²åå¨çä¼åä»·', priceData?.price); 308 } else { 309 // å¤ç¨æ¹æ¡ï¼ç´æ¥æ´æ°innerHTML 310 const vipSpan = nextElement.querySelector('.customer-vip-price'); 311 if (vipSpan) { 312 nextElement.innerHTML = `${$formatMoney(priceData?.price)}<span class="customer-vip-price">VIP</span>`; 313 } 314 $log('éè¿innerHTMLæ´æ°ä¼åä»·', priceData?.price); 315 } 316 return; 317 } 318 319 // å建æ°å ç´ 320 let productClass = '' 321 if (!node.closest('.f-product-card')) { 322 productClass = 'm:text-xl md:m:text-2xl'; 323 } 324 const newElement = document.createElement('div'); 325 newElement.className = `customer-vip-price-box f-price__regular ${productClass}`; 326 newElement.innerHTML = `${$formatMoney(priceData?.price)} 327 <span class="customer-vip-price">VIP</span> 328 `; 329 330 // å¨ node åé¢æå ¥æ°å ç´ 331 if (node.nextSibling) { 332 node.parentNode.insertBefore(newElement, node.nextSibling); 333 } else { 334 node.parentNode.appendChild(newElement); 335 } 336 337 $log('ä¼åä»·æ ¼æ¸²æå®æ', { 338 price: priceData?.price, 339 nodeType: node.closest('.f-product-card') ? 'ååå¡ç' : 'åå详æ /å¿«éè´ä¹°' 340 }); 341 } 342 } 343 344 customElements.define('f-customer-vip-price', FCustomerVipPrice); 345} 346
347// å°å®ä¾æè½½å°å ¨å±ï¼æ¹ä¾¿æå¨è°ç¨ 348if (!window.FCustomerVipPrice) { 349 window.FCustomerVipPrice = { 350 instance: null, 351 352 /** 353 * è·åä¼åä»·æ ¼ç»ä»¶å®ä¾ 354 */ 355 getInstance: function() { 356 if (!this.instance) { 357 const element = document.querySelector('f-customer-vip-price'); 358 if (element) { 359 this.instance = element; 360 } 361 } 362 return this.instance; 363 }, 364 365 /** 366 * æå¨å·æ°ææä¼åä»·æ ¼ 367 * @param {HTMLElement} container - å¯éï¼æå®å·æ°ç容å¨èå´ 368 */ 369 refresh: function(container) { 370 const instance = this.getInstance(); 371 if (instance && instance.refresh) { 372 instance.refresh(container); 373 } else { 374 $log('æ¾ä¸å° f-customer-vip-price å®ä¾'); 375 } 376 } 377 }; 378}
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.