(function(){ let w = window.innerWidth; function setHeaderCssVar() { const headerEle = document.getElementById('shoplaza-section-header'); if(!headerEle){ return }; document.body.style.setProperty('--window-height', `${window.innerHeight}px`); document.body.style.setProperty('--header-height', `${headerEle.clientHeight}px`); const mdScorllHideEle = headerEle.querySelector('.header__mobile .header__scroll_hide'); if (mdScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-md', `${mdScorllHideEle.clientHeight}px`); } const pcScorllHideEle = headerEle.querySelector('.header__desktop .header__scroll_hide'); if (pcScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-pc', `${pcScorllHideEle.clientHeight}px`); } } function handlResize() { if(w == window.innerWidth){return}; w = window.innerWidth; setHeaderCssVar(); }; function init(){ setHeaderCssVar(); window.removeEventListener('resize', window._theme_header_listener) window._theme_header_listener = handlResize; window.addEventListener('resize', window._theme_header_listener); } init(); })();

BALENCIAGA@theOpticalShop

BALENCIAGA BB0096S
CA$503.20
CA$629.00
Save CA$125.80
(function(){ const TAG = 'spz-custom-lamb-add-btn'; class SpzCustomLambAddBtn extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.action_ = null; this.selected_variant = '{"id":"2ebafafa-5e23-4d5b-a861-af59ef32bbf0","product_id":"c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5","title":"","weight_unit":"kg","inventory_quantity":1,"sku":"","barcode":"51398W","position":1,"option1":"","option2":"","option3":"","note":"","image":null,"wholesale_price":[{"price":503.2,"min_quantity":1}],"weight":"0","compare_at_price":"629","price":"503.2","retail_price":"629","available":true,"url":"\/products\/balenciaga-theopticalshop-4mre?variant=2ebafafa-5e23-4d5b-a861-af59ef32bbf0","available_quantity":1,"options":[],"off_ratio":"20","flashsale_info":{"variant_id":"2ebafafa-5e23-4d5b-a861-af59ef32bbf0","product_id":"","quantity":0,"discount_id":"457413b7-9358-408d-80a1-62af1c5978ef","limit_time":253369,"limit_buy":-1,"user_limit_buy":-1,"discount_sales":0,"discount_sales_rate":"0","discount_stock":1,"ends_at":1715669999,"starts_at":1714582706,"allow_oversold":"uncheck","allocation_method":"none","price":"503.2","compare_at_price":"629","discount_price":"503.2","customary_saved_price":"125.8","customary_off_ratio":"20","discount_saved_price":"125.8","discount_off_ratio":"20","use_before_price":false,"before_price":"","title":"","properties":"","color_setting_promotional_copy":"","discount_quantity":0,"is_need_split":false},"sales":0}'; this.lens_process_id = ""; } buildCallback() { this.xhr_ = SPZServices.xhrFor(this.win); this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.getLambLensSteps_(true); this.setupAction_(); } mountCallback() { document.addEventListener('dj.variantChange', (event) => { const variant = event.detail.selected; if (variant.product_id == 'c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5') { this.selected_variant = JSON.stringify(variant); } }); } fetchLambLensSteps() { const lens_process_id = location.search.replace('?', '').split('&').find(v => v.includes('lens_process_id')); const template_id = location.search.replace('?', '').split('&').find(v => v.includes('template_id')); const stepsUrl = '/api/fireant/product/c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5/steps' + ((lens_process_id ? `?${lens_process_id}` : '') || (template_id ? `?${template_id}` : '')); return fetch(stepsUrl).then((res)=>{ return res.json(); }).then((data)=>{ return data; }) } showProductBtn() { var addToCart = document.getElementById('add_to_cart_normal'); var productQuantityBtn = document.getElementById('product_info_quantity_normal'); addToCart && addToCart.classList.remove('hidden'); productQuantityBtn && productQuantityBtn.classList.remove('hidden'); } getLambLensSteps_(isInit) { try{ this.fetchLambLensSteps().then((data)=>{ if(data.errors) { if(isInit){ this.renderBtn(false); this.showProductBtn(); } }else{ if(isInit){ this.lens_process_id = data?.lens_process_id; let frameOnlyArr = data?.prescription_types?.options.filter((option)=>{ return option.prescription_type === 'Frame Only' }); let frameOnlyObj = frameOnlyArr.length > 0 ? frameOnlyArr[0] : {}; let available = true; if(available && (frameOnlyObj?.frame_only_display_at == 'product_detail' || frameOnlyObj?.frame_only_display_at == 'all')){ this.renderBtn({ ...frameOnlyObj, process_type: data.process_type }); }else{ this.renderBtn({ process_type: data.process_type }); } } } }); }catch(e){ this.renderBtn(false); this.showProductBtn(); } } renderBtn = (isRenderProcessBtn) => { return this.templates_ .findAndRenderTemplate(this.element, isRenderProcessBtn) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } handleClickFrameOnlyBtn_ = async () => { try { const reqBody = { product_id: "c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5", variant_id: JSON.parse(this.selected_variant)?.id, quantity: 1, properties: { lens_processing_id: this.lens_process_id, prescription_type: "Frame Only" } } const data = await this.xhr_.fetchJson('/api/fireant/customize_cart', { method: "post", body: reqBody }); if(data.state === 'success') { this.atc_loading_ = false; this.element.removeAttribute('atc-loading'); this.triggerEvent_("dj.addToCart", data); window.location.href = "/cart"; } else { this.atc_loading_ = false; this.element.removeAttribute('atc-loading'); } } catch (e) { this.atc_loading_ = false; this.element.removeAttribute('atc-loading'); const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { e.then((result)=>{ api.showToast(result?.errors[0] || 'Unknown error'); }) }); } } trackAddToCart = () => { const params = { business_type: "product_plugin", function_name: "prescription_lens", plugin_name: "prescription_lens", module: "apps", tab_name: "process_btn", event_type: "click", event_developer: "jozy", event_info: JSON.stringify({ action_type: "frame_only_add_to_cart", product_id: '"c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5"', process_id: this.lens_process_id, process_type: "glasses", element_type: "button", element_name: "frame_only_btn" }) } window.spzutm && window.spzutm.registerParams('add_to_cart', params ); } setupAction_ = () => { this.registerAction('handleClickBtn', (invocation) => { const glassesInfo = { product:{ title: "BALENCIAGA@theOpticalShop", id: "c69dcd93-86b9-46f7-8e5e-1e0a1648d6f5", image: {"src":"\/\/img.staticdj.com\/e85331d31d6a3bfebce2674cc6d0273e.JPG","path":"e85331d31d6a3bfebce2674cc6d0273e.JPG","width":2448,"height":1836,"alt":"","aspect_ratio":1.3333333333333333}, selected_variant: JSON.parse(this.selected_variant) }, atcType: 'add_lens' }; const tempElement = document.getElementById('process-request-script'); tempElement && SPZ.whenApiDefined(tempElement).then(async (api) => { await api.requestLensProcess(glassesInfo, invocation.args.process_type); }); setTimeout(() => { const proceeBtn = document.getElementById('lens_add_to_cart'); proceeBtn && proceeBtn.classList.remove('pointer-events-none'); }, 3000); }); this.registerAction('handleClickFrameOnlyBtn', (invocation) => { this.trackAddToCart(); this.atc_loading_ = true; this.element.setAttribute('atc-loading',""); this.handleClickFrameOnlyBtn_(); }); } triggerEvent_(name, params) { const event = SPZUtils.Event.create( this.win, name, params, {bubbles: true} ); this.element.dispatchEvent(event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomLambAddBtn) })()
Prescription Verification

Your eyewear prescription consists of specific instructions that enable you to purchase eyeglasses or contact lenses tailored to address your visual requirements. This document will indicate the lens power, size, and recommended brand. In cases where you have specific needs, like astigmatism correction or multifocal lenses, additional information may be included in your prescription.

You will receive this prescription after receiving an eye examination conducted by a certified eye care professional. During the exam, they will assess your visual needs and provide guidance on when to replace your eyewear based on your lifestyle and eye sensitivity.

Disclaimer:

You hereby confirm that you possess a valid prescription for the lenses you intend to order. This means that your prescription is current, having been issued within the past 12 months, and has been provided by a qualified optometrist or lens fitter (referred to hereafter as a "practitioner").

We reserve the right to verify this information with your practitioner. This same requirement applies to returning customers ordering lenses. If a registered practitioner has advised against wearing lenses, we reserve the right to decline any sale, unless redirected by the same or another registered practitioner.

Furthermore, you affirm that you do not have any medical conditions that contraindicate the use of lenses. When making a purchase from us, you are confirming that a qualified practitioner has prescribed the lenses you are ordering. Consequently, we are not responsible if you purchase lenses that have not been specifically prescribed for you or if the time between the prescription and purchase exceeds 12 months.

We strongly recommend regular eye examinations by a qualified practitioner to safeguard your eye health. While it is not mandatory to send us your prescription, by accepting the terms and conditions outlined above, you are verifying that you possess a valid prescription in accordance with Canadian law. Please note that contact lens wearers must be at least 16 years old.

If you desire assistance with your prescription, we can help by receiving a scanned copy sent to theopticalshoponwhyte@gmail.com.

Need any help? Please contact us

If you need any additional assistance regarding your prescription, please visit our store located at 10524 82nd Ave NW, Edmonton, AB T6E 2A4, or call us at 1 (780) 437-1110. Our highly trained staff will be more than happy to help! 

Details
Brand: 
BALENCIAGA
Frame Shape: 
RECTANGLE SHORT
Frame Color: 
WHITE
Frame Size: 
51
Description

Model: BALENCIAGA BB0096S

Size: 51 18 130

Color: 011 / WHITE

Balenciaga's sunglasses and eyeglasses collection embodies disruptive couture and fashion realism. Crafted from the finest materials from Italy. They are truly one great indulgence.

(function(){ const TAG = 'spz-custom-lamb-locale-script'; class SpzCustomLambLocaleScript extends SPZ.BaseElement { constructor(element) { super(element); this.isInit = false; this.lambLocale = null; } buildCallback() { this.getlambLocale_(); } getLambLocale() { if (this.lambLocale) { this.setIsInit_(true); return this.lambLocale; } if (this.isInit) { return this.lambLocale; } this.setIsInit_(true); return this.getlambLocale_(); } async getLambLocaleValue({nameSpace, localeIndex, keyValue}) { let locale_value = await this.getLambLocale(); if(nameSpace == "contact"){ locale_value = locale_value.contact_lens_map.get(localeIndex); }else{ locale_value = locale_value.lens_map.get(localeIndex); } if(keyValue) { const arr = keyValue.split(';') arr.forEach(item =>{ const itemArr = item.split(':') const key = itemArr[0].trim(); const value = itemArr[1]; if(locale_value){ let reg = new RegExp(key,"g") locale_value = locale_value.replace(reg,value); } }) } return locale_value; } getlambLocale_() { this.lambLocale = fetch('/api/fireant/locales/en-US').then((res)=>{ return res.json(); }).then((data)=>{ const map = new Map(); const contactMap = new Map(); data.list.map((item)=>{ map.set(item.locale_index,item.locale_value); }); data.contact_lens_list.map((item)=>{ contactMap.set(item.locale_index,item.locale_value); }); return { "lens_map": map, "contact_lens_map": contactMap }; }) } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } setIsInit_(params = true) { this.isInit = params; } } SPZ.defineElement(TAG, SpzCustomLambLocaleScript) })() const TAG = 'spz-custom-lamb-price'; class SpzCustomPriceHandle extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = SPZServices.templatesForDoc(); } static deferredMount() { return false; } buildCallback = () => { const render = () => { const { zero_price_option: zeroPriceOption, zero_price_text: zeroPriceText } = window.lens_process.settings; const [priceDom, comparePriceDom] = this.element.children; if (!priceDom) return; const currencyDom = this.getCurrencyDom(priceDom); const price = currencyDom ? Number(currencyDom.getAttribute('value')) : 0; if (zeroPriceOption === 0 && price <= 0) { if (this.isCurrencyNode(priceDom)) { priceDom.style.display = 'none'; } else { priceDom.innerHTML = ""; } } else if (zeroPriceOption === 2 && price <= 0) { const newDom = document.createElement("div"); newDom.innerHTML = zeroPriceText; if (this.isCurrencyNode(priceDom)) { priceDom.style.display = 'none'; Array.from(priceDom.classList).forEach(className => { if (!className.includes('spzhtml')) newDom.classList.add(className); }); priceDom.parentNode.insertBefore(newDom, priceDom); } else { priceDom.innerHTML = ""; priceDom.appendChild(newDom); } } if (comparePriceDom) { const compareCurrencyDom = this.getCurrencyDom(comparePriceDom); const comparePrice = compareCurrencyDom ? Number(compareCurrencyDom.getAttribute('value')) : 0; if (price <= 0 && comparePrice > price && zeroPriceOption === 0) { comparePriceDom.style.display = 'none'; } else { } } }; this.mutateElement(render); } getCurrencyDom = (dom) => { return this.isCurrencyNode(dom) ? dom : dom.querySelector('ljs-currency'); } isCurrencyNode = (node) => { return node.nodeName === 'LJS-CURRENCY'; } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomPriceHandle) const TAG = 'spz-custom-lamb-render'; class SPZCustomLambRender extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } mountCallback = () => {} render = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { this.element.children[0].style.display = 'none'; const utilsEl = document.getElementById('spz_custom_lens_util'); utilsEl && SPZ.whenApiDefined(utilsEl).then((api) => { api.debounceRender(el, this); }); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomLambRender) const TAG = 'spz-custom-lamb-locale'; class SpzCustomLambLocale extends SPZ.BaseElement { constructor(element) { super(element); this.localeIndex = ""; this.nameSpace = ""; this.templates_ = SPZServices.templatesForDoc(); } static deferredMount() { return false; } buildCallback = () => { this.localeIndex = this.element.getAttribute('localeIndex'); this.nameSpace = this.element.getAttribute('nameSpace'); this.keyValue = this.element.getAttribute('keyValue'); } mountCallback() { const render = async () => { const tempElement = document.getElementById('spz-custom-lamb-locale-script'); SPZ.whenApiDefined(tempElement).then(async (api) => { let lacale_value = await api.getLambLocaleValue({nameSpace: this.nameSpace, keyValue: this.keyValue, localeIndex: this.localeIndex }); var spanDom = document.createElement("div"); spanDom.innerHTML = lacale_value; this.element.innerHTML = ""; this.element.appendChild(spanDom); }); }; this.mutateElement(render); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomLambLocale) const TAG = 'spz-custom-lens-process'; class LensProcess extends SPZ.BaseElement { constructor(element) { super(element); this.product_id = '' this.process_data_ = {} this.prescriptionSettings_ = {} this.globalSettings_ = null this.stepsIndex_ = { currentIndex: 0, fromIndex: 0, preIndex: 0, } this.isHasNewStep_ = false; this.currentStepDomId_ = null; this.currentStepId_ = null; this.currentStepType_ = null; this.stepsData_ = [ { type: PRESCRIPTION, // 处方类型 form: {}, id: "prescription-type-step", index: 0, }, { type: SUBMIT_METHOD, // 提交方式 form: {}, id: "submit-method-step", index: 1, }, { type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, // 处方填写 or 老花处方 form: {}, id: "prescription-submit-step", index: 2, }, { type: LENS_TYPE, // 镜片类型 id: "lens-type-step", form: {}, index: 3, }, ] this.initProductInfo_ = {} this.productInfo_ = { product: {}, stepsPriceData: [], } this.quantity_ = 1 this.copyProperties_ = {} this.copyLensProcessData_ = {} this.atcPrescriptionConvertParams_ = {}; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); this.resizeHeight(); } static deferredMount() { return false; } clearAllStatus_ = async () => { let trackReportData = { ...IncidentReportingData, event_desc: "sidebar_close", event_type: "popup_click", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", action_type: "sidebar_close", step_type: this.currentStepType_, step_id: this.currentStepId_, process_id: this.process_data_.lens_process_id, process_type: "glasses", element_type: "button", element_name: "close" }) } window.sa.track("function_click", trackReportData); this.stepsIndex_ = { currentIndex: 0, fromIndex: 0, preIndex: 0, } this.isHasNewStep_ = false this.currentStepDomId_ = null; this.currentStepId_ = null; this.currentStepType_ = null; this.stepsData_ = [ { type: PRESCRIPTION, // 处方类型 form: {}, id: "prescription-type-step", index: 0, }, { type: SUBMIT_METHOD, // 提交方式 form: {}, id: "submit-method-step", index: 1, }, { type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, // 处方填写 or 老花处方 form: {}, id: "prescription-submit-step", index: 2, }, { type: LENS_TYPE, // 镜片类型 id: "lens-type-step", form: {}, index: 3, }, ] this.productInfo_.stepsPriceData = [] await this.renderStep1_(this.prescription_types_); await this.renderStep2_(this.submit_methods_); await this.renderPrice_() } mountCallback = async() => { this.triggerEvent_('mount'); this.lensUtilsApi_ = await SPZ.whenApiDefined(document.querySelector('#spz_custom_lens_util')); } getLensProcessData(id, params='') { return this.xhr_.fetchJson(`/api/fireant/product/${ id }/steps${params}`, { method: "get", }); } saveEditData_ = ({properties, lensProcessData}) => { if (JSON.stringify(this.copyProperties_) === '{}') { this.copyProperties_ = JSON.parse(JSON.stringify(properties || {})) } if (JSON.stringify(this.copyLensProcessData_) === '{}') { this.copyLensProcessData_ = JSON.parse(JSON.stringify(lensProcessData || {})); } } generateMailtoLink = (email) => { return `<\a href="mailto:${email}" class="no-lens-notice-email">${email}<\/a>`; } trackNoMatchLensExpose_ = async(selector) => { let trackReportData = { ...IncidentReportingData, event_type: "popup_expose", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", action_type: "no_next_step", step_id: this.currentStepId_, process_id: this.process_data_.lens_process_id, process_type: "glasses" }) } const lensUtilsApi = await SPZ.whenApiDefined(document.querySelector('#spz_custom_lens_util')); lensUtilsApi.incidentReportingFn(selector,trackReportData,"function_expose") } transEmailLink = (str,serviceEmail) => { let emailStr = str; const emailMatchArr = emailStr.match(/[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+/g); const uniqArr = Array.from(new Set(emailMatchArr)); let emailMap = new Map(); if(serviceEmail) { emailMap.set('{email}',serviceEmail); } if (uniqArr.length) { uniqArr.sort((a, b) => { if (a.length === b.length) { return 0; } return a.length > b.length ? -1 : 1; }); uniqArr.forEach((email,index) => { let emailKey = '{email'+index+ '}'; let reg = new RegExp(email,"g") emailStr = emailStr.replace(reg,emailKey); emailMap.set(emailKey,email); }) } emailMap.forEach((value,key) => { let email = value; let emailKey = key; let emailLink = this.generateMailtoLink(email); let reg = new RegExp(emailKey,"g") emailStr = emailStr.replace(reg,emailLink); }) return emailStr; } handleNoLensNoticeWithEmail_ = async() => { try { const data = await this.xhr_.fetchJson('/api/fireant/store/store_info', { method: "get", }); const tempElement = document.getElementById('spz-custom-lamb-locale-script'); const localeApi = await SPZ.whenApiDefined(tempElement); var lamb_locale = await localeApi.getLambLocale(); lamb_locale = lamb_locale.lens_map; this.no_lens_notice = lamb_locale.get("filter.no_lens_notice"); this.no_lens_notice = this.transEmailLink(this.no_lens_notice,data?.service_email); }catch(err) { console.log('err'); } } getNolensNotice = async() => { if(!this.no_lens_notice) { await this.handleNoLensNoticeWithEmail_(); } return this.no_lens_notice; } init_ = async (data, params="") => { console.log('The lens process is initializing.......') let { atcType, product } = data this.atcType_ = atcType this.initProductInfo_ = { ...this.initProductInfo_, ...product, }; this.handleNoLensNoticeWithEmail_(); if (atcType === 'add_lens') { this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); this.product_id = product.id; this.variant_id = product.selected_variant?.id; const process_data_ = await this.getLensProcessData(this.product_id, params); this.process_data_ = process_data_; this.lensUtilsApi_.openSidebar('lens-process-sidebar'); const { prescription_types, lens, submit_methods, steps } = this.process_data_; // 判断有没有新步骤,没有新步骤的话到第三步就加购 this.isHasNewStep_ = !!steps.length this.prescription_types_ = this.lensUtilsApi_.resolvePrescriptionTypes_(prescription_types); this.submit_methods_ = submit_methods this.lens_ = lens this.steps_ = steps this.stepsConditions_ = this.resolveCondition_(steps) this.globalSettings_ = await this.lensUtilsApi_.getGlobalSettings_(this.product_id, 'glasses_setting'); window.lens_process.settings = this.globalSettings_; this.lens_.price_display_rule = this.globalSettings_.price_display_rule; this.prescriptionSettings_ = await this.getPrescriptionSelects_(this.globalSettings_) this.lensUtilsApi_.setGlobalPrimaryColor(this.globalSettings_, 'glasses'); await this.renderStep1_(prescription_types); await this.renderStep2_(submit_methods); this.showIndex_({ index: 0 }); this.renderPrice_(); let trackReportData = { ...IncidentReportingData, event_type: "popup_expose", event_desc: "sidebar_open", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", product_id: product.id, selected_variant_id: product.selected_variant.id, process_id: this.process_data_?.lens_process_id, process_type: "glasses", last_action_type: "click_lenses_and_purchases_btn", last_element_type: "button", last_element_name: "select_lenses_and_purchase" }) } this.lensUtilsApi_.incidentReportingFn("#lens-process-sidebar",trackReportData,"function_expose") } else { const { properties, product_id, variant_id, item_id } = product this.lensUtilsApi_.setupVariantOptions_.call(this, product); this.quantity_ = product.quantity product.framePrice = product.trunk_price if (product_id === this.product_id && this.line_item_id === item_id) { this.productInfo_ = { product: {}, stepsPriceData: [] } // 还是编辑的之前的商品 } else { // 更换了商品的编辑 this.productInfo_ = { product: {}, stepsPriceData: [] } } this.line_item_id = item_id this.product_id = product_id this.variant_id = variant_id this.lensUtilsApi_.openSidebar('lens-process-sidebar'); const data = this.lensUtilsApi_.resolveProperties_(JSON.parse((`${properties}`).replace(/&(quot);/ig,'"'))) data.steps = data.steps.map(item => { let newItem = {}; if(item.hasOwnProperty('color_property_id') && item.hasOwnProperty('text_property_id')) { newItem = {...item, first_level_property_id: item.text_property_id, second_level_property_id: item.color_property_id} delete newItem.color_property_id; delete newItem.text_property_id; } else { newItem = {...item} } return newItem; }); try { const process_data_ = await this.getLensProcessData(this.product_id) this.process_data_ = process_data_; await this.saveEditData_({properties: data, lensProcessData: process_data_}) this.renderEditComponent_(product, data, process_data_) } catch(err) { setTimeout(() => { this.triggerEvent_('closeSidebar'); }, 500) err.then(data => { const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { api.showToast(data.message || (data.errors && data.errors[0]) || 'Unknown error'); }); }) } } } resolveCondition_ = (steps) => { return steps.reduce((total, cur) => total.concat(cur.step_condition.rules.map(item => { return { ...item, to_id: cur.id } })), []) } calcPrescriptionDataFromEdit_ = (prescription_type, submit_method, main_prescription_id, sub_prescription_id) => { const setStepData_ = (prescription_type, prescriptionOptionData, reading_type = null, main_prescription_id, sub_prescription_id) => { this.stepsData_[0].form = { prescription_type, reading_type, main_prescription_id, sub_prescription_id}; let subPrescriptionOptionData = prescriptionOptionData?.properties?.find(item => item.id === sub_prescription_id); if(subPrescriptionOptionData) { prescriptionOptionData = { ...prescriptionOptionData, text_property: { price: subPrescriptionOptionData.price, compare_at_price: subPrescriptionOptionData.compare_at_price } } }else { delete prescriptionOptionData.text_property; } this.productInfo_.stepsPriceData[0] = { "type": PRESCRIPTION, "title": prescription_type, "index": 0, ...prescriptionOptionData } } let reading_type = null; if (prescription_type === PRESCRIPTION_TYPE.READING) { reading_type = submit_method ? READING_TYPE.READING_LENS : READING_TYPE.READING_NO_LENS; } const prescriptionOptionData = this.prescription_types_.options.find(item => item.id === main_prescription_id); let isNotMatchSubPrescription = false; if(sub_prescription_id) { let subPrescriptionOptionData = prescriptionOptionData?.properties?.find(item => item.id === sub_prescription_id); isNotMatchSubPrescription = !subPrescriptionOptionData; } if (!prescriptionOptionData || (reading_type && !this.judgeIsIncludeReadingType_(reading_type, main_prescription_id)) || !!isNotMatchSubPrescription) { throw new Error("您之前选择的处方类型现已不可用,我们对此造成的不便深感抱歉。请您重新选择一种提交方式。"); } setStepData_(prescription_type, prescriptionOptionData, reading_type, main_prescription_id, sub_prescription_id); } calcSubmitMethodDataFromEdit_ = (submit_method, prescription_submit_form_data) => { if (!submit_method) return; let submitMethodOptionData = this.submit_methods_.options.find(item => item[SUBMIT_METHOD] === submit_method); if (!submitMethodOptionData) { throw new Error("您之前选择的提交方式现已不可用,我们对此造成的不便深感抱歉。请您重新选择一种提交方式。"); } this.stepsData_[1].form = { submit_method }; if (submit_method) { this.productInfo_.stepsPriceData[1] = { "type": SUBMIT_METHOD, "title": submit_method, "index": 1, ...submitMethodOptionData, price: prescription_submit_form_data.is_prism ? submitMethodOptionData.price : 0 }; } this.prescription_image = prescription_submit_form_data.prescription_image || ''; return submitMethodOptionData; } getFilteredPrescriptionData_ = (prescription_submit_form_data) => { return this.filterNonNullProperties_(prescription_submit_form_data, Object.values(propertyEnum).reduce((total, cur) => total.concat(cur), [])); } getInvalidPrescriptionKeys_ = (filterPrescriptionData) => { return Object.keys(filterPrescriptionData).filter(key => { return !this.prescriptionSettings_[key].options.includes(String(filterPrescriptionData[key])) }); } getTypeAndPrescription_ = () => { const reading_type = this.stepsData_[0].form?.reading_type; let type = SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY; let prescription = this.stepsData_[0].form[PRESCRIPTION]; let main_prescription_id = this.stepsData_[0].form.main_prescription_id; if (prescription === PRESCRIPTION_TYPE.READING && reading_type === READING_TYPE.READING_NO_LENS && this.judgeIsIncludeReadingType_(READING_TYPE.READING_NO_LENS, main_prescription_id)) { type = READING_TYPE.READING_NO_LENS; } return { type, [PRESCRIPTION]: prescription }; } calcEnterPrescriptionManuallyDataFromEdit_ = ({submitMethodOptionData, prescription_submit_form_data}) => { const filterPrescriptionData = this.getFilteredPrescriptionData_(prescription_submit_form_data); const errorkeys = this.getInvalidPrescriptionKeys_(filterPrescriptionData); this.stepsData_[2].form = prescription_submit_form_data; return this.getTypeAndPrescription_(); } createNewStepFormData_ = (form) => { return { type: NEW_STEP, id: "new-step", new_step_id: form.step_id, form, index: this.stepsData_.length } } validateOptionSelection_ = ({stepOptions, stepFormData}) => { const lensOptionData = stepOptions.find(item => item.id === stepFormData.step_option_id); if (!lensOptionData) { throw new Error(`Lens type not available`); } let prescriptionData = this.stepsData_.slice(0, 3).reduce((total, cur) => ({...total, ...cur.form}), {}); prescriptionData = this.prescriptionConvert(prescriptionData).formData const isOptionShow = this.verifyOptionShow(lensOptionData, prescriptionData); if (!isOptionShow) { throw new Error(`Option cannot be displayed properly`); } if (stepFormData.first_level_property_id || stepFormData.second_level_property_id) { if (lensOptionData.properties) { const optionPropertie = lensOptionData.properties.find(item => item.id === stepFormData.first_level_property_id || item.id === stepFormData.second_level_property_id); if (!optionPropertie) { throw new Error(`Property does not exist`); } if (optionPropertie.sub_properties) { const subOptionPropertie = optionPropertie.sub_properties.find(item => item.id === stepFormData.first_level_property_id || item.id === stepFormData.second_level_property_id); if (!subOptionPropertie) { throw new Error(`Sub property does not exist`); } } } } } validateNextStepHasChange_ = ({stepFormData, atcNextNewStep, isLast}) => { if (!atcNextNewStep) return const eligibleSteps = this.getEligibleSteps_(this.stepsConditions_, atcNextNewStep); if (!eligibleSteps?.length || !eligibleSteps.find(step => step.to_id === stepFormData.step_id)) { throw new Error(`您之前选择的镜片或其他类型的选项已经没有后续了,请您重新选择。`); } } assignStepFormData_ = ({stepFormData, stepType, index}) => { if (stepType === LENS_TYPE) { this.stepsData_[index].form = stepFormData; } else { this.stepsData_[index] = this.createNewStepFormData_(stepFormData); } } getOptionsAndTitle_ = ({stepFormData, stepType}) => { let options = [], stepTitle = ''; if (stepType === LENS_TYPE) { options = this.lens_.options; stepTitle = LENS_TYPE; } else { const currentStep = this.steps_.find(step => step.id === stepFormData.step_id) options = currentStep.step_options stepTitle = currentStep.title; } return { options, stepTitle }; } calcLensOrNewStepDataFromEdit_ = ({stepOptions, stepFormData, index, atcNextNewStep, isLast = false, stepType}) => { this.validateOptionSelection_({stepOptions, stepFormData}); this.validateNextStepHasChange_({stepFormData, atcNextNewStep, isLast}); this.assignStepFormData_({stepFormData, index, stepType}); const { options, stepTitle } = this.getOptionsAndTitle_({stepFormData, stepType}); this.productInfo_.stepsPriceData.push(this.calcStepPrice_(stepFormData, options, stepTitle, stepType)); } calcLensDataFromEdit_ = (stepsList) => { const stepsLength = stepsList.length; const stepFormData = stepsList.find(step => step.step_id === this.lens_.id); if (!stepFormData) return null this.calcLensOrNewStepDataFromEdit_({ stepOptions: this.lens_.options, stepFormData, index: 3, stepType: LENS_TYPE }); return stepFormData; } calcNewStepDataFromEdit_ = (stepsList, lensStepIndex) => { const stepsLength = stepsList.length stepsList.forEach((step, index) => { if (![this.prescription_types_.id, this.submit_methods_.id, this.lens_.id].includes(step.step_id) && (step.step_id && step.step_option_id)) { const step_data = this.steps_.find(item => item.id === step.step_id) if (step_data) { this.calcLensOrNewStepDataFromEdit_({ stepOptions: step_data.step_options, stepFormData: step, index: index - lensStepIndex + 3, atcNextNewStep: stepsList[index - 1], stepType: NEW_STEP, }); } } }) } showErrorMessage_ = (message) => { const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { api.showToast('The information has been updated, the option you originally selected no longer exists, please select again.'); }); } renderStepsFormEdit_ = async(data) => { const { prescription_type, submit_method, steps: stepsList, lens_process_id, ...prescription_submit_form_data } = data let submitMethodOptionData = {}, prescriptionFormData = {} let lensTypeOptionData = null try { this.calcPrescriptionDataFromEdit_(prescription_type, submit_method, stepsList[0]?.step_option_id, stepsList[0]?.first_level_property_id); submitMethodOptionData = this.calcSubmitMethodDataFromEdit_(submit_method, prescription_submit_form_data); prescriptionFormData = this.calcEnterPrescriptionManuallyDataFromEdit_({submitMethodOptionData, prescription_submit_form_data}); const lensStepIndex = stepsList.findIndex(step => step.step_id === this.lens_.id); lensTypeOptionData = this.calcLensDataFromEdit_(stepsList); this.calcNewStepDataFromEdit_(stepsList, lensStepIndex); } catch(error) { console.log('error', error); this.showErrorMessage_(error.message); } finally { await this.renderStep1_(this.prescription_types_, this.stepsData_[0].form); await this.renderStep2_(this.submit_methods_, {...this.stepsData_[1].form, prescription_image: this.prescription_image}); await this.renderStep2_1FromEdit_({submitMethodOptionData, prescription_submit_form_data}); await this.renderStep3_(lensTypeOptionData); } } scrollToSeletedPrescription_ = (dom) => { if(dom){ const parentDom = document.querySelector('#prescription-type-step-form'); parentDom.scrollTo({top: (dom.offsetTop - 60) , behavior: "smooth"}) } } renderEditComponent_ = async (product, data, process_data) => { await this.setupProperties_(product, data, process_data); await this.renderStepsFormEdit_(data, process_data); this.setupProductInfo_(product); await this.renderPrice_(); this.showIndex_({ index: 0 }); setTimeout(() => { const prescription_type_dom = document.querySelector(`#prescription-label-${this.stepsData_[0].form[PRESCRIPTION_ID]}`); this.scrollToSeletedPrescription_(prescription_type_dom); },400) this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); } setupProperties_ = async (product, data, process_data) => { this.product = product; const { prescription_type, submit_method, steps: stepsList, lens_process_id, ...prescription_submit_form_data } = data; this.prescription_image = prescription_submit_form_data.prescription_image || ''; this.setupStepsData_({prescription_type, submit_method, prescription_submit_form_data, stepsList}); this.process_data_ = process_data; this.setupProcessData_(this.process_data_); this.stepsConditions_ = this.resolveCondition_(this.process_data_.steps); this.globalSettings_ = await this.lensUtilsApi_.getGlobalSettings_(this.product_id, 'glasses_setting'); window.lens_process.settings = this.globalSettings_; this.lens_.price_display_rule = this.globalSettings_.price_display_rule; this.prescriptionSettings_ = await this.getPrescriptionSelects_(this.globalSettings_); this.lensUtilsApi_.setGlobalPrimaryColor(this.globalSettings_, 'glasses'); let trackReportData = { ...IncidentReportingData, event_type: "popup_expose", event_desc: "edit_prescription_sidebar_open", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", product_id: product?.product_id, selected_variant_id: product?.variant_id, process_id: this.process_data_?.lens_process_id, process_type: "glasses", last_action_type: "click_edit_prescription_btn", last_element_type: "button", last_element_name: "edit_prescription" }) } this.lensUtilsApi_.incidentReportingFn("#lens-process-sidebar",trackReportData,"function_expose"); } setupStepsData_ = ({prescription_type, submit_method, prescription_submit_form_data, stepsList}) => { this.stepsData_[0].form = { prescription_type, main_prescription_id:stepsList[0]?.step_option_id, sub_prescription_id: stepsList[0]?.first_level_property_id }; if (prescription_type === PRESCRIPTION_TYPE.READING) { this.stepsData_[0].form = { prescription_type, main_prescription_id:stepsList[0]?.step_option_id, ['reading_type']: submit_method ? READING_TYPE.READING_LENS : READING_TYPE.READING_NO_LENS }; } this.stepsData_[1].form = { submit_method }; this.stepsData_[2].form = prescription_submit_form_data; } setupProcessData_ = ({ prescription_types, lens, submit_methods, steps }) => { this.isHasNewStep_ = !!steps.length; this.prescription_types_ = this.lensUtilsApi_.resolvePrescriptionTypes_(prescription_types); this.submit_methods_ = submit_methods; this.lens_ = lens; this.steps_ = steps; } renderSteps_ = async (data) => { const { prescription_type, submit_method, steps: stepsList, lens_process_id, ...prescription_submit_form_data } = data; const prescriptionOptionData = this.prescription_types_.options.find(item => item.id === stepsList[0]?.step_option_id) this.productInfo_.stepsPriceData[0] = { "type": PRESCRIPTION, "title": prescription_type, "index": 0, ...prescriptionOptionData } let submitMethodOptionData = {} if (submit_method) { submitMethodOptionData = this.submit_methods_.options.find(item => item[SUBMIT_METHOD] === submit_method) || {} if (!prescription_submit_form_data.is_prism) { submitMethodOptionData = { ...submitMethodOptionData, price: 0 } } this.productInfo_.stepsPriceData[1] ={ "type": SUBMIT_METHOD, "title": submit_method, "index": 1, ...submitMethodOptionData } } await this.renderStep1_(this.prescription_types_, this.stepsData_[0].form); await this.renderStep2_(this.submit_methods_, {...this.stepsData_[1].form, prescription_image: this.prescription_image}); await this.renderStep2_1({ type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...submitMethodOptionData, }, prescription_submit_form_data); await this.renderRemainingSteps_(data); } renderRemainingSteps_ = async (data) => { const lens_step_form = data.steps.find(step => step.step_id === this.lens_.id); this.stepsData_[3].form = lens_step_form || {}; if (lens_step_form) { await this.renderStep3_(lens_step_form); const lens_price_data = this.calcStepPrice_(lens_step_form, this.lens_.options, LENS_TYPE, LENS_TYPE); this.productInfo_.stepsPriceData.push(lens_price_data); } const new_step_list = data.steps.filter(step => ![this.prescription_types_.id, this.submit_methods_.id, this.lens_.id].includes(step.step_id) && (step.step_id && step.step_option_id)); this.renderNewSteps_(new_step_list); } renderNewSteps_ = (new_step_list) => { new_step_list.forEach(step => { const step_data = this.steps_.find(item => item.id === step.step_id); if (step_data) { this.stepsData_.push({ type: NEW_STEP, id: "new-step", new_step_id: step.step_id, form: step, index: this.stepsData_.length }); this.productInfo_.stepsPriceData.push(this.calcStepPrice_(step, step_data.step_options, step_data.title, 'new_step')) } }); } setupProductInfo_ = (product) => { this.initProductInfo_ = { ...this.initProductInfo_, title: product.product_title, id: this.product_id, image: product.image, }; } renderStep2_1FromEdit_ = async ({submitMethodOptionData, prescription_submit_form_data}) => { const reading_type = this.stepsData_[0].form?.reading_type let type = SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY; let prescription = this.stepsData_[0].form[PRESCRIPTION]; let main_prescription_id = this.stepsData_[0].form.main_prescription_id; if (reading_type === READING_TYPE.READING_NO_LENS && this.isReadingTypeApplicable_(reading_type, main_prescription_id)) { type = READING_TYPE.READING_NO_LENS; } await this.renderStep2_1({ type, [PRESCRIPTION]: prescription, selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...submitMethodOptionData, }, prescription_submit_form_data); } isReadingTypeApplicable_ = (reading_type, main_prescription_id) => { if (!reading_type) return false; const readingOption = this.prescription_types_.options.find(item => (item[PRESCRIPTION] === PRESCRIPTION_TYPE.READING && item.id === main_prescription_id)); if (!readingOption) return false; const { is_diff_reading_glass, is_reading_no_glass } = readingOption; return is_diff_reading_glass || (is_reading_no_glass && reading_type === READING_TYPE.READING_NO_LENS) || (!is_reading_no_glass && reading_type === READING_TYPE.READING_LENS); } judgeIsIncludeReadingType_ = (reading_type, main_prescription_id) => { if (!reading_type) return false; const readingOption = this.prescription_types_.options.find(item => (item[PRESCRIPTION] === PRESCRIPTION_TYPE.READING && item.id === main_prescription_id)); if (!readingOption) return false; const { is_diff_reading_glass, is_reading_no_glass } = readingOption; return is_diff_reading_glass || (is_reading_no_glass && reading_type === READING_TYPE.READING_NO_LENS) || (!is_reading_no_glass && reading_type === READING_TYPE.READING_LENS); } goToIndex_ = (index) => { this.stepsIndex_.fromIndex = this.stepsIndex_.currentIndex; this.stepsIndex_.currentIndex = index; this.stepsIndex_.preIndex = this.stepsIndex_.currentIndex - 1; } goBackIndex_ = () => { [this.stepsIndex_.currentIndex, this.stepsIndex_.fromIndex] = [this.stepsIndex_.fromIndex, this.stepsIndex_.currentIndex]; this.stepsIndex_.preIndex = this.stepsIndex_.currentIndex - 1; } goPreIndex_ = () => { this.stepsIndex_.fromIndex = this.stepsIndex_.currentIndex; this.stepsIndex_.currentIndex = this.stepsIndex_.preIndex; this.stepsIndex_.preIndex = this.stepsIndex_.preIndex - 1; } goManyPreIndex_ = (nums = 0) => { this.stepsIndex_.fromIndex = this.stepsIndex_.currentIndex; this.stepsIndex_.currentIndex = this.stepsIndex_.currentIndex + nums; this.stepsIndex_.preIndex = this.stepsIndex_.currentIndex - 1; } showIndex_ = async ({ index }) => { await this.renderBackBtn_(index); const currentStepData = this.stepsData_[index]; const baseStepsId = { [PRESCRIPTION]: this.prescription_types_.id, [SUBMIT_METHOD]: this.submit_methods_.id, [SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY]: this.submit_methods_.id, [LENS_TYPE]: this.lens_.id, } this.currentStepType_ = currentStepData.type; const isNewStep_ = ![PRESCRIPTION, SUBMIT_METHOD, SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, LENS_TYPE].includes(this.currentStepType_); this.currentStepDomId_ = currentStepData.id; this.currentStepId_ = isNewStep_ ? currentStepData.new_step_id : baseStepsId[this.currentStepType_] // 只显示当前步骤,其他步骤全部隐藏 allStepComponentIds.forEach(id => { const ele_ = document.getElementById(id) if (ele_) { if (this.currentStepDomId_ === id) { ele_.style.display = 'block' } else { ele_.style.display = 'none' } } }) // 渲染下方提交 or 继续按钮 if (index > 4) { index = 4 } if(index == 2) { // 为了选填时图片上传的回填显示 const tempElement = document.getElementById("prescription-submit-step"); const api = await SPZ.whenApiDefined(tempElement) await api.handleIsPrescriptionImageUpdate_(); } this.handleLensProcessShowBtnAttr(index); } handleLensProcessShowBtnAttr = (index) => { const sidebarDom = document.querySelector('#lens-process-sidebar'); const currentStepData = this.stepsData_[index]; const currentStepDom = sidebarDom.querySelector('#' + currentStepData.id); const showBtn = currentStepDom.hasAttribute('show-btn') if (showBtn) { sidebarDom.setAttribute('show-btn', ''); } else { sidebarDom.removeAttribute('show-btn'); } } // 展示提交按钮,下一步还是atc showSubmitBtn_ = (type = 'atc', show = true) => { } getStepsData_() { return this.stepsData_ } // 更新 stepsData_ 中当前操作的数据 updateStepData = (type, id, value) => { const currentData = this.stepsData_.find(item => item.type === type && item.id === id) currentData.form = { ...currentData.form, ...value } } // 判断是否只有一个手动输入处方的提交方式 juadgeIsOnlyEnterPrescriptionManually = () => { return this.submit_methods_.options.length === 1 && this.submit_methods_.options[0][SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY } // 判断没区分老化处方和成品老花时,当前选中的是哪个老花 getCurrentReadingType = (main_prescription_id) => { const readingOption = this.prescription_types_.options.find(item => (item[PRESCRIPTION] === PRESCRIPTION_TYPE.READING && item.id === main_prescription_id)) return !readingOption.is_diff_reading_glass ? readingOption.is_reading_no_glass ? READING_TYPE.READING_NO_LENS : READING_TYPE.READING_LENS : null; } resolvePrescriptionFormData_ = (formData) => { const prescriptionType = this.stepsData_[0].form[PRESCRIPTION] if (prescriptionType === PRESCRIPTION_TYPE.READING) { const readingType = this.stepsData_[0].form['reading_type'] if (readingType === READING_TYPE.READING_NO_LENS) { // 成品老花在步骤三、新步骤的选项筛选是,将 add、cyl 的值设为 0 formData.prescription_od_add = '0'; formData.prescription_os_add = '0'; formData.prescription_od_cyl = '0'; formData.prescription_os_cyl = '0'; } } return formData } updatePrescriptionStepPriceData_ = async(formData) => { if (!formData) { const currentStepDom = document.getElementById('prescription-type-step') const currentFormDom = currentStepDom.querySelector('form') formData = new FormData(currentFormDom) formData = this.lensUtilsApi_.getFormData_(formData) } // 价格展示栏 let currentStepPrice = this.productInfo_.stepsPriceData.find(step => step.type === PRESCRIPTION) const currentStepOptionData = this.prescription_types_.options.find(prescription => prescription.id === formData.main_prescription_id); const currPropertyData = currentStepOptionData?.properties?.find(property => property.id === formData.sub_prescription_id); if (currentStepPrice) { currentStepPrice = { ...currentStepPrice, title: formData[PRESCRIPTION], ...currentStepOptionData, } if(currPropertyData) { currentStepPrice = { ...currentStepPrice, text_property: { price: currPropertyData.price, compare_at_price: currPropertyData.compare_at_price } } } else { delete currentStepPrice.text_property; } this.productInfo_.stepsPriceData[0] = currentStepPrice; } else { let priceData = {...currentStepOptionData}; if(currPropertyData) { priceData = { ...priceData, text_property: { price: currPropertyData.price, compare_at_price: currPropertyData.compare_at_price } } }else { delete priceData.text_property; } this.productInfo_.stepsPriceData.push({ "type": PRESCRIPTION, "title": formData[PRESCRIPTION], "index": 0, ...priceData, }) } } // 提交步骤 submitStep_ = async ( { type, id, formData, cb = function () {}, ...attrs }) => { // 如果没有 formData,那么就通过 form_id 直接获取 formData 的数据 // 通常有 formData 的情况都是被组件处理过后传输过来的 if (!formData) { const currentStepDom = document.getElementById(id) const currentFormDom = currentStepDom.querySelector('form') formData = new FormData(currentFormDom) formData = this.lensUtilsApi_.getFormData_(formData) } let currentStepData = {} if (NEW_STEP === type) { currentStepData = this.stepsData_.find(item => item.type === type && item.id === id && item.new_step_id === formData.step_id) } else { currentStepData = this.stepsData_.find(item => item.type === type && item.id === id) } let index = currentStepData.index if (PRESCRIPTION === type) { index = 1 let isChange = true // 处方类型更新了,就要清除后面步骤的数据 if (formData[PRESCRIPTION] === PRESCRIPTION_TYPE.READING) { isChange = (this.stepsData_[0].form[PRESCRIPTION_ID] !== formData[PRESCRIPTION_ID] || this.stepsData_[0].form['reading_type'] !== formData['reading_type']) } else{ isChange = this.stepsData_[0].form[PRESCRIPTION_ID] !== formData[PRESCRIPTION_ID] || (this.stepsData_[0].form[SUB_PRESCRIPTION_ID] && this.stepsData_[0].form[SUB_PRESCRIPTION_ID] !== formData[SUB_PRESCRIPTION_ID]) } if (isChange) { this.stepsData_.splice(4) this.stepsData_[1].form = {} this.stepsData_[2].form = {} this.stepsData_[3].form = {} this.productInfo_.stepsPriceData.splice(1) await this.renderStep2_(this.submit_methods_) } // 价格展示栏 this.updatePrescriptionStepPriceData_(); // 区分老化处方和成品老花处方 // 如果是成品处方, 那么就到 处方填写组件(包含成品处方选择) if (formData[PRESCRIPTION] === PRESCRIPTION_TYPE.READING) { const { reading_type, main_prescription_id } = formData if (reading_type === READING_TYPE.READING_NO_LENS || this.getCurrentReadingType(main_prescription_id) === READING_TYPE.READING_NO_LENS) { index = 2 // 渲染处方填写组件 await this.renderStep2_1({ type: READING_TYPE.READING_NO_LENS, [PRESCRIPTION]: PRESCRIPTION_TYPE.READING, selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...attrs }, this.stepsData_[2]?.form || {}) } } // 如果是无处方,那么直接到镜片类型选择 if (formData[PRESCRIPTION] === PRESCRIPTION_TYPE.NON_PRESCRIPTION) { index = 3 // 渲染镜片类型组件 if (isChange) { currentStepData.form = formData await this.renderStep3_() } } if(formData[PRESCRIPTION] === PRESCRIPTION_TYPE.FRAME_ONLY){ index = 0; if(attrs?.title === "frame_only_add_to_cart"){ currentStepData.form = formData; await this.addToCart_(); return false; }else{ await this.renderPrice_(); return false; } } if (this.juadgeIsOnlyEnterPrescriptionManually() && formData[PRESCRIPTION] !== PRESCRIPTION_TYPE.NON_PRESCRIPTION && ((formData[PRESCRIPTION] === PRESCRIPTION_TYPE.READING && formData['reading_type'] !== READING_TYPE.READING_NO_LENS) || (formData[PRESCRIPTION] !== PRESCRIPTION_TYPE.READING)) ) { currentStepData.form = formData index = 2 this.stepsData_[1].form = { [SUBMIT_METHOD] : SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY } let currentStepOptionData = this.submit_methods_.options.find(submit_step => submit_step[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) if (!this.is_prism) { currentStepOptionData = { ...currentStepOptionData, price: 0, compare_at_price: 0 } } await this.renderStep2_1({ type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...attrs, ...currentStepOptionData }) let currentStepPrice = this.productInfo_.stepsPriceData.find(step => step.type === SUBMIT_METHOD) if (currentStepPrice) { currentStepPrice = { ...currentStepPrice, title: formData[SUBMIT_METHOD], ...currentStepOptionData } this.productInfo_.stepsPriceData[1] = currentStepPrice } else { this.productInfo_.stepsPriceData.push({ "type": SUBMIT_METHOD, "title": currentStepOptionData.title, "index": 1 }) } } } if (SUBMIT_METHOD === type) { // 处方填写 or 老花处方 const isChange = this.stepsData_[1].form[SUBMIT_METHOD] !== formData[SUBMIT_METHOD] const currentStepOptionData = this.submit_methods_.options.find(submit_step => submit_step[SUBMIT_METHOD] === formData[SUBMIT_METHOD]) if (isChange) { // 提交方式更新了,就要清除后面步骤的数据 this.stepsData_.splice(4) this.stepsData_[2].form = {} this.stepsData_[3].form = {} this.productInfo_.stepsPriceData.splice(2) this.is_prism = false } let currentStepPrice = this.productInfo_.stepsPriceData.find(step => step.type === SUBMIT_METHOD) if (currentStepPrice) { currentStepPrice = { ...currentStepPrice, title: formData[SUBMIT_METHOD], ...currentStepOptionData } if (!this.is_prism) { currentStepPrice.price = 0 currentStepPrice.compare_at_price = 0 } this.productInfo_.stepsPriceData[1] = currentStepPrice } else { this.productInfo_.stepsPriceData.push({ "type": SUBMIT_METHOD, "title": currentStepOptionData.title, "index": 1, }) } if (formData[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) { index = 2 if (isChange) { await this.renderStep2_1({ type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...attrs, ...currentStepOptionData }) } } else { index = 3 // upload 或者 email later if (formData[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.UPLOAD) { this.prescription_image = formData.prescription_image } else { this.prescription_image = '' } if (isChange) { currentStepData.form = formData await this.renderStep3_() } } // 处方填写 or 老花处方才能到 处方提交组件 // upload 和 email later } if (SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY === type) { const { is_prism } = formData this.is_prism = is_prism const { is_save_prescription, prescription_id, metafield_id } = formData this.is_save_prescription = is_save_prescription || false this.prescription_id = prescription_id this.metafield_id = metafield_id if (this.stepsData_[0].form.prescription_type !== "Reading" || (this.stepsData_[0].form.prescription_type === "Reading" && this.stepsData_[0].form.reading_type !== READING_TYPE.READING_NO_LENS)) { let currentStepPrice = this.productInfo_.stepsPriceData.find(step => step.type === SUBMIT_METHOD) if (currentStepPrice) { if (is_prism) { const currentStepOptionData = this.submit_methods_.options.find(it => it[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) currentStepPrice = { ...currentStepPrice, ...currentStepOptionData } } else { delete currentStepPrice.price delete currentStepPrice.compare_at_price } this.productInfo_.stepsPriceData[1] = currentStepPrice; } } currentStepData.form = this.clearPrescriptionFormData_(formData); if (Object.keys(this.stepsData_[3].form).length !== 0) { this.judgeLensAndNewStepChanged_(); } else { this.stepsData_.splice(4); this.stepsData_[3].form = {}; this.productInfo_.stepsPriceData.splice(2); await this.renderStep3_() } index = 3 } if (LENS_TYPE === type) { index = 4 const nextNewStep = await this.findNextStep_(formData) await this.findStepPrice_(formData) let isNull = JSON.stringify(currentStepData.form) === "{}" let isChange = this.judgeIsOptionAndPropertiesChange_(currentStepData.form, formData) let newStepFormData = this.stepsData_.find(step => step.index === 4 && step.type === NEW_STEP) if (isNull || isChange || (newStepFormData?.form?.step_id !== nextNewStep?.id)) { this.stepsData_.splice(4) if ((!isNull && isChange) || (newStepFormData?.form?.step_id !== nextNewStep?.id)) { this.productInfo_.stepsPriceData.splice(this.productInfo_.stepsPriceData.findIndex(item => item.type === LENS_TYPE) + 1) } if (!nextNewStep) { currentStepData.form = formData await this.addToCart_() return false } this.stepsData_.push( { type: NEW_STEP, id: "new-step", new_step_id: nextNewStep.id, form: {}, index: 4 } ) await this.renderNewStep_(nextNewStep) } else { if (!nextNewStep) { currentStepData.form = formData await this.addToCart_() return false } let newStepFormData = this.stepsData_.find(step => step.index === 4 && step.type === NEW_STEP) // 没有修改镜片类型的话就需要回填数据 await this.renderNewStep_(nextNewStep, newStepFormData?.form || null) } } if (NEW_STEP === type) { index++ const nextNewStep = await this.findNextStep_(formData) await this.findStepPrice_(formData) let isNull = JSON.stringify(currentStepData.form) === "{}" let isChange = this.judgeIsOptionAndPropertiesChange_(currentStepData.form, formData) let newStepFormData = this.stepsData_.find(step => step.new_step_id === nextNewStep?.id) if (isNull || isChange || (newStepFormData?.form?.step_id !== nextNewStep?.id)) { this.stepsData_.splice(currentStepData.index + 1) if ((!isNull && isChange) || (nextNewStep && newStepFormData && newStepFormData?.form?.step_id !== nextNewStep.id)) { this.productInfo_.stepsPriceData.splice(this.productInfo_.stepsPriceData.findIndex(item => item.step_id === currentStepData.form.step_id) + 1) } currentStepData.form = formData if (!nextNewStep) { await this.addToCart_() return false } this.stepsData_.push( { type: NEW_STEP, id: "new-step", new_step_id: nextNewStep.id, form: {}, index: currentStepData.index + 1 } ) await this.renderNewStep_(nextNewStep) } else { currentStepData.form = formData if (!nextNewStep) { await this.addToCart_() return false } let newStepFormData = this.stepsData_.find(step => step.new_step_id === nextNewStep.id) await this.renderNewStep_(nextNewStep, newStepFormData?.form || null) } } currentStepData.form = formData // 增加一个回调给步骤组件 await cb(true) // 跳转到下一步 await this.renderPrice_() this.goToIndex_(index) await this.showIndex_({ index }) } getFormData_(formData) { let obj = {} for (let key of formData.keys()) { obj[key] = formData.get(key) } return obj } calcAfterStepsPrice_ = (currentStepDataForm) => { const currentStepIndex = this.stepsData_.findIndex(step => step.form.step_id === currentStepDataForm.step_id); if (currentStepIndex !== -1) { const stepList = this.stepsData_.slice(currentStepIndex + 1); stepList.forEach(step => this.calcStepPriceAndPush_(step)); } } calcStepPriceAndPush_ = (step) => { const step_data = this.steps_.find(item => item.id === step.form.step_id) if (step_data && !this.productInfo_.stepsPriceData.find(item => item.step_id === step.form.step_id)) { this.productInfo_.stepsPriceData.push(this.calcStepPrice_(step.form, step_data.step_options, step_data.title, NEW_STEP)) } } renderBackBtn_ = async (index) => { const backBtnDom = document.getElementById('process-back-btn') const backBtnApi = await SPZ.whenApiDefined(backBtnDom) await backBtnApi.doRender_(index === 0 ? 'hidden' : 'block'); } renderSubmitBtn_ = async (data) => { const backBtnDom = document.getElementById('process-submit-btn') const backBtnApi = await SPZ.whenApiDefined(backBtnDom) await backBtnApi.doRender_(data); } renderStep1_ = async (data, formData) => { // render 步骤一, 处方类型 // 步骤一:列表 const renderData = { ...data, price_display_rule: this.globalSettings_.price_display_rule } const tempElement = document.getElementById("prescription-type-step"); const api = await SPZ.whenApiDefined(tempElement) await api.doRender_(renderData, formData); } renderStep2_ = async (data, formData) => { // render 步骤二, 提交方式 // 步骤二:列表 const tempElement = document.getElementById("submit-method-step"); const api = await SPZ.whenApiDefined(tempElement) await api.doRender_(data, formData); } renderStep2_1 = async (data, formData) => { // render 步骤二, 提交方式-填写处方 // 步骤二:列表 const tempElement = document.getElementById("prescription-submit-step"); const api = await SPZ.whenApiDefined(tempElement) data.lens_process_id = this.process_data_.lens_process_id; await api.doRender_(data, formData); } renderStep3_ = async (formData) => { // 步骤三:列表, 镜片类型x // 获取步骤一二的表单数据: 处方类型、提交方式、填写表单 // template let stepFormData = this.stepsData_.slice(0, 3).reduce((total, cur) => { return { ...total, ...cur.form } }, {}) const {formData: prescriptionData, isConvert} = this.prescriptionConvert(stepFormData) this.setAtcPrescriptionConvertParams_(prescriptionData, isConvert); // 选项条件过滤 let filterLens = this.lens_.options.filter(item => this.verifyOptionShow(item, prescriptionData)) const data = { ...this.lens_, options: filterLens, } const tempElement = document.getElementById("lens-type-step"); const api = await SPZ.whenApiDefined(tempElement) await api.doRender_(data, formData ? JSON.parse(JSON.stringify(formData)) : null); } renderNewStep_ = async (newStepData, formData) => { let stepFormData = this.stepsData_.slice(0, 3).reduce((total, cur) => { return { ...total, ...cur.form } }, {}) const {formData: prescriptionData, isConvert} = this.prescriptionConvert(stepFormData) this.setAtcPrescriptionConvertParams_(prescriptionData, isConvert); // 选项条件过滤 let filterLens = newStepData.step_options.filter(item => this.verifyOptionShow(item, prescriptionData)) const data = { ...newStepData, step_options: filterLens, price_display_rule: this.globalSettings_.price_display_rule, } const tempElement = document.getElementById("new-step"); const api = await SPZ.whenApiDefined(tempElement) await api.doRender_(JSON.parse(JSON.stringify(data)), formData ? JSON.parse(JSON.stringify(formData)) : null); } setAtcPrescriptionConvertParams_ = (prescriptionData, isConvert) => { if (!isConvert) { this.atcPrescriptionConvertParams_ = {}; return; } this.atcPrescriptionConvertParams_ = Object.keys(this.lensUtilsApi_.filterObject_(prescriptionData, ['prescription_od_add', 'prescription_od_axis', 'prescription_od_cyl', 'prescription_od_sph', 'prescription_os_add', 'prescription_os_axis', 'prescription_os_cyl', 'prescription_os_sph'])).reduce((acc, key) => { if (prescriptionData[key] !== null) { acc[key + '_conversion_to'] = prescriptionData[key]; } return acc; }, {}); } judgeLensAndNewStepChanged_ = async () => { let stepFormData = this.stepsData_.slice(0, 3).reduce((total, cur) => { return { ...total, ...cur.form } }, {}) const {formData: prescriptionData} = this.prescriptionConvert(stepFormData) try { this.judgeLensChanged_(prescriptionData); this.judgeNewsStepChanged_(prescriptionData); await this.renderStep3_(this.stepsData_[3].form); } catch(err) { let errorId = err.message; const errorDataIndex = this.stepsData_.findIndex(item => item.form.step_id === errorId); const errorPriceIndex = this.productInfo_.stepsPriceData.findIndex(step => step.step_id === errorId); if (errorDataIndex === 3) { this.stepsData_.splice(4); this.stepsData_[3].form = {}; this.productInfo_.stepsPriceData.splice(2); await this.renderStep3_(); } else { if (errorPriceIndex !== -1) { this.productInfo_.stepsPriceData.splice(errorPriceIndex); } if (errorDataIndex > 3) { this.stepsData_.splice(errorDataIndex); await this.renderStep3_(this.stepsData_[3].form); } } } } judgeLensChanged_ = (prescriptionData) => { let filterLens = this.lens_.options.filter(item => this.verifyOptionShow(item, prescriptionData)); if (!filterLens.find(item => item.id === this.stepsData_[3].form.step_option_id)) { throw new Error(this.lens_.id); } } judgeNewsStepChanged_ = (prescriptionData) => { const newStepList = this.productInfo_.stepsPriceData.filter(item => ![PRESCRIPTION, SUBMIT_METHOD, LENS_TYPE].includes(item.type)); newStepList.forEach((stepData, index) => { const step = this.steps_.find(item => item.id === stepData.step_id); if (step) { let filterLens = step.step_options.filter(item => this.verifyOptionShow(item, prescriptionData)); if (!filterLens.find(item => item.id === stepData.id)) { throw new Error(step.id); } } }) } changePrismStatus_ = (status) => { this.is_prism = status let currentStepPrice = this.productInfo_.stepsPriceData.find(step => step.type === SUBMIT_METHOD) if (currentStepPrice) { if (status) { const currentStepOptionData = this.submit_methods_.options.find(it => it[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) currentStepPrice = { ...currentStepPrice, ...currentStepOptionData } } else { currentStepPrice.price = 0 currentStepPrice.compare_at_price = 0 } this.productInfo_.stepsPriceData[1] = currentStepPrice this.renderPrice_() } } saveMyPrescription_ = (data) => { return this.xhr_.fetchJson("/api/fireant_auth/prescriptions", { method: "post", body: data }); } getPrescriptionSelects_ = (data) => { const tempElement = document.getElementById('spz-custom-lamb-locale-script'); return SPZ.whenApiDefined(tempElement).then(async (api) => { let lacale_value = await api.getLambLocale(); lacale_value = lacale_value.lens_map; const birth_config = data.birth_config; const eye_height_config = data.eye_height_config; const current_year = new Date().getFullYear(); const prescription_selects = { sph: { options: window.lens_process.getOptions(data["min_sph"], data["max_sph"], 0.25, { type: 'decimal', has_symbol: true }), selected: "0.00", }, cyl: { options: window.lens_process.getOptions(data["min_cyl"], data["max_cyl"], 0.25, { type: 'decimal', has_symbol: true }), selected: "0.00", }, axis: { options: window.lens_process.getOptions(data["min_axis"], data["max_axis"], 1), selected: lacale_value.get("prescription.default.none"), placeholder: { value: lacale_value.get("prescription.default.none"), }, disabled: true, }, add: { options: window.lens_process.getOptions(data["min_add"], data["max_add"], 0.25, { type: 'decimal' }), selected: "0.00", }, pd: { options: window.lens_process.getOptions(data["min_pd"], data["max_pd"], 1), selected: "63", }, pd_r: { // two pds options: window.lens_process.getOptions(data["min_bipd"], data["max_bipd"], 0.5, { type: 'decimal' }), selected: lacale_value.get("prescription.default.right_pd"), placeholder: { value: lacale_value.get("prescription.default.right_pd"), disabled: true, } }, pd_l: { // two pds options: window.lens_process.getOptions(data["min_bipd"], data["max_bipd"], 0.5, { type: 'decimal' }), selected: lacale_value.get("prescription.default.left_pd"), placeholder: { value: lacale_value.get("prescription.default.left_pd"), disabled: true, } }, bd: { options: [ { value: lacale_value.get("prescription.term.in"), title: lacale_value.get("prescription.term.in") }, { value: lacale_value.get("prescription.term.out"), title: lacale_value.get("prescription.term.out") } ], selected: "", placeholder: { value: "", title: lacale_value.get("prescription.default.select"), disabled: true, }, disabled: true }, bd_r: { options: [ { value: lacale_value.get("prescription.term.up"), title: lacale_value.get("prescription.term.up") }, { value: lacale_value.get("prescription.term.down"), title: lacale_value.get("prescription.term.down") } ], selected: "", placeholder: { value: "", title: lacale_value.get("prescription.default.select"), disabled: true, }, disabled: true }, pv: { options: window.lens_process.getOptions(0, 5, 0.25, { type: 'decimal' }), selected: '0.00' }, ocular_height: { options: window.lens_process.getOptions(data["min_eye_height"], data["max_eye_height"], 0.25,{ type: 'decimal'}), selected: lacale_value.get("prescription.default.select"), }, birth_year: { options: window.lens_process.getOptions(1913, current_year, 1), selected: lacale_value.get("prescription.default.select"), }, } window.lens_process.locale = { "select": lacale_value.get("prescription.default.select"), "please_enter_name": lacale_value.get("prescription.default.please_enter_name") } return { prescription_od_sph: prescription_selects.sph, prescription_os_sph: prescription_selects.sph, prescription_od_cyl: prescription_selects.cyl, prescription_os_cyl: prescription_selects.cyl, prescription_od_axis: prescription_selects.axis, prescription_os_axis: prescription_selects.axis, prescription_od_add: prescription_selects.add, prescription_os_add: prescription_selects.add, prescription_pd: prescription_selects.pd, prescription_pd_l: prescription_selects.pd_l, prescription_pd_r: prescription_selects.pd_r, prescription_od_pv: prescription_selects.pv, prescription_os_pv: prescription_selects.pv, prescription_od_bd: prescription_selects.bd, prescription_os_bd: prescription_selects.bd, prescription_od_pv_r: prescription_selects.pv, prescription_os_pv_r: prescription_selects.pv, prescription_od_bd_r: prescription_selects.bd_r, prescription_os_bd_r: prescription_selects.bd_r, ocular_height: eye_height_config != 'no' ? prescription_selects.ocular_height : '', birth_year: birth_config != 'no' ? prescription_selects.birth_year : '', upload_prescription_image_config: data.upload_prescription_image_config, } }); } resolvePrescriptionNone_ = (formData) => { let keys = ['prescription_od_cyl', 'prescription_od_sph', 'prescription_od_axis', 'prescription_os_cyl', 'prescription_os_sph', 'prescription_os_axis']; let collectToNoneKeys = []; function convertToNumberOrZero(key, value) { let tempValue = Number(value); if (Number.isNaN(tempValue)) { collectToNoneKeys.push(key); return 0; } return tempValue; } keys.forEach(key => { formData[key] = convertToNumberOrZero(key, formData[key]); }); return {formData, collectToNoneKeys}; } prescriptionConvert = (formData) => { formData = this.resolvePrescriptionFormData_(formData) if (!['positive_to_negative', 'negative_to_positive'].includes(this.globalSettings_.convert_prescription_config)) { return {formData, isConvert: false} } let isConvert = false let convert_od_sph, convert_od_cyl, convert_od_axis, convert_os_sph, convert_os_cyl, convert_os_axis let {formData: newFormData, collectToNoneKeys} = this.resolvePrescriptionNone_(JSON.parse(JSON.stringify(formData))); formData = newFormData; let { prescription_od_cyl, prescription_od_sph, prescription_od_axis, prescription_os_cyl, prescription_os_sph, prescription_os_axis } = formData let total = {} if(this.globalSettings_.convert_prescription_config === 'positive_to_negative') { if (prescription_od_cyl > 0) { isConvert = true convert_od_sph = prescription_od_cyl + prescription_od_sph; convert_od_cyl = - prescription_od_cyl; convert_od_axis = prescription_od_axis > 90 ? prescription_od_axis - 90 : prescription_od_axis + 90 ; total = { ...total, prescription_od_sph: convert_od_sph, prescription_od_cyl: convert_od_cyl, prescription_od_axis: convert_od_axis, } } if (prescription_os_cyl > 0) { isConvert = true convert_os_sph = prescription_os_cyl + prescription_os_sph; convert_os_cyl = - prescription_os_cyl; convert_os_axis = prescription_os_axis > 90 ? prescription_os_axis - 90 : prescription_os_axis + 90; total = { ...total, prescription_os_sph: convert_os_sph, prescription_os_cyl: convert_os_cyl, prescription_os_axis: convert_os_axis } } } else if(this.globalSettings_.convert_prescription_config === 'negative_to_positive'){ if (prescription_od_cyl < 0) { isConvert = true convert_od_sph = prescription_od_cyl + prescription_od_sph; convert_od_cyl = - prescription_od_cyl; convert_od_axis = prescription_od_axis > 90 ? prescription_od_axis - 90 : prescription_od_axis + 90 ; total = { ...total, prescription_od_sph: convert_od_sph, prescription_od_cyl: convert_od_cyl, prescription_od_axis: convert_od_axis, } } if (prescription_os_cyl < 0) { isConvert = true convert_os_sph = prescription_os_cyl + prescription_os_sph; convert_os_cyl = - prescription_os_cyl; convert_os_axis = prescription_os_axis > 90 ? prescription_os_axis - 90 : prescription_os_axis + 90; total = { ...total, prescription_os_sph: convert_os_sph, prescription_os_cyl: convert_os_cyl, prescription_os_axis: convert_os_axis } } } function convertNumberValuesToString(obj) { for (let key in obj) { if (typeof obj[key] === 'number') { if (['prescription_os_axis', 'prescription_od_axis'].includes(key)) { obj[key] = obj[key].toString(); } else { obj[key] = (obj[key] > 0 ? "+" : "") + obj[key].toFixed(2).toString(); } } } return obj; } collectToNoneKeys.forEach(key => { formData[key] = 'None' }) return {formData: convertNumberValuesToString({...formData, ...total}), isConvert} } trackAddToCart = () => { const params = { product_id: this.initProductInfo_?.id, variant_id: this.variant_id, quantity: Number(this.quantity_), number: Number(this.quantity_), name: this.initProductInfo_?.title, item_price: this.initProductInfo_?.price, source: "add_to_cart", _extra: { ...IncidentReportingData, event_type: "click", event_developer: "jozy", event_info: JSON.stringify({ action_type: "glasses_lens_add_to_cart", product_id: this.product_id, process_id: this.process_data_.lens_process_id, process_type: "glasses", element_type: "button", element_name: "add_to_cart_btn" }) } } const event = new CustomEvent('dj.addToCart', { detail: params || null, bubbles: true }); document.body && document.body.dispatchEvent(event); } addToCart_ = async () => { this.trackAddToCart(); // 加购 // 将 stepsData_ 的数据变成加购接口请求的数据 const stepData = this.stepsData_.reduce((total, cur) => { if (cur.type === 'prescription_type') { const currentPrescription = this.prescription_types_.options.find(item => item.id === cur.form.main_prescription_id); total.steps = total.steps.concat({ step_id: this.prescription_types_.id, step_option_id: currentPrescription?.id, first_level_property_id: cur.form.sub_prescription_id }) total.prescription_type = currentPrescription[PRESCRIPTION] } else if (cur.type === 'submit_method') { let submit_method_option = this.submit_methods_.options.find(item => item.submit_method === cur.form.submit_method) // 无处方时,是没有提交方式的,所以在这里处理一下 if (submit_method_option) { total.steps = total.steps.concat({ step_id: this.submit_methods_.id, step_option_id: submit_method_option.id, }) total.submit_method = submit_method_option[SUBMIT_METHOD] } return total } else if (cur.type === 'Enter prescription manually') { // 处方提交填写的值是平铺到请求体中 total = { ...total, ...cur.form } } else if (cur.type === 'lens_type') { // 镜片的数据无需特别处理 total.steps = total.steps.concat(cur.form) } else if (cur.type === 'new_step') { // 新步骤的数据无需特别处理 total.steps = total.steps.concat(cur.form) } return total }, { steps: [] }) stepData.steps = stepData.steps.filter(step => step.step_id && step.step_option_id) let that = this; let prescription_image = this.prescription_image; if (stepData.submit_method && (stepData.submit_method === 'Enter prescription manually')) { prescription_image = stepData.prescription_image; } const reqBody = { product_id: this.product_id, variant_id: this.variant_id, quantity: Number(this.quantity_), is_save_prescription: this.is_save_prescription, prescription_id: this.prescription_id, metafield_id: this.metafield_id, properties: { currency: window.SHOPLAZZA.currency_code, lens_processing_id: that.process_data_.lens_process_id, ...stepData, prescription_image, ...this.atcPrescriptionConvertParams_, } } if(this.atc_loading_) return; this.atc_loading_ = true; document.querySelector('#lens-process-sidebar').setAttribute('atc-loading', ''); try { let requestMethod = 'post' if (this.atcType_ === 'edit_lens') { requestMethod = 'put' reqBody.line_item_id = this.line_item_id } else { delete reqBody.line_item_id } const data = await this.xhr_.fetchJson('/api/fireant/customize_cart', { method: requestMethod, body: reqBody }); if(data.state === 'success') { window.location.href = "/cart" } else { this.atc_loading_ = false; document.querySelector('#lens-process-sidebar').removeAttribute('atc-loading'); } // 跳转到购物车 // 加购成功,关闭弹窗 // this.triggerEvent_('atcSuccess') } catch (e) { this.atc_loading_ = false; document.querySelector('#lens-process-sidebar').removeAttribute('atc-loading'); e.then(data => { const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { api.showToast(data.message || (data.errors && data.errors[0]) || 'Unknown error'); }); }) } } getStepDataByType_ = (type, id) => { } getCurrentStepData_ = () => { for (let i = 0; i < this.stepsData_.length; i++) { if (i === this.stepsIndex_.currentIndex) { return this.stepsData_[i] } } } verifyOptionShow = (option, conditionObj) => { conditionObj.prescription_option_id = `${conditionObj.main_prescription_id}${conditionObj.sub_prescription_id ? '/' + conditionObj.sub_prescription_id : ''}`; // 判断选项 option 是否展示 let { match_option, rule_modules } = option.rule if (!match_option || !rule_modules.length) { return true } let verifyType = verifyTypes[match_option] rule_modules = rule_modules.filter(item => item.rule_items?.length > 0) if (!rule_modules?.length) { return true } return rule_modules[verifyType]((rule_module) => { const { match_option, rule_items } = rule_module let verifyType = verifyTypes[match_option] return rule_items[verifyType](({ key, value, optional_value, operator }) => { if (!key) return true; let properties = propertyEnum[key] if (key === "PD") { if ("prescription_pd" in conditionObj) { properties = ["prescription_pd"] } else { properties = ["prescription_pd_l", "prescription_pd_r"] } } return properties.every(property => { let currentConditionValue = conditionObj[property] if ([undefined, null, '', 'None'].includes(currentConditionValue)) { // 如果值不存在,则不符合条件 return false } if (operator === 'BETWEEN') { // 在什么之间 currentConditionValue = Number(currentConditionValue) const min = Number(value) const max = Number(optional_value) return currentConditionValue >= min && currentConditionValue <= max } if (operator === 'GREATER_THAN_OR_EQUAL' || operator === 'MORE_THAN') { currentConditionValue = Number(currentConditionValue) const max = Number(value) return currentConditionValue >= max } if (operator === 'SMALLER_THAN_OR_EQUAL' || operator === 'LESS_THAN') { currentConditionValue = Number(currentConditionValue) const min = Number(value) return currentConditionValue <= min } if (operator === 'GREATER_THAN') { currentConditionValue = Number(currentConditionValue) const max = Number(value) return currentConditionValue > max } if (operator === 'SMALLER_THAN') { currentConditionValue = Number(currentConditionValue) const min = Number(value) return currentConditionValue < min } if (operator === 'IN') { // 包含 let valueArray = value.split(','); if(key === 'PRESCRIPTION_OPTION_ID') { return valueArray.some(v => { if(v.includes('/')) { return valueArray.includes(currentConditionValue); } else { return currentConditionValue.includes(v); } }); } else { return valueArray.includes(currentConditionValue); } } if (operator === 'EQUAL') { // 等于 if(key === 'PRESCRIPTION_OPTION_ID' && value.includes('/')) { const [main_prescription_id, sub_prescription_id] = value.split('/'); return main_prescription_id === conditionObj.main_prescription_id && sub_prescription_id === conditionObj.sub_prescription_id; } else { return value === currentConditionValue } } if (operator === 'NOT_EQUAL') { // 不等于 if(key === 'PRESCRIPTION_OPTION_ID' && value.includes('/')) { const [main_prescription_id, sub_prescription_id] = value.split('/'); return main_prescription_id !== conditionObj.main_prescription_id || sub_prescription_id !== conditionObj.sub_prescription_id; } else { return value !== currentConditionValue } } return true }) }) }) } rerender_ = async () => { // 清空之前的数据 this.stepsData_.forEach((step) => { step.form = {} }) // 重新加载步骤一、二 await this.renderStep1_(prescription_types); await this.renderStep2_(submit_methods); this.showIndex_({ index: 0 }) } // 查找下一步的数据,没查到就返回 null findNextStep_ = async (conditionObj) => { this.clearStepsPriceByChange(conditionObj); const nextStep = this.findNextStepId(this.stepsConditions_, conditionObj); return nextStep ? this.steps_.find(step => step.id == nextStep.to_id) : null; } clearStepsPriceByChange = (formData) => { let currentStepData = this.stepsData_.find(item => item.form?.step_id === formData?.step_id); if (!currentStepData) return; let isChange = this.judgeIsOptionAndPropertiesChange_(currentStepData.form, formData) if (isChange) { this.clearFollowingSteps_(formData); } else { this.calcAfterStepsPrice_(formData); } } clearFollowingSteps_ = (formData) => { const priceIndex = this.productInfo_.stepsPriceData.findIndex(step => step.step_id == formData.step_id); if (priceIndex !== -1) { this.productInfo_.stepsPriceData.splice(priceIndex + 1); } } findStepPrice_ = async (conditionObj) => { const currentStepPrice = { step_id: conditionObj.step_id, ...this.findPropertyPrice_(conditionObj) } const priceIndex = this.productInfo_.stepsPriceData.findIndex(step => step.step_id == currentStepPrice.step_id) if (priceIndex !== -1) { this.productInfo_.stepsPriceData.splice(priceIndex, 1, currentStepPrice) } else { this.productInfo_.stepsPriceData.push(currentStepPrice) } await this.renderPrice_() } getEligibleSteps_ = (stepConditions, conditionObj) => { const { step_id, step_option_id, first_level_property_id, second_level_property_id } = conditionObj; return stepConditions?.filter(({ step_id: id, step_option_id: optionId, first_level_property_id: textId, second_level_property_id: colorId }) => { return id === step_id && (!optionId || optionId === step_option_id) && (!textId || textId === first_level_property_id) && (!colorId || colorId === second_level_property_id); }); } findNextStepId = (stepConditions, conditionObj) => { const satisfyOptions = this.getEligibleSteps_(stepConditions, conditionObj); if (!satisfyOptions?.length) { return null; } let currentObj = {} // comparedPriority 对比哪个步骤的优先级高 const comparedPriority = (pre, cur) => { const properties = ['second_level_property_id', 'first_level_property_id', 'step_option_id']; for (let property of properties) { if (pre[property] && cur[property]) { return Math.round(Math.random()) === 1 ? cur : pre; } if (pre[property] || cur[property]) { return pre[property] ? pre : cur; } } return Math.round(Math.random()) === 1 ? cur : pre; } satisfyOptions.forEach((stepCondition, index) => { if (index === 0) { currentObj = stepCondition } else { currentObj = comparedPriority(currentObj, stepCondition) } }) return currentObj } findPropertyPrice_ = ({ step_id, step_option_id, first_level_property_id, second_level_property_id }) => { let option = null if (step_id === this.lens_.id) { option = this.lens_.options.find(option => option.id === step_option_id) option.type = LENS_TYPE option.step_title = LENS_TYPE; } else { const currentStep = this.steps_.find(step => step.id === step_id) option = currentStep.step_options.find(option => option.id === step_option_id) option.type = NEW_STEP; option.step_title = currentStep.title; } let data = null data = this.lensUtilsApi_.filterObject_(option, ["id", "title", "price", "compare_at_price", "type", "step_title"]) if (first_level_property_id) { const text_property = option.properties.find(item => item.id === first_level_property_id) if(text_property.property_type === 'color') { data.text_property = {} data.text_property.title = '' data.text_property.color_property = this.lensUtilsApi_.filterObject_(text_property, ["id", "title", "price", "compare_at_price", "tips", "property_type"]) if(!data.text_property.price) { data.text_property.price = '0.00' } if(!data.text_property.compare_at_price) { data.text_property.compare_at_price = '0.00' } } else { data.text_property = this.lensUtilsApi_.filterObject_(text_property, ["id", "title", "price", "compare_at_price", "tips", "property_type"]) } if (second_level_property_id) { const color_property = text_property.sub_properties.find(item => item.id === second_level_property_id) if (color_property) { data.text_property.color_property = this.lensUtilsApi_.filterObject_(color_property, ["id", "title", "price", "compare_at_price", "tips", "property_type"]) } } } else { if (second_level_property_id) { const color_property = option.properties.find(item => item.id === second_level_property_id) if (color_property) { data.text_property = {} data.text_property.title = '' data.text_property.color_property = this.lensUtilsApi_.filterObject_(color_property, ["id", "title", "price", "compare_at_price", "tips", "property_type"]) if(!data.text_property.price) { data.text_property.price = '0.00' } if(!data.text_property.compare_at_price) { data.text_property.compare_at_price = '0.00' } } } } return data } clearPrescriptionFormData_ = (formData) => { if (this.stepsData_[1].form[SUBMIT_METHOD] == SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) { Object.keys(formData).forEach(key => { // 判断 this.prescriptionSettings_ 中存不存在 key 属性;若存在且formData[key]为空时formData[key]='None' if ((key.startsWith('prescription_od') || key.startsWith('prescription_os')) && this.prescriptionSettings_[key] && !formData[key]) { formData[key] = 'None' } }) } return formData } judgeIsOptionAndPropertiesChange_ = (preData, curData) => { if (preData.step_id === curData.step_id) { if (preData.step_option_id === curData.step_option_id) { if (preData.first_level_property_id === curData.first_level_property_id) { if (preData.second_level_property_id === curData.second_level_property_id) { return false } } } } return true } filterObject_(properties) { return properties.reduce((total, property) => ({ ...total, [property]: this[property] }), {}) } filterNonNullProperties_(data, properties) { return properties.reduce((filteredData, property) => { if (data[property]) { filteredData[property] = data[property]; } return filteredData; }, {}); } chooseUploadSubmit_ = async () => { let currentSubmitStepPrice = this.productInfo_.stepsPriceData.find(item => item.type === SUBMIT_METHOD) if (!currentSubmitStepPrice || (currentSubmitStepPrice && currentSubmitStepPrice[SUBMIT_METHOD] !== SUBMIT_METHOD_TYPE.UPLOAD)) { const currentStepOptionData = this.submit_methods_.options.find(it => it[SUBMIT_METHOD] === SUBMIT_METHOD_TYPE.UPLOAD) this.productInfo_.stepsPriceData.splice(1, 0, { "type": SUBMIT_METHOD, "title": currentStepOptionData.title, "index": 1 }) this.productInfo_.stepsPriceData.splice(2) await this.renderPrice_() } } // 更改数据类型的操作 changeStepType_ = (type) => { } setupAction_ = () => { this.registerAction('goBack', async (invocation) => { const prescription_type = this.stepsData_[0].form[PRESCRIPTION] const submit_method = this.stepsData_[1].form[SUBMIT_METHOD] if (this.stepsIndex_.currentIndex === 2) { if (prescription_type === PRESCRIPTION_TYPE.READING) { const reading_type = this.stepsData_[0].form.reading_type if (reading_type && reading_type === READING_TYPE.READING_NO_LENS) { this.goManyPreIndex_(-2) this.showIndex_({ index: this.stepsIndex_.currentIndex }) return } } if (this.juadgeIsOnlyEnterPrescriptionManually()) { this.goManyPreIndex_(-2) this.showIndex_({ index: this.stepsIndex_.currentIndex }) return } } else if (this.stepsIndex_.currentIndex === 3) { if (prescription_type === PRESCRIPTION_TYPE.NON_PRESCRIPTION) { this.goManyPreIndex_(-3) this.showIndex_({ index: this.stepsIndex_.currentIndex }) return } if ([SUBMIT_METHOD_TYPE.UPLOAD, SUBMIT_METHOD_TYPE.EMAIL_LATER].includes(submit_method)) { this.goManyPreIndex_(-2) this.showIndex_({ index: this.stepsIndex_.currentIndex }) return } } else if (this.stepsIndex_.currentIndex > 4) { if (this.stepsData_.length === 5) { this.goPreIndex_() this.showIndex_({ index: this.stepsIndex_.currentIndex }) } else if (this.stepsData_.length > 5) { const backStepData = this.stepsData_[this.stepsIndex_.currentIndex - 1] const { form: formData, new_step_id: stepId } = backStepData const stepData = this.steps_.find(step => step.id === stepId) await this.renderNewStep_(stepData, formData) this.goPreIndex_() this.showIndex_({ index: this.stepsIndex_.currentIndex }) } return } else { this.goPreIndex_() this.showIndex_({ index: this.stepsIndex_.currentIndex }) return } this.goPreIndex_() this.showIndex_({ index: this.stepsIndex_.currentIndex }) }); this.registerAction('goNext', (invocation) => { this.goPreIndex_() alert('goNext') // this.showIndex_({index: this.stepsIndex_.currentIndex }) }); this.registerAction('addToCart', (invocation) => { alert('addToCart') }); this.registerAction('renderPrice', async (invocation) => { await this.renderPrice_() }); this.registerAction('initGlasses', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; this.init_(data); }); this.registerAction('clearAllStatus', async (invocation) => { allStepComponentIds.forEach(id => { const ele_ = document.getElementById(id) if (ele_) { ele_.style.display = 'none' } }) await this.clearAllStatus_() if (this.atcType_ !== 'add_lens') { this.initProductInfo_.image = {width: 0, height: 0, src: '',path:'', alt: ''} this.initProductInfo_.title = '' this.initProductInfo_.selected_variant = {compare_at_price: 0, image: '', option1: '',option2:'', price : 0} this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); } }); this.registerAction('handleMeasurePdEvent',(invocation) => { let trackReportData = { ...IncidentReportingData, event_desc: "measure_pd", event_type: "popup_click", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", action_type: "measure_pd", process_id: this.process_data_.lens_process_id, element_type: "text_url_button", element_name: "measure_my_pd" }) } window.sa && window.sa.track("function_click", trackReportData); }); this.registerAction('editStep', async (invocation) => { const { type, step_id } = invocation.args let isNewStep = ![PRESCRIPTION, SUBMIT_METHOD, SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, LENS_TYPE].includes(type); let isStepChange = isNewStep ? step_id !== this.currentStepId_ : type !== this.currentStepType_; if (!isStepChange) { return } let editStepData = isNewStep ? this.stepsData_.find(step => step.new_step_id === step_id) : this.stepsData_.find(step => step.type === type); let stepFormData = editStepData.form if(![PRESCRIPTION, SUBMIT_METHOD, SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, LENS_TYPE].includes(type)) { // 新步骤肯定是要重新render + 数据回填 const toStep = this.steps_.find(step => step.id === step_id); await this.renderNewStep_(toStep, stepFormData) } this.goToIndex_(editStepData.index) await this.showIndex_({ index: this.stepsIndex_.currentIndex }) }); this.registerAction('resizeApp', () => { this.appHeight(); }) } calcStepPrice_ = (formData, options, step_title, type) => { const option = options.find(option => option.id === formData.step_option_id); const option_obj = { step_id: formData.step_id, id: option.id, title: option.title, price: option.price, compare_at_price: option.compare_at_price, type, step_title } if (formData.first_level_property_id) { const text_property = option.properties.find(property => property.id === formData.first_level_property_id) if (text_property.property_type === 'color') { option_obj.text_property = {} option_obj.text_property.title = '' option_obj.text_property.color_property = { id: text_property.id, price: text_property.price, compare_at_price: text_property.compare_at_price, title: text_property.title, tips: text_property.tips, property_type: text_property.property_type, } if(!option_obj.text_property.price) { option_obj.text_property.price = '0.00' } if(!option_obj.text_property.compare_at_price) { option_obj.text_property.compare_at_price = '0.00' } } else { option_obj.text_property = { id: text_property.id, price: text_property.price, compare_at_price: text_property.compare_at_price, title: text_property.title, tips: text_property.tips, property_type: text_property.property_type, } } if (formData.second_level_property_id) { const color_property = text_property.sub_properties.find(property => property.id === formData.second_level_property_id); option_obj.text_property.color_property = { id: color_property.id, price: color_property.price, compare_at_price: color_property.compare_at_price, title: color_property.title, tips: color_property.tips, property_type: color_property.property_type, } } } if (!formData.first_level_property_id && formData.second_level_property_id) { const color_property = option.properties.find(property => property.id === formData.second_level_property_id); if (color_property) { option_obj.text_property = {} option_obj.text_property.title = '' option_obj.text_property.color_property = { id: color_property.id, price: color_property.price, compare_at_price: color_property.compare_at_price, title: color_property.title, tips: color_property.tips, property_type: color_property.property_type, } if(!option_obj.text_property.price) { option_obj.text_property.price = '0.00' } if(!option_obj.text_property.compare_at_price) { option_obj.text_property.compare_at_price = '0.00' } } } return option_obj } renderPrice_ = async () => { const backBtnDom = document.getElementById('frame-and-price') const backBtnApi = await SPZ.whenApiDefined(backBtnDom) await backBtnApi.doRender_({ ...this.productInfo_, product: this.initProductInfo_ }); const backBtnDomMd = document.getElementById('frame-and-price-md') const backBtnApiMd = await SPZ.whenApiDefined(backBtnDomMd) await backBtnApiMd.doRender_({ ...this.productInfo_, product: this.initProductInfo_ }); this.triggerEvent_('mdTotalPriceChange', { ...this.productInfo_, product: this.initProductInfo_ }) } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } appHeight() { if(window.innerWidth < 960){ const doc = document.documentElement; doc.style.setProperty('--app-height', `${window.innerHeight}px`); } }; resizeHeight(){ if(window.innerWidth < 960){ window.addEventListener('resize', this.appHeight); } this.appHeight(); } } SPZ.defineElement(TAG, LensProcess) (function () { const TAG = 'spz-custom-contact-process'; class ContactProcess extends SPZ.BaseElement { constructor(element) { super(element); this.product_id = '' this.process_data_ = {} this.prescriptionSettings_ = {} this.globalSettings_ = null this.stepsIndex_ = { currentIndex: 0, fromIndex: 0, preIndex: 0, } this.isHasNewStep_ = false this.submitBtnShow_ = { show: true, disabled: false, type: "continue" } this.stepsData_ = [ { type: PRESCRIPTION, // 处方类型 form: { [PRESCRIPTION]: 'Contact', }, id: "contact-prescription-type-step", index: 0, }, { type: SUBMIT_METHOD, // 提交方式 form: {}, id: "contact-submit-method-step", index: 1, }, { type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, // 处方填写 or 老花处方 form: {}, id: "contact-lenses-prescription-submit-step", index: 2, } ] this.initProductInfo_ = {} this.productInfo_ = { product: {}, stepsPriceData: [{ "type": PRESCRIPTION, "title": 'Contact', "index": 0, }], } this.quantity_ = 1 this.copyProperties_ = {} this.copyLensProcessData_ = {} this.totalQty_ = 1; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); this.resizeHeight(); } clearAllStatus_ = async () => { this.stepsIndex_ = { currentIndex: 0, fromIndex: 0, preIndex: 0, } this.stepsData_ = [ { type: PRESCRIPTION, // 处方类型 form: { [PRESCRIPTION]: 'Contact', }, id: "contact-prescription-type-step", index: 0, }, { type: SUBMIT_METHOD, // 提交方式 form: {}, id: "contact-submit-method-step", index: 1, }, { type: SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY, // 处方填写 or 老花处方 form: {}, id: "contact-lenses-prescription-submit-step", index: 2, } ] this.productInfo_.stepsPriceData = [] await this.clearRenderSteps_(); await this.renderPrice_(); let trackReportData = { ...IncidentReportingData, event_type: "popup_click", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", action_type: "sidebar_close", process_id: this.process_data_.lens_process_id, process_type: "contact_lens", element_type: "lens_process_sidebar", element_name: "close" }) } window.sa && window.sa.track("function_click", trackReportData); } clearRenderSteps_ = async() => { await this.renderStep2_(this.submit_methods_, {[SUBMIT_METHOD]: null} ); await this.renderStep2_1( { type: null, [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, }, {}); } mountCallback = async() => { this.triggerEvent_('mount'); this.lensUtilsApi_ = await SPZ.whenApiDefined(document.querySelector('#spz_custom_lens_util')); } changeQty_ = ({od_qty = 0, os_qty = 0}) => { this.totalQty_ = Number(od_qty || 0) + Number(os_qty || 0); this.quantity_ = this.totalQty_; this.initProductInfo_.selected_variant.process_qty = this.totalQty_; this.renderPrice_(); } submitStep_ = async ({ type, id, formData, cb = function () {}, ...attrs}) => { if (!formData) { const currentFormDom = document.querySelector(`#${id} form`); formData = this.lensUtilsApi_.getFormData_(new FormData(currentFormDom)); } let currentStepData = this.stepsData_.find(item => { return item.type === type && item.id === id && (type !== NEW_STEP || item.new_step_id === formData.step_id); }); let index = currentStepData.index; if (type === SUBMIT_METHOD) { await this.processSubmitMethodType_(index, currentStepData, formData, attrs); } else if (type === SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) { await this.processEnterPrescriptionManuallyType_(currentStepData, formData, attrs); } await this.renderPrice_(); } getTypeAndPrescription_ = () => { let prescription = this.stepsData_[0].form[PRESCRIPTION]; return {[PRESCRIPTION]: prescription }; } processEnterPrescriptionManuallyType_ = async (currentStepData, formData, attrs) => { const { is_save_prescription, prescription_id, metafield_id, prescription_image } = formData this.is_save_prescription = is_save_prescription || false this.prescription_id = prescription_id this.metafield_id = metafield_id this.prescription_image = prescription_image this.clearQtyAndRelatedParams(formData) currentStepData.form = {...currentStepData.form, ...formData}; await this.addToCart_(); } clearQtyAndRelatedParams = (form) => { this.clearParamsIfQtyZero('od_qty', 'prescription_od', form) this.clearParamsIfQtyZero('os_qty', 'prescription_os', form) } clearParamsIfQtyZero = (qtyKey, prefix, form) => { if (form[qtyKey] == 0) { Object.keys(form).forEach(key => { if (key.startsWith(prefix)) { form[key] = '' } }) } } resolvePrescriptionFormData_ = (formData) => { if (this.stepsData_[1].form[SUBMIT_METHOD] == SUBMIT_METHOD_TYPE.ENTER_PRESCRIPTION_MANUALLY) { Object.keys(formData).forEach(key => { // 判断 this.prescriptionSettings_ 中存不存在 key 属性;若存在且formData[key]为空时formData[key]='None' if ((key.startsWith('prescription_od') || key.startsWith('prescription_os')) && this.prescriptionSettings_[key] && !formData[key]) { formData[key] = 'None' } }) } return formData } processSubmitMethodType_ = async (index, currentStepData, formData, attrs) => { this.productInfo_.stepsPriceData.splice(2); const isChange = this.stepsData_[1].form[SUBMIT_METHOD] !== formData[SUBMIT_METHOD]; const currentStepOptionData = this.submit_methods_.options.find(submit_step => submit_step[SUBMIT_METHOD] === formData[SUBMIT_METHOD]); if (isChange) { currentStepData.form = formData; this.updateStepPrice(SUBMIT_METHOD, currentStepOptionData); this.renderStep2_1( { type: currentStepData.form[SUBMIT_METHOD], [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...attrs, ...currentStepOptionData }); } } trackAddToCart = () => { const params = { product_id: this.initProductInfo_?.id, variant_id: this.variant_id, quantity: Number(this.quantity_), number: Number(this.quantity_), name: this.initProductInfo_?.title, item_price: this.initProductInfo_?.price, source: "add_to_cart", _extra: { ...IncidentReportingData, event_type: "click", event_developer: "jozy", event_info: JSON.stringify({ action_type: "contact_lens_add_to_cart", product_id: this.product_id, process_id: this.process_data_.lens_process_id, process_type: "contact_lens", element_type: "button", element_name: "add_to_cart_btn" }) }, } const event = new CustomEvent('dj.addToCart', { detail: params || null, bubbles: true }); document.body && document.body.dispatchEvent(event); } addToCart_ = async () => { //加购埋点 this.trackAddToCart(); // 加购 // 将 stepsData_ 的数据变成加购接口请求的数据 const stepData = this.stepsData_.reduce((total, cur) => { if (cur.type === 'prescription_type') { const currentPrescription = this.prescription_types_.options.find(item => item.prescription_type === cur.form.prescription_type) total.steps = total.steps.concat({ step_id: this.prescription_types_.id, step_option_id: currentPrescription.id }) total.prescription_type = currentPrescription[PRESCRIPTION] } else if (cur.type === 'submit_method') { let submit_method_option = this.submit_methods_.options.find(item => item.submit_method === cur.form.submit_method) total.steps = total.steps.concat({ step_id: this.submit_methods_.id, step_option_id: submit_method_option.id, }) total.submit_method = submit_method_option[SUBMIT_METHOD] } else if (cur.type === 'Enter prescription manually') { // 处方提交填写的值是平铺到请求体中 total = { ...total, ...cur.form } } return total }, { steps: [] }) stepData.steps = stepData.steps.filter(step => step.step_id && step.step_option_id) let that = this const reqBody = { product_id: this.product_id, variant_id: this.variant_id, quantity: Number(this.quantity_), is_save_prescription: this.is_save_prescription, prescription_id: this.prescription_id, metafield_id: this.metafield_id, properties: { currency: window.SHOPLAZZA.currency_code, prescription_image: this.prescription_image, lens_processing_id: that.process_data_.lens_process_id, ...stepData, } } if(this.atc_loading_) return; this.atc_loading_ = true; this.element.setAttribute('atc-loading', ''); try { let requestMethod = 'post' if (this.atcType_ === 'edit_lens') { requestMethod = 'put' reqBody.line_item_id = this.line_item_id } else { delete reqBody.line_item_id } const data = await this.xhr_.fetchJson('/api/fireant/customize_cart', { method: requestMethod, body: reqBody }); if(data.state === 'success') { window.location.href = "/cart" } else { this.atc_loading_ = false; this.element.removeAttribute('atc-loading'); } // 跳转到购物车 // 加购成功,关闭弹窗 // this.triggerEvent_('atcSuccess') } catch (e) { this.atc_loading_ = false; this.element.removeAttribute('atc-loading'); e.then(data => { const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { api.showToast(data.message || (data.errors && data.errors[0]) || 'Unknown error'); }); }) } } init_ = async (data, params="") => { console.log('The lens process is initializing.......') let { atcType, product } = data this.atcType_ = atcType this.initProductInfo_ = { ...this.initProductInfo_, ...product, }; if (atcType === 'add_lens') { await this.initAddLensProcess(product, params); } else { await this.initEditLensProcess(product); } } initAddLensProcess = async (product, params) => { this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); this.product_id = product.id; this.variant_id = product.selected_variant?.id; const processData = await this.getLensProcessData(this.product_id, params); this.process_data_ = processData; this.lensUtilsApi_.openSidebar('lens-process-sidebar'); this.setupProcessData_(this.process_data_); this.globalSettings_ = await this.lensUtilsApi_.getGlobalSettings_(this.product_id, 'contact_lens_setting'); window.lens_process.settings = this.globalSettings_; const prescriptionSettings = await this.filterNonNullProperties_(await this.getPrescriptionSelects_(this.globalSettings_), ['birth_year']) this.prescriptionSettings_ = this.resolveContactPrescriptionSelects_(prescriptionSettings); await this.setDefaultSubmitMethodStep_(); this.calcStepsPrice_(); this.changeQty_({os_qty: this.prescriptionSettings_.os_qty.selected, od_qty: this.prescriptionSettings_.od_qty.selected}); this.renderPrice_(); await this.lensUtilsApi_.setGlobalPrimaryColor(this.globalSettings_, 'contact'); let trackReportData = { ...IncidentReportingData, event_type: "popup_expose", event_desc: "sidebar_open", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", product_id: this.product_id, selected_variant_id: this.variant_id, process_id: this.process_data_.lens_process_id, process_type: "contact_lens", last_action_type: "click_continue_btn", last_element_type: "button", last_element_name: "continue" }) } this.lensUtilsApi_.incidentReportingFn("#lens-process-sidebar",trackReportData,"function_expose") } initEditLensProcess = async (product) => { this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); const { properties, product_id, variant_id, item_id } = product this.lensUtilsApi_.setupVariantOptions_.call(this, product); this.quantity_ = product.quantity product.framePrice = product.trunk_price if (product_id === this.product_id && this.line_item_id === item_id) { this.productInfo_ = { product: {}, stepsPriceData: [] } } else { this.productInfo_ = { product: {}, stepsPriceData: [] } } this.line_item_id = item_id this.product_id = product_id this.variant_id = variant_id this.lensUtilsApi_.openSidebar('lens-process-sidebar'); const data = this.lensUtilsApi_.resolveProperties_(JSON.parse((`${properties}`).replace(/&(quot);/ig,'"'))) try { const process_data_ = await this.getLensProcessData(this.product_id) this.renderEditComponent_(product, data, process_data_) } catch(err) { this.handleError_(err); } } setDefaultSubmitMethodStep_ = async(prescription_submit_form_data) => { let currentStepOptionData = this.submit_methods_.options[0] this.stepsData_[1].form = { submit_method: currentStepOptionData[SUBMIT_METHOD] } await this.renderStep2_(this.submit_methods_, this.stepsData_[1].form ); await this.renderStep2_1( { type: currentStepOptionData[SUBMIT_METHOD], [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...currentStepOptionData }, prescription_submit_form_data); } calcStepsPrice_ = () => { const prescriptionOptionData = this.prescription_types_.options.find(item => item[PRESCRIPTION] === 'Contact') this.productInfo_.stepsPriceData[0] = { "type": PRESCRIPTION, "title": 'Contact', "index": 0, ...prescriptionOptionData } const submitStepOptionData = this.submit_methods_.options.find(item => item[SUBMIT_METHOD] === this.stepsData_[1].form[SUBMIT_METHOD]) this.productInfo_.stepsPriceData[1] = { "type": SUBMIT_METHOD, "title": submitStepOptionData.title, "index": 1, ...submitStepOptionData } } hexToRGBA_ = (hex, alpha=0.2) => { hex = hex.replace("#", ""); var r = parseInt(hex.substring(0, 2), 16); var g = parseInt(hex.substring(2, 4), 16); var b = parseInt(hex.substring(4, 6), 16); return "rgba(" + r + ", " + g + ", " + b + "," + alpha + ")"; } getLensProcessData(id, params='') { return this.xhr_.fetchJson(`/api/fireant/product/${ id }/steps${params}`, { method: "get", }); } updateStepPrice = (stepType, updatedData) => { const stepPriceIndex = this.productInfo_.stepsPriceData.findIndex(step => step.type === stepType); if (stepPriceIndex !== -1) { this.productInfo_.stepsPriceData[stepPriceIndex] = { ...this.productInfo_.stepsPriceData[stepPriceIndex], ...updatedData }; } else { this.productInfo_.stepsPriceData.push(updatedData); } } // Show a specific index showIndex_ = async ({ index }) => { // Limit the index to 4 index = Math.min(index, 4); const { id: currentId } = this.stepsData_[index] || {}; // Hide all steps and show the current one allStepComponentIds.forEach(id => { const element = document.getElementById(id); if (element) { element.style.display = (currentId === id) ? 'block' : 'none'; } }); } renderStep_ = async (stepId, data, formData) => { const tempElement = document.getElementById(stepId); const api = await SPZ.whenApiDefined(tempElement); await api.doRender_(data, formData); } renderStep1_ = async (data, formData) => { await this.renderStep_("contact-prescription-type-step", data, formData); } renderStep2_ = async (data, formData) => { await this.renderStep_("contact-submit-method-step", data, formData); } renderStep2_1 = async (data, formData) => { data = {lens_process_id: this.process_data_.lens_process_id, ...data} await this.renderStep_("contact-lenses-prescription-submit-step", data, formData); } filterNonNullProperties_(data, properties) { return properties.reduce((filteredData, property) => { if (data[property]) { filteredData[property] = data[property]; } return filteredData; }, {}); } fnGetSphRangesData = (sphRanges) => { let rangesRes = sphRanges.map((sphRange) => { const cur = this.generateOptions(Number(sphRange.min), Number(sphRange.max), Number(sphRange.interval) || 1, {type: 'decimal', has_symbol: true},null); return cur.options; }); rangesRes = rangesRes.flat(); rangesRes.sort((a, b) => a - b); let uniqueArray = [...new Set(rangesRes)]; return uniqueArray; } resolveContactPrescriptionSelects_ = (prescriptionSettings) => { const prescriptionOptionData = this.prescription_types_.options.find(item => item[PRESCRIPTION] === 'Contact') const configColumn = JSON.parse(prescriptionOptionData.config_column.replace(/&(quot);/ig,'"')); const splitStr = (str) => { return str.split(/[\u002C\uFF0C]/).map(item => item.trim()).filter(Boolean); } let bc_ranges_options = configColumn.bc_range.is_display ? splitStr(configColumn.bc_range.value) : null; let dia_ranges_options = configColumn.dia_range.is_display ? splitStr(configColumn.dia_range.value) : null; let qty_ranges_options = this.generateOptions(Number(configColumn.qty_range.min), Number(configColumn.qty_range.max), 1, null, configColumn.qty_range.default); let selectSettings = { "od_qty": qty_ranges_options, "os_qty": qty_ranges_options, } if (bc_ranges_options) { selectSettings = { ...selectSettings, "prescription_od_bc":{"options": bc_ranges_options}, "prescription_os_bc":{"options": bc_ranges_options}, } } if (dia_ranges_options) { selectSettings = { ...selectSettings, "prescription_od_dia":{"options": dia_ranges_options}, "prescription_os_dia":{"options": dia_ranges_options}, } } let keys = ["sph", "cyl", "axis", "add"]; const sides = ["od", "os"]; if(configColumn.add_range.is_display && configColumn.add_range.enable_value_option) { keys.splice(3,1); let add_ranges_options = splitStr(configColumn.add_range.value); selectSettings = { ...selectSettings, "prescription_od_add":{"options": add_ranges_options}, "prescription_os_add":{"options": add_ranges_options}, } } let sphRanges = configColumn.sph_range.ranges; if(configColumn.sph_range.is_display && sphRanges){ let sph_ranges_options = this.fnGetSphRangesData(sphRanges); keys.splice(0,1); selectSettings = { ...selectSettings, "prescription_od_sph":{"options": sph_ranges_options}, "prescription_os_sph":{"options": sph_ranges_options}, } } keys.forEach((key) => { sides.forEach((side) => { if (configColumn[`${key}_range`]?.is_display) { if(key.includes("axis")){ selectSettings[`prescription_${side}_${key}`] = this.generateOptions(Number(configColumn[`${key}_range`].min), Number(configColumn[`${key}_range`].max), Number(configColumn[`${key}_range`]?.interval) || 1); }else{ selectSettings[`prescription_${side}_${key}`] = this.generateOptions(Number(configColumn[`${key}_range`].min), Number(configColumn[`${key}_range`].max), Number(configColumn[`${key}_range`]?.interval) || 1, {type: 'decimal', has_symbol: true},null); } } }); }); return { ...selectSettings, ...prescriptionSettings, } } generateOptions = (min, max, step, additionalConfig = {}, defaultValue) => { return { options: window.lens_process.getOptions(min, max, step, additionalConfig), selected: defaultValue, }; }; getPrescriptionSelects_ = async (data) => { const tempElement = document.getElementById('spz-custom-lamb-locale-script'); const api = await SPZ.whenApiDefined(tempElement); let lacale_value = await api.getLambLocale(); lacale_value = lacale_value.contact_lens_map; const current_year = new Date().getFullYear(); const generatePlaceholder = (value) => { return { placeholder: { value: lacale_value.get(value), disabled: true, }, disabled: true }; }; const prescription_selects = { birth_year: this.generateOptions(1913, current_year, 1), }; window.lens_process.locale = { "select": lacale_value.get("prescription.default.select"), "please_enter_name": lacale_value.get("prescription.default.please_enter_name") }; return { birth_year: data.birth_config != 'no' ? prescription_selects.birth_year : '', }; } setupProcessData_ = ({ prescription_types, lens, submit_methods, steps, contact_lens }) => { this.isHasNewStep_ = !!steps.length; this.prescription_types_ = this.lensUtilsApi_.resolvePrescriptionTypes_(prescription_types); this.submit_methods_ = submit_methods; this.lens_ = lens; this.steps_ = steps; } renderStepsFormEdit_ = async(data) => { const {od_qty, os_qty, prescription_type, submit_method, steps: stepsList, lens_process_id, ...prescription_submit_form_data } = data let submitMethodOptionData = {}, prescriptionFormData = {} let lensTypeOptionData = null try { this.calcPrescriptionDataFromEdit_(prescription_type, submit_method); submitMethodOptionData = this.calcSubmitMethodDataFromEdit_(submit_method, prescription_submit_form_data); prescriptionFormData = this.calcEnterPrescriptionManuallyDataFromEdit_({submitMethodOptionData, prescription_submit_form_data}); } catch(error) { console.log('error', error); this.showErrorMessage_(error.message); } finally { if (submitMethodOptionData && submitMethodOptionData[SUBMIT_METHOD]) { await this.renderStep2_(this.submit_methods_, this.stepsData_[1].form); await this.renderStep2_1({ type: this.stepsData_[1].form[SUBMIT_METHOD], [PRESCRIPTION]: this.stepsData_[0].form[PRESCRIPTION], ...prescriptionFormData, selectSettings: this.prescriptionSettings_, globalSettings: this.globalSettings_, ...submitMethodOptionData, }, {...prescription_submit_form_data, od_qty, os_qty}); } else { this.setDefaultSubmitMethodStep_({...prescription_submit_form_data, od_qty, os_qty}); this.calcStepsPrice_(); } this.changeQty_({os_qty, od_qty}); } } calcEnterPrescriptionManuallyDataFromEdit_ = ({submitMethodOptionData, prescription_submit_form_data}) => { this.stepsData_[2].form = prescription_submit_form_data; return this.getTypeAndPrescription_(); } calcPrescriptionDataFromEdit_ = (prescription_type, submit_method) => { const setStepData_ = (prescription_type, prescriptionOptionData, reading_type = null) => { this.stepsData_[0].form = { prescription_type, reading_type }; this.productInfo_.stepsPriceData[0] = { "type": PRESCRIPTION, "title": prescription_type, "index": 0, ...prescriptionOptionData } } let reading_type = null; if (prescription_type === PRESCRIPTION_TYPE.READING) { reading_type = submit_method ? READING_TYPE.READING_LENS : READING_TYPE.READING_NO_LENS; } const prescriptionOptionData = this.prescription_types_.options.find(item => item[PRESCRIPTION] === prescription_type); if (!prescriptionOptionData || (reading_type && !this.judgeIsIncludeReadingType_(reading_type))) { setStepData_(prescription_type, prescriptionOptionData); throw new Error("您之前选择的处方类型现已不可用,我们对此造成的不便深感抱歉。请您重新选择一种提交方式。"); } setStepData_(prescription_type, prescriptionOptionData, reading_type); } calcSubmitMethodDataFromEdit_ = (submit_method, prescription_submit_form_data) => { if (!submit_method) return; let submitMethodOptionData = this.submit_methods_.options.find(item => item[SUBMIT_METHOD] === submit_method); if (!submitMethodOptionData) { throw new Error("您之前选择的提交方式现已不可用,我们对此造成的不便深感抱歉。请您重新选择一种提交方式。"); } this.stepsData_[1].form = { submit_method }; if (submit_method) { submitMethodOptionData = { ...submitMethodOptionData, price: prescription_submit_form_data.is_prism ? submitMethodOptionData.price : 0 }; this.productInfo_.stepsPriceData[1] = { "type": SUBMIT_METHOD, "title": submit_method, "index": 1, ...submitMethodOptionData }; } this.prescription_image = prescription_submit_form_data.prescription_image || ''; return submitMethodOptionData; } showErrorMessage_ = (message) => { const toast = SPZCore.Dom.scopedQuerySelector(document, `#error-toast`); toast && SPZ.whenApiDefined(toast).then((api) => { api.showToast('The information has been updated, the option you originally selected no longer exists, please select again.'); }); } renderEditComponent_ = async (product, data, process_data) => { await this.setupProperties_(product, data, process_data); await this.renderStepsFormEdit_(data, process_data); this.setupProductInfo_(product); await this.renderPrice_(); this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); let trackReportData = { ...IncidentReportingData, event_type: "popup_expose", event_desc: "edit_prescription_sidebar_open", event_developer: "jozy", event_info: JSON.stringify({ popup_name: "popup_prescription_lens", product_id: product?.product_id, selected_variant_id: product?.variant_id, process_id: process_data.lens_process_id, process_type: "contact_lens", last_action_type: "click_edit_prescription_btn", last_element_type: "button", last_element_name: "edit_prescription" }) } this.lensUtilsApi_.incidentReportingFn("#lens-process-sidebar",trackReportData,"function_expose") } setupProperties_ = async (product, data, process_data) => { this.product = product; const { prescription_type, submit_method, steps: stepsList, lens_process_id, ...prescription_submit_form_data } = data; this.prescription_image = prescription_submit_form_data.prescription_image || ''; this.setupStepsData_(prescription_type, submit_method, prescription_submit_form_data); this.process_data_ = process_data; this.setupProcessData_(this.process_data_); this.globalSettings_ = await this.lensUtilsApi_.getGlobalSettings_(this.product_id, 'contact_lens_setting'); window.lens_process.settings = this.globalSettings_; const prescriptionSettings = await this.filterNonNullProperties_(await this.getPrescriptionSelects_(this.globalSettings_), ['birth_year']) this.prescriptionSettings_ = this.resolveContactPrescriptionSelects_(prescriptionSettings); await this.lensUtilsApi_.setGlobalPrimaryColor(this.globalSettings_, 'contact'); } setupStepsData_ = (prescription_type, submit_method, prescription_submit_form_data) => { this.stepsData_[0].form = { prescription_type }; if (prescription_type === PRESCRIPTION_TYPE.READING) { this.stepsData_[0].form = { prescription_type, ['reading_type']: submit_method ? READING_TYPE.READING_LENS : READING_TYPE.READING_NO_LENS }; } this.stepsData_[1].form = { submit_method }; this.stepsData_[2].form = prescription_submit_form_data; } setupProductInfo_ = (product) => { this.initProductInfo_ = { ...this.initProductInfo_, title: product.product_title, id: this.product_id, image: product.image, }; } renderPrice_ = async () => { const backBtnDom = document.getElementById('frame-and-price') const backBtnApi = await SPZ.whenApiDefined(backBtnDom) await backBtnApi.doRender_({ ...this.productInfo_, product: this.initProductInfo_ }); const backBtnDomMd = document.getElementById('frame-and-price-md') const backBtnApiMd = await SPZ.whenApiDefined(backBtnDomMd) await backBtnApiMd.doRender_({ ...this.productInfo_, product: this.initProductInfo_ }); this.triggerEvent_('mdTotalPriceChange', { ...this.productInfo_, product: this.initProductInfo_ }) } setupAction_ = () => { this.registerAction('renderPrice', async (invocation) => { await this.renderPrice_() }); this.registerAction('initGlasses', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; this.init_(data); }); this.registerAction('clearAllStatus', async (invocation) => { await this.clearAllStatus_() if (this.atcType_ !== 'add_lens') { this.lensUtilsApi_.clearInitProductInfo_.call(this); this.triggerEvent_('initGlasses', { ...this.productInfo_, product: this.initProductInfo_ }); } }); this.registerAction('resizeApp', () => { this.appHeight(); }) } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } appHeight() { if(window.innerWidth < 960){ const doc = document.documentElement; doc.style.setProperty('--app-height', `${window.innerHeight}px`); } }; resizeHeight(){ if(window.innerWidth < 960){ window.addEventListener('resize', this.appHeight); } this.appHeight(); } } SPZ.defineElement(TAG, ContactProcess) })(); (function(){ const TAG = 'spz-custom-lens-util'; const DEFAULT_DELAY_TIME = 100; class SpzCustomLensUtil extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = SPZServices.templatesForDoc(); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } static deferredMount() { return false; } mountCallback() { } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } debounceRender(el, thisEl, containerStr) { return this.smoothRender_(el, thisEl, containerStr).then(() => this.attemptToFit_(thisEl)); } smoothRender_(newEl, thisEl, containerStr) { const that = this; that.appendAsUnvisibleContainer_(newEl, thisEl); const components = newEl.querySelectorAll('[layout]'); return Promise.race([ Promise.all( Array.prototype.map.call(components, (e) => SPZ.whenDefined(e).then(() => e.whenBuilt()) ) ), SPZServices.timerFor(that.win).promise(DEFAULT_DELAY_TIME), ]).then(() => { return containerStr !== 'form_' ? thisEl.mutateElement(() => that.quickReplace(thisEl, newEl)) : thisEl.mutateElement(() => that.quickReplaceForm(thisEl, newEl)); }); } quickReplace(thisEl, newEl) { thisEl.container_ && this.toggleVisible_(thisEl.container_); this.toggleVisible_(newEl, true); thisEl.container_ && SPZCore.Dom.removeElement(thisEl.container_); thisEl.container_ = newEl; }; quickReplaceForm(thisEl, newEl) { thisEl.form_ && this.toggleVisible_(thisEl.form_); this.toggleVisible_(newEl, true); const children = thisEl.form_.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.toggleVisible_(thisEl.form_, true); thisEl.form_.appendChild(newEl); }; appendAsUnvisibleContainer_(el, thisEl) { this.toggleVisible_(el); thisEl.element.appendChild(el); } attemptToFit_(thisEl) { const fitFunc = () => { thisEl.mutateElement(this.setElementHeight_.bind(thisEl)); }; const container = thisEl.container_ || thisEl.form_; if (container) { const children = container.querySelectorAll('*:not(template)'); const spzChildren = Array.prototype.filter .call(children, SPZUtils.isSpzElement) .filter((e) => !(e.isMount && e.isMount())); spzChildren .map((e) => SPZ.whenDefined(e).then(() => e.whenMounted())) .forEach((p) => p.then(() => fitFunc())); } return fitFunc(); } setElementHeight_() { const targetHeight = (this.container_ || this.form_)?./*OK*/ scrollHeight; const height = this.element./*OK*/ offsetHeight; if (height !== targetHeight) { SPZCore.Dom.setStyles(this.element, { height: `${targetHeight}px`, }); } } toggleVisible_(el, visible = false) { if (!visible) { el.classList.add('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': -100000, 'opacity': 0, }); } else { el.classList.remove('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': 'auto', 'opacity': 1, }); } } setMinWidth_() { const targetWidth = this.container_?./*OK*/ scrollWidth; const width = this.element./*OK*/ offsetWidth; if (width !== targetWidth) { SPZCore.Dom.setStyles(this.element, { 'min-width': `${targetWidth}px`, }); } } showNextOrAtcBtn_ = async (ele, data, init = false, index, type) => { const tempElement = document.getElementById('glasses_custom_atc'); const api = await SPZ.whenApiDefined(tempElement); const res = await api.findNextStep_(data); if(!init) { if(type === 'text' && !data.second_level_property_id) { return; } await api.findStepPrice_(data); } if(ele.bottomBtn_ && !ele.bottomBtn_.classList.contains('!hidden')) { ele.element.removeAttribute('show-btn'); ele.bottomBtn_.classList.toggle('!hidden', true); } if(ele.option_.properties && ele.option_.properties.length>=1) { ele.submit_ && ele.submit_.classList.toggle('hidden', true); ele.atc_submit_ && ele.atc_submit_.classList.toggle('hidden', true); if(!ele.form_data_.first_level_property_id && (!ele.option_.properties.filter(op => op.id===ele.form_data_.second_level_property_id)||!ele.form_data_.second_level_property_id)) { api.handleLensProcessShowBtnAttr(index); return }; for(let i = 0;i < ele.option_.properties.length;i ++) { if(ele.form_data_.first_level_property_id === ele.option_.properties[i].id) { if(ele.option_.properties[i].sub_properties && !ele.form_data_.second_level_property_id) { api.handleLensProcessShowBtnAttr(index); return }; } } } if(ele.bottomBtn_ && ele.bottomBtn_.classList.contains('!hidden')) { ele.element.setAttribute('show-btn', ''); ele.bottomBtn_.classList.toggle('!hidden', false); } if(!!res) { if(!ele.option_.properties?.length) { ele.element.removeAttribute('show-btn'); ele.bottomBtn_.classList.toggle('!hidden', true); if(!init) { ele.handleSubmit_(); api.handleLensProcessShowBtnAttr(index); return; } } ele.submit_ && ele.submit_.classList.toggle('hidden', false); ele.atc_submit_ && ele.atc_submit_.classList.toggle('hidden', true); } else { ele.atc_submit_ && ele.atc_submit_.classList.toggle('hidden', false); ele.submit_ && ele.submit_.classList.toggle('hidden', true); } api.handleLensProcessShowBtnAttr(index); } calculatePrice = (option, price, first_property_id = "") => { const calculateOptionPrice = (property, type = 'text') => { const basePrice = parseFloat(option[price]) + parseFloat(property[price]); if (property.sub_properties) { return property.sub_properties.map( (sub_property) => basePrice + parseFloat(sub_property[price]) ); } else { return type === 'color' ? basePrice : [basePrice]; } }; if (option.properties && option.properties.length > 0) { if (first_property_id) { const targetProperty = option.properties.find( (it) => it.id === first_property_id ); // 如果目标一级属性为颜色属性 则收集所有一级颜色属性 if (targetProperty.property_type === "color") { const colorProperties = option.properties.filter( (it) => it.property_type === "color" ); return colorProperties.map(colorProperty => calculateOptionPrice(colorProperty, 'color')); } else { return calculateOptionPrice(targetProperty); } } else { return option.properties.flatMap(calculateOptionPrice); } } else { return [option[price]]; } }; calculateColorPrice = (option, price, targetColorId) => { let totalPrice = parseFloat(option[price]); for (const property of option.properties) { if (property.id === targetColorId) { totalPrice += parseFloat(property[price]); } if (property.sub_properties) { for (const subProperty of property.sub_properties) { if (subProperty.id === targetColorId) { totalPrice += parseFloat(property[price]) + parseFloat(subProperty[price]); } } } } return totalPrice; } propertyIdChange = (data, steps, type) => { let currentOption = null; if(type === 'lens-type') { currentOption = steps.options.find(option => option.id === data.step_option_id); } else { currentOption = steps.step_options.find(option => option.id === data.step_option_id); } if(data.first_level_property_id && !data.second_level_property_id) { const isColor = currentOption.properties.find(property => property.id === data.first_level_property_id).property_type === 'color'; if(isColor) { data.second_level_property_id = data.first_level_property_id; data.first_level_property_id = ""; } } return data; } impressListen = (selector, cb) => { const el = document.querySelector(selector); const onImpress = (e) => { if (e) { e.stopPropagation(); } cb(); }; if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress) } else if (el) { onImpress(); } } incidentReportingFn = (selector,IncidentReportingData,eventName) => { this.impressListen(selector, function(){ window.sa.track(eventName, IncidentReportingData); }); }; triggerEvent_ = (name, data) => { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } uploadSignV2 = ({file, onSuccess, onFail, onFinally}) => { return this.xhr_.fetchJson('/api/file/signv2').then((sign) => { if (sign) { const signData = sign.data; const formData = new FormData(); const hashKey = (new Date()).getTime(); const fileType = file.name.split(".").pop() formData.append('key', hashKey + "." + fileType); formData.append('policy', signData.policy); formData.append('OSSAccessKeyId', signData.access_id); formData.append('success_action_status', '200'); formData.append('x-oss-forbid-overwrite', 'true'); formData.append('signature', signData.sign); formData.append('file', file); fetch(signData.write_host, { method: 'post', mode: 'cors', body: formData, }).then((data) => { const fileUrl = signData.read_host + hashKey + "." + fileType; onSuccess && onSuccess({signData, data, fileUrl}); }).catch(function (e) { onFail && onFail(e); console.log("上传图片失败", e); }).finally(() => { onFinally && onFinally(); }) } }); } getCurrentDate = () => { const date = new Date(); return date.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); } getGlobalSettings_ = async (id, type) => { const data = await this.xhr_.fetchJson('/api/fireant/v2/setting', { method: "get", }); return data[type] } setupVariantOptions_(product) { const variantOptionObj = product.options.reduce((total, cur, index) => { return { ...total, ['option' + (index + 1)]: cur.value } }, {}) this.initProductInfo_.selected_variant = { price: product.trunk_price, compare_at_price: product.compare_at_price, image: product.image.src, ...variantOptionObj }; } resolveProperties_ = (properties) => { const keyMap = { 'OD Right': 'od', 'OS Left': 'os', 'PD': 'pd', 'Prism OD': 'od', 'Prism OS': 'os', 'Ocular Height': 'ocular_height', 'Prescription Type': 'prescription_type', 'Submission Method': 'submit_method', 'Birth Year': 'birth_year', '_steps': 'steps', 'lens_processing_id': 'lens_process_id', 'Prescription File': 'prescription_image', '_is_prism': 'is_prism', '_od_qty': 'od_qty', '_os_qty': 'os_qty', '_uid': 'uid', }; let total = {}; Object.keys(properties).forEach(key => { switch (key) { case 'OD Right': case 'OS Left': properties[key].split("|").forEach(property => { const [propertyKey, value] = property.trim().split(' '); total[`prescription_${keyMap[key]}_${propertyKey.toLowerCase()}`] = value; }); break; case 'PD': if (properties[key].includes('Dual PD')) { const [ , leftValue, , rightValue] = properties[key].replace('Dual PD | ', '').trim().split(' '); total['prescription_pd_l'] = leftValue; total['prescription_pd_r'] = rightValue; } else { total['prescription_pd'] = properties[key]; } break; case 'Prism OD': case 'Prism OS': const [value1, value2, value3, value4] = properties[key].replaceAll(' ', '').split('|'); total[`prescription_${keyMap[key]}_pv`] = value1; total[`prescription_${keyMap[key]}_bd`] = value2; total[`prescription_${keyMap[key]}_pv_r`] = value3; total[`prescription_${keyMap[key]}_bd_r`] = value4; break; case 'Prescription Type': total['prescription_type'] = properties[key].split(':')[0]; break; case 'Submission Method': total['submit_method'] = properties[key].split('+')[0].trim(); break; default: if (keyMap[key]) { total[keyMap[key]] = properties[key]; } } }); return total; } hexToRGBA_ = (hex, alpha=0.2) => { hex = hex.replace("#", ""); var r = parseInt(hex.substring(0, 2), 16); var g = parseInt(hex.substring(2, 4), 16); var b = parseInt(hex.substring(4, 6), 16); return "rgba(" + r + ", " + g + ", " + b + "," + alpha + ")"; } setGlobalPrimaryColor = (data,lensType) => { const GlobalPrimaryColor = data.color; const styleDom = document.createElement('style'); const lightColor = this.hexToRGBA_(data.color, 0.3); let wrapperClass = ''; if(lensType == 'glasses'){ wrapperClass = '.glasses-wrapper'; }else if(lensType == 'contact'){ wrapperClass = '.contact-wrapper' } // 设置主题颜色 document.documentElement.style.setProperty('--lens-setting-color', this.hexToRGBA_(data.color, 1)); // 设置处方选项hover的颜色 document.documentElement.style.setProperty('--lens-option-hover-color', this.hexToRGBA_(data.color, 0.1)); // 设置处方选项选择时颜色 document.documentElement.style.setProperty('--lens-option-pressed-color', this.hexToRGBA_(data.color, 0.2)); // 设置处方子选项选择时颜色 document.documentElement.style.setProperty('--lens-sub-option-pressed-color', this.hexToRGBA_(data.color, 0.3)); styleDom.innerHTML = ` .lamb-primary-color, .atc_modal_footer_sticky .button-secondary, .icon_tick_half { color: ${GlobalPrimaryColor}; } .atc_radio_input:checked ~ .atc_radio_label, input:checked + .my_prescription_item_label { outline: 2px solid ${GlobalPrimaryColor}; } .lens-process-container ${wrapperClass} .button-primary, .atc_radio_input:checked ~ label .atc_radio, .atc_checkbox_input:checked + label .atc_checkbox, .atc_checkbox_input:checked + .label .atc_checkbox { background-color: ${GlobalPrimaryColor}; } .atc_modal_footer_sticky .button-secondary { border-color: ${GlobalPrimaryColor} } .atc_radio_input:checked ~ .atc_radio_label { background: ${lightColor} } [role="prescription_form"] select:not([disabled]):hover, [role="prescription_form"] select:not([disabled]):focus, .atc_select:not([disabled]):hover, .atc_select:not([disabled]):focus { border-color: ${GlobalPrimaryColor} } .atc_radio_input:checked ~ .atc_radio_label_light_text { color: #292929 } `; document.body.appendChild(styleDom); } resolvePrescriptionTypes_ = (prescription_types) => { prescription_types.options.forEach(option => { if (option[PRESCRIPTION] === PRESCRIPTION_TYPE.READING) { if(!option?.reading_no_lens_title) { option.reading_no_lens_title = option.reading_glass_title; } if (!option?.reading_lens_title) { option.reading_lens_title = option.reading_no_glass_title; } return prescription_types } }) return prescription_types } filterObject_(data, properties) { return properties.reduce((total, property) => ({ ...total, [property]: data[property] }), {}) } openSidebar = (sidebarId) => { const sidebarEl = document.getElementById(sidebarId); sidebarEl && SPZ.whenApiDefined(sidebarEl).then((api) => { api.open(); }); } getFormData_(formData) { return Object.fromEntries(formData); } clearInitProductInfo_() { this.initProductInfo_.image = {width: 0, height: 0, src: '',path:'', alt: ''} this.initProductInfo_.title = '' this.initProductInfo_.selected_variant = {compare_at_price: 0, image: '', option1: '',option2:'', price : 0} } } SPZ.defineElement(TAG, SpzCustomLensUtil) })(); (function () { const TAG = 'spz-custom-process-back-btn'; class SpzCustomProcessBackBtn extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } mountCallback = () => { } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } setupAction_ = () => { this.registerAction('goBack', (invocation) => { alert('返回') }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomProcessBackBtn) })() (function () { const TAG = 'spz-custom-process-submit-btn'; class processSubmitBtn extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } mountCallback = () => { } onDocumentState = (doc, stateFn, callback) => { let ready = stateFn(doc); if (ready) { callback(doc); } else { const readyListener = () => { if (stateFn(doc)) { if (!ready) { ready = true; callback(doc); } doc.removeEventListener('readystatechange', readyListener); } }; doc.addEventListener('readystatechange', readyListener); } } isDocumentReady = (doc) => { return doc.readyState != 'loading' && doc.readyState != 'uninitialized'; } whenReadyFuc = () => { return new Promise((resolve) => { this.onDocumentState(window.document,this.isDocumentReady,resolve) }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); this.addBtnListener_() }); } addBtnListener_ = () => { this.continueBtnDom_ = SPZCore.Dom.scopedQuerySelector( this.element, '.continue' ); } setupAction_ = () => { this.registerAction('goBack', (invocation) => { alert('返回') }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, processSubmitBtn) })() (function(){ const TAG = 'spz-custom-html-unescape'; class SpzCustomHtmlUnescape extends SPZ.BaseElement { constructor(element) { super(element); this.htmlStr_ = ""; this.templates_ = SPZServices.templatesForDoc(); } buildCallback = () => { this.htmlStr_ = this.element.getAttribute('htmlStr') } mountCallback() { const spanDom = document.createElement("div"); spanDom.innerHTML = this.htmlStr_; this.element.appendChild(spanDom); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomHtmlUnescape) })(); (function(){ const TAG = 'spz-custom-pd-guide'; class SpzCustomPdGuide extends SPZ.BaseElement { constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); } buildCallback = () => { this.setupAction_(); } mountCallback() { const render = async () => { this.data = await this.getSettingData_(); // 设置默认颜色 const el = document.querySelector('#PD_number_guide_modal') el.style.setProperty('--pd-color-default', this.data.color); this.doRender_(this.data); }; render(); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } getSettingData_ = ()=> { return this.xhr_.fetchJson('/api/fireant/setting', { method: "get", }); } setupAction_ = () => { this.registerAction('renderRulerUrl', async () => { if(this.data){ this.doRender_(this.data); } else { this.data = await this.getSettingData_(); this.doRender_(this.data); } }) } doRender_ = (data) => { const renderEl = document.getElementById('spz-custom-pd-guide-render') SPZ.whenApiDefined(renderEl).then(async (api) => { api.render(data); }) } } SPZ.defineElement(TAG, SpzCustomPdGuide) })(); (function(){ const TAG = 'spz-custom-pd-warning'; class SpzCustomPdGuide extends SPZ.BaseElement { constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); } buildCallback = () => { this.setupAction_(); } mountCallback() { } doRender = (data) => { const { tipTypes, settings } = data; const renderEl = document.getElementById('pd-warning-render') SPZ.whenApiDefined(renderEl).then(async (api) => { api.render(data); }) } renderModal = (data)=> { const { type, settings } = data; const renderEl = document.getElementById('pd-warning-render'); const pd_warning_modal = document.getElementById("pd_warning_modal"); SPZ.whenApiDefined(renderEl).then(async (api) => { api.render(data).then(()=>{ SPZ.whenApiDefined(pd_warning_modal).then(api => { api.open(); }); }); }) } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } getSettingData_ = ()=> { return this.xhr_.fetchJson('/api/fireant/setting', { method: "get", }); } setupAction_ = () => { this.registerAction('renderPdWarning', (invocation) => { const data = invocation && invocation.args && invocation.args.data; }); } } SPZ.defineElement(TAG, SpzCustomPdGuide) })(); const TAG = 'spz-custom-lens-marketing-block'; class SpzLensBlockComponent extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.product = {}; this.variant = []; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.templates_ = SPZServices.templatesForDoc(this.element); this.setAction_(); this.xhr_ = SPZServices.xhrFor(this.win); this.action_ = SPZServices.actionServiceForDoc(this.element); } mountCallback() { const that = this; } getLambLensSteps_(data) { const type = data.args.type; const glassesInfo = { product:{ title: this.product.title, id: this.product.id, image: this.product.image, variants: this.product.variants, selected_variant: this.variant }, atcType: 'add_lens' }; const tempElement = document.getElementById('process-request-script'); tempElement && SPZ.whenApiDefined(tempElement).then(async (api) => { await api.requestLensProcess(glassesInfo, type); }); } frameOnlyAddCart(event) { const { lens_processing_id, product_id} = event.args; const reqBody = { product_id, variant_id: this.variant.id, quantity: 1, properties: { lens_processing_id: lens_processing_id, prescription_type: "Frame Only" } } this.xhr_.fetchJson('/api/fireant/customize_cart', { method: "post", body: reqBody }).then(res => { if(res.state === 'success') { this.triggerEvent_('atcSuccess', res.data.items[0]); } }).catch(async err => { const data = await err; if(data.state && data.state !== 'success') this.triggerEvent_('atcError', data); }) } setAction_() { this.registerAction('handleProductChange', (data) => { const variant = data.args.data.variant; const product = data.args.data.product; this.variant = variant; const imageRenderEl = document.getElementById('lens_marketing_product_image'); SPZ.whenApiDefined(imageRenderEl).then((api) => { this.product = product; api.render({ variant: variant, product: product }); }); }); this.registerAction('getLambLensSteps_', (data) => { this.getLambLensSteps_(data); }) this.registerAction('frameOnlyAddCart', (data) => { this.frameOnlyAddCart(data); }) this.registerAction('handleAtcSuccess', (detail) => { const data = detail.args; data.data.product = data.data.product || {}; data.data.variant = data.data.variant || {}; const product_id = data.data.product_id; const product_title = data.data.product_title; const variant_id = data.data.variant_id; const price = data.data.price; const params = { id: product_id, product_id: product_id, number: 1, name: product_title, variant_id: variant_id, item_price: price, source: 'frame_only', _extra: { spm: `${window.SHOPLAZZA.meta.page.template_name}`, } }; this.tranckAddToCart(params); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } tranckAddToCart(detail) { const event = new CustomEvent('dj.addToCart', { detail, bubbles: true }) document.body.dispatchEvent(event) } } SPZ.defineElement(TAG, SpzLensBlockComponent); const templateName = `product`; if(!['couponsCollection', 'couponCollection', 'flashsaleCollection', 'rebateCollection'].includes(templateName)) return; const productMap = {}; let allProduct = []; let productInfo = null; let lensBtnText = ''; const couponObj = window.couponObj || window.rebateObj || {}; function fetchProductData(product_ids, path, fields=null, method="POST") { const params = { method, headers: { "Content-Type": "application/json" }, }; if(method === 'POST') { params.body = JSON.stringify({ product_ids: product_ids, fields }); } return fetch(window.SHOPLAZZA.routes.root + path, params).then(function(res){ if(res.ok){ return res.json(); } }).catch(function(err){ console.log(err); const loadingEl = document.getElementById('lens_marketing_loading'); if (loadingEl) { loadingEl.style.display = 'none'; } }); } function handleOpenModal(id) { const modalRender = document.getElementById('lens_marketing_product_modal_render'); fetchProductData(null, `/api/products/${id}`, null, "GET").then(res => { const product = res.data?.product || {}; let variant_ids = couponObj.entitled_product_ids?.includes(id) ? [] : couponObj.entitled_variant_ids; product.variants.forEach(variant => { if(!variant_ids) return; if(couponObj.target_selection === 'exclude') { if(!variant_ids.length || variant_ids.includes(variant.id)) { variant.available = false; } } else { if(variant_ids.length && !variant_ids.includes(variant.id)) { variant.available = false; } } }); productInfo = product; SPZ.whenApiDefined(modalRender).then((api) => { api.render({product: product, lensData: productMap[id]}, true).then(() => { const modalEl = document.getElementById('lens_marketing_product_modal'); SPZ.whenApiDefined(modalEl).then((modal) => { modal.open(); }); const formEl = document.getElementById('lens_marketing_product_form'); SPZ.whenApiDefined(formEl).then((form) => { form.setProduct(product); }); const variantEl = document.getElementById('lens_marketing_product_variants'); SPZ.whenApiDefined(variantEl).then((variant) => { variant.handleRender(product); }); }); }) }); } function handleReplaceDom(dom) { const lensBtn = document.createElement('div'); const button = document.createElement('button'); const btn = dom.querySelector('.atc-button'); button.classList.add('atc-button'); lensBtn.classList.add('lens-text-clamp'); button.setAttribute('data-p-id', btn.getAttribute('data-p-id')); button.setAttribute('on:tap', "lens_marketing_loading.showLoading"); button.onclick = handleOpenModal.bind(lensBtn, btn.getAttribute('data-p-id')); lensBtn.innerHTML = lensBtnText; button.appendChild(lensBtn); dom.replaceChild(button, btn); } async function handleQueryLens(products) { const ids = Array.from(products).map(p => p.getAttribute('data-track-id')); const filterIds = Array.from(new Set([...ids, ...allProduct])); const temp = [...allProduct]; const paramsIds = filterIds.filter(id => !temp.includes(id)); allProduct = filterIds; if(paramsIds && paramsIds.length) { const data = await fetchProductData(paramsIds || [], '/api/fireant/product/button_config'); data.button_configs.forEach(p => { productMap[p.product_id] = p } ); } products.forEach(product => { const id = product.getAttribute('data-track-id'); if(!productMap[id]) return; lensBtnText = productMap[id].add_lens_button_text; handleReplaceDom(product); }); } const lens_observe = (() =>{ const observedSelector= []; let mObserver=null; return (targetSelector, callback)=>{ observedSelector.push(targetSelector); document.body.addEventListener('dj.lens_market_dom', (e) => { if (e.detail.selector == targetSelector) { callback(e.detail.added); } }) if (mObserver) return; mObserver = new MutationObserver((mutationsList)=>{ var addNodes = [].concat.apply([], mutationsList.map((m) =>{ return Array.from(m.addedNodes || [],(n)=> (n && n.nodeType) == 3 ? n.parentNode : n ).filter(Boolean).filter(n => n.nodeType != 8); })); observedSelector.forEach((selector) => { var matchedAddedNodes = [].concat.apply([], addNodes.map((n) => { return n ? ((n.matches && n.matches(selector)) ? [n] : Array.from(n.querySelectorAll(selector))) : []; })); matchedAddedNodes.length && (document.body.dispatchEvent(new CustomEvent('dj.lens_market_dom', { detail: { selector: selector, added: matchedAddedNodes } }))); }) }); mObserver.observe(document.body, { subtree: true, childList: true }); } })() lens_observe('.product-snippet', (e) => { handleQueryLens(e); }) if(document.querySelectorAll('.product-snippet').length) { document.body.dispatchEvent(new CustomEvent('dj.lens_market_dom', { detail: { selector: '.product-snippet', added: document.querySelectorAll('.product-snippet'), flag: 'first'} })); } (function () { const TAG = 'spz-custom-process-request-script'; class SpzCustomProcessRequestScript extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } mountCallback = () => { } requestLensProcess = async (glassesInfo, process_type) => { const { product, atcType } = glassesInfo; const lens_process_id = location.search.replace('?', '').split('&').find(v => v.includes('lens_process_id')); const template_id = location.search.replace('?', '').split('&').find(v => v.includes('template_id')); const stepsUrl = lens_process_id ? `?${lens_process_id}` : '' || template_id ? `?${template_id}` : ''; const productId = atcType === 'edit_lens' ? product.product_id : product.id; let atcProcessId = 'glasses_custom_atc' if (process_type === 'contact_lens') { atcProcessId = 'contact_custom_atc' } else { process_type = 'glasses' } const processSidebar = document.querySelector('#lens-process-sidebar'); ['glasses_custom_atc', 'contact_custom_atc'].forEach(id => { const tempElement = processSidebar.querySelector(`[role=${id}]`); tempElement.style.display = (id === atcProcessId ? 'block' : 'none'); }) document.querySelector('#lens-process-sidebar').setAttribute("process-type", process_type) const tempElement = document.getElementById(atcProcessId); const api = await SPZ.whenApiDefined(tempElement); await api.init_(glassesInfo, stepsUrl); } setupAction_ = () => { this.registerAction('editLensProcess', async(invocation) => { const {process_type, ...glassesInfo} = invocation.args.data; this.requestLensProcess(glassesInfo, process_type) }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } } SPZ.defineElement(TAG, SpzCustomProcessRequestScript) })() (function(){ const TAG = 'spz-custom-prescription-table'; class prescriptionTableComponent extends SPZ.BaseElement { constructor(element) { super(element); this.prescriptionId_ = null; this.prescriptionData_ = null; } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.setupAction_(); this.prescriptionId_ = this.element.getAttribute('prescription-id'); } mountCallback = () => { const from = ''; if(!this.prescriptionData_ && from === 'order') return; this.doRender_(); } doRender_ = async (renderData = '') => { let data = {} const prescriptionTableScript = document.getElementById('prescription-table-script'); if(prescriptionTableScript) { const api = await SPZ.whenApiDefined(prescriptionTableScript); if(renderData) { data = api.changePrescriptionData_(renderData); } else { data = api.getTargetPrescriptionData_(this.prescriptionId_); } } return this.templates_ .findAndRenderTemplate(this.element, data) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } getDeepestData = (obj) => { if (obj.data && typeof obj.data === 'object') { return this.getDeepestData(obj.data); } else { return obj; } } setupAction_ = () => { this.registerAction('rerender', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; const prescriptionData = this.getDeepestData(data); this.prescriptionData_ = [{name: 'Prescription Id', value: data?.data?.data?.id}].concat(prescriptionData); this.doRender_(this.prescriptionData_); }); } triggerEvent_ = (name, data) => { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } }; SPZ.defineElement(TAG, prescriptionTableComponent); })(); (function(){ const TAG = 'spz-custom-prescription-table-script'; class prescriptionTableScript extends SPZ.BaseElement { constructor(element) { super(element); this.prescriptionData_ = []; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.setupAction_(); } getPrescriptionData_ = (data) => { this.prescriptionData_ = data; return this.prescriptionData_; } getTargetPrescriptionData_ = (id) => { if(this.prescriptionData_.length > 0) { return this.prescriptionData_.find((prescription) => prescription.prescription_id === id); } else { return this.prescriptionData_; } } changePrescriptionData_ = (originData) => { const prescriptionData = { is_prism: originData.findIndex(p => p.name.includes('Prism')) != -1 || false, }; const handlers = { 'Prescription Type': (value) => { prescriptionData[`prescription_type`] = value; }, 'OD Right': (value) => { value.split("| ").forEach((v) => { const parts = v.split(' ').map(str => str.trim()); prescriptionData[`prescription_od_${parts[0].toLowerCase()}`] = parts.slice(1).join(' ').trim(); }); }, 'OS Left': (value) => { value.split("| ").forEach((v) => { const parts = v.split(' ').map(str => str.trim()); prescriptionData[`prescription_os_${parts[0].toLowerCase()}`] = parts.slice(1).join(' ').trim(); }); }, 'PD': (value) => { if(value.indexOf("Dual PD") > -1){ let prescription_dual_pd = value.split("|"); prescription_dual_pd = prescription_dual_pd.length > 1 && prescription_dual_pd[1].trim().split("Right"); prescriptionData.prescription_pd_r = prescription_dual_pd.length > 1 && prescription_dual_pd[1].trim(); var prescription_pd_l = prescription_dual_pd[0].trim().split("Left"); prescriptionData.prescription_pd_l = prescription_pd_l.length > 1 && prescription_pd_l[1].trim(); } else { prescriptionData.prescription_pd = value.replace('Single PD',"").trim(); } }, 'Prism OD': (value) => { let prescription_prism_od = value.split("|"); prescriptionData.prescription_od_pv = prescription_prism_od[0].trim(); prescriptionData.prescription_od_bd = prescription_prism_od[1].trim(); prescriptionData.prescription_od_pv_r = prescription_prism_od[2].trim(); prescriptionData.prescription_od_bd_r = prescription_prism_od[3].trim(); }, 'Prism OS': (value) => { let prescription_prism_os = value.split("|"); prescriptionData.prescription_os_pv = prescription_prism_os[0].trim(); prescriptionData.prescription_os_bd = prescription_prism_os[1].trim(); prescriptionData.prescription_os_pv_r = prescription_prism_os[2].trim(); prescriptionData.prescription_os_bd_r = prescription_prism_os[3].trim(); }, 'Ocular Height': (value) => { prescriptionData.ocular_height = value.trim(); }, 'Birth Year': (value) => { prescriptionData.birth_year = value.trim(); }, 'process_type': (value) => { prescriptionData.process_type = value.trim(); }, 'Prescription Id': (value) => { prescriptionData.prescription_id = value.trim(); } }; originData.map((data) => { if (handlers[data.name]) { handlers[data.name](data.value); } }); return prescriptionData; }; setupAction_ = () => { this.registerAction('getData', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; data.cart.line_items.forEach((item) => { item.properties_obj = this.changePrescriptionData_(item.parsedProperties); }); const prescriptionData = data.cart.line_items.map((item) => { return { prescription_id: item.item_id, process_type: item.parsedProperties.findIndex(p => p.name.includes('Quantity')) != -1 ? 'contact_lens' : 'glasses', ...item.properties_obj, } }); this.getPrescriptionData_(prescriptionData); }); } triggerEvent_ = (name, data) => { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.LOGIC; } }; SPZ.defineElement(TAG, prescriptionTableScript); })();