(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 = `cart`; 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); })(); (function(){ const TAG = 'spz-custom-contact-lamb-cart'; class spzCustomContactLambCart extends SPZ.BaseElement { constructor(element) { super(element); this.configList = null; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } mountCallback() { } getConfigList = async({product_ids}) => { const reqBody = { product_ids: product_ids } try{ const data = await this.xhr_.fetchJson('/api/fireant/lens_process', { method: 'post', body: reqBody }); let configList = []; data.lens_processes.forEach(item => { const config_column = item.prescription_types?.options[0]?.config_column; const parseConfigColumn = config_column && JSON.parse(config_column); if(item.process_type === "contact_lens") { let obj = { product_id: item.product_id, qty_range: parseConfigColumn.qty_range }; configList.push(obj); } }) this.configList = configList.length && configList; }catch { console.log('json parse err') } return this.configList; } initMaxAndMinQty = (lineItems) => { lineItems?.forEach(lineItem => { const configItem = this.configList.length && this.configList.find(item => item.product_id == lineItem.product_id); if(configItem && configItem.qty_range) { const max_qty = configItem.qty_range.max; const min_qty = configItem.qty_range.min; const el = document.querySelector(`#contact-qty-${lineItem.id}`); el && SPZ.whenApiDefined(el).then((api) => { let params = {}; if(Number(max_qty)>=0) { params.max = max_qty; } if(Number(min_qty) >=0) { params.min = min_qty; } api.updateOsQty(params); api.updateOdQty(params); }); } }) } editLineItem_ = async(data) => { const id = data.id const currentFormDom = document.querySelector(`#glasses-edit-form-${id}`); const os_qty = currentFormDom.querySelector('[name="os_qty"] input')?.value || 0; const od_qty = currentFormDom.querySelector('[name="od_qty"] input')?.value || 0; try{ const reqBody = { product_id: data.product_id, variant_id: data.variant_id, left_quantity: parseInt(os_qty), right_quantity: parseInt(od_qty), line_item_id: data.cart_id, }; try { await this.xhr_.fetchJson('/api/fireant/customize_cart/quantity', { method: 'patch', body: reqBody }); } catch { console.log('err') } const cartDom = document.getElementById('cart-content'); const totalCount = parseInt(os_qty) + parseInt(od_qty); const api = await SPZ.whenApiDefined(cartDom); if(totalCount <= 0) { await api.handleDelete_(id); }else { await api.handleUpdate_({value: totalCount, id}); } } catch(e) { console.log(e) } } showLoading(isLoading){ const loadingEl = document.querySelector('[role="loading"]') if(isLoading == true) { loadingEl.setAttribute('show',''); loadingEl.removeAttribute('hide'); } else { loadingEl.setAttribute('hide',''); loadingEl.removeAttribute('show'); } } setupAction_ = async() => { this.registerAction('init', async(invocation) => { const data = invocation.args.data; const lineItems = data.cart?.line_items; const productIds = lineItems?.map(item => { return item.product_id; }) if(productIds.length) { if(!this.configList){ await this.getConfigList({product_ids: productIds}); } this.initMaxAndMinQty(lineItems); } }); this.registerAction('limitContactQty', async(invocation) => { const data = invocation.args.data; const lineItems = data.cart?.line_items; if(lineItems.length) { if(!this.configList){ const productIds = lineItems?.map(item => { return item.product_id; }) await this.getConfigList({product_ids: productIds}); } setTimeout(()=>{ this.initMaxAndMinQty(lineItems); },100) } }); this.registerAction('handleContactQty', async(invocation) => { const data = invocation.args; const type = data.type; const product_id = data.product_id; const value = data.value; const itemId = data.id; const variant_id = data.variant_id; if(!value && value != 0) { return; } this.showLoading(true); const configItem = this.configList.length && this.configList.find(item => item?.product_id == product_id); if(configItem) { const max_qty = configItem.qty_range?.max; const min_qty = configItem.qty_range?.min; const el = document.querySelector(`#contact-qty-${itemId}`); el && SPZ.whenApiDefined(el).then((api) => { let params = {}; if(Number(max_qty) >= 0) { params.max = max_qty; } if(Number(min_qty) >= 0) { params.min = min_qty; } if(data.value > Number(max_qty)) { params.value = max_qty; this.triggerEvent_('toast',{text:`The maximum optional quantity at a time is ${max_qty}`}); }else if (data.value < Number(min_qty)){ params.value = min_qty; this.triggerEvent_('toast',{text:`The minimum optional quantity at a time is ${min_qty}`}); } if(data.type == 'OS'){ api.updateOsQty(params); }else if(data.type == 'OD'){ api.updateOdQty(params); } }); } setTimeout(()=>{ this.editLineItem_(data).then(() => { this.showLoading(false); }).catch(() => { this.showLoading(false); }); },100) }); this.registerAction('handleQtyUnderflow', async(invocation) => { const data = invocation.args; const value = data.value; this.triggerEvent_('toast',{text:`The minimum optional quantity at a time is ${value}`}); setTimeout(()=>{ this.editLineItem_(data); },100) }); this.registerAction('handleQtyOverflow', async(invocation) => { const data = invocation.args; const value = data.value; this.triggerEvent_('toast',{text:`The maximum optional quantity at a time is ${value}`}); setTimeout(()=>{ this.editLineItem_(data); },100) }); this.registerAction('editLineItem', async(invocation) => { }); } 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, spzCustomContactLambCart) })(); (function(){ const TAG = 'spz-custom-contact-qty'; class spzCustomContactQty extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); } mountCallback() { } updateOsQty = (data) => { this.triggerEvent_('updateOsQty',data) } updateOdQty = (data) => { this.triggerEvent_('updateOdQty',data) } setupAction_ = async() => { this.registerAction('updateOsQty', async(invocation) => { const data = invocation.args.data; this.triggerEvent_('updateOsQty',data) }) this.registerAction('updateOdQty', async(invocation) => { const data = invocation.args.data; this.triggerEvent_('updateOdQty',data) }) } 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, spzCustomContactQty) })();

Your shopping bag is empty

Continue shopping