Demitko Lens Portal

专业镜片订购与订单管理

让镜片下单更准确、更清晰。

客户在线提交处方、镜片参数和收货地址;卖家在后台确认价格、更新生产状态并下载订单。

三语界面中文、English、Русский 随时切换
处方结构化OD / OS、SPH、CYL、AXIS、ADD、PD
订单可追踪确认、生产、发货、完成全过程

登录系统

客户可注册并提交订单,卖家使用后台账号处理订单。

新Demitko V8 FINAL · 已导入玖玖镜片目录并客户订单系统升级:联系方式、城市物流与商家生产管理已优化。

📩 遇到问题?联系 Telegram

如遇到注册、登录或下单问题,可扫码联系,或点击下方链接打开 Telegram。

Glasses Hunter Telegram QR
` } function downloadOrder(id){const o=orders.find(x=>x.id===id);if(!o)return;const win=window.open('','_blank');if(!win){toast(lang==='zh'?'浏览器阻止了新窗口,请允许弹窗后重试。':lang==='ru'?'Браузер заблокировал новое окно. Разрешите всплывающие окна.':'The browser blocked the new window. Please allow pop-ups and try again.');return}win.document.open();win.document.write(orderTicketHTML(o));win.document.close()} function excelDocument(title,body){return `

${esc(title)}

${body} `} function orderExcelHTML(o){const p=o.prescription||{od:{},os:{}},l=o.lens||{},a=o.address||{};const info=`${excelRow(tr('orderNo'),o.orderNo)}${excelRow(tr('date'),fmtDate(o.createdAt))}${excelRow(tr('status'),statusLabel(o.status))}${excelRow(tr('customer'),o.customerName)}${excelRow(tr('phone'),[o.mobilePhone,o.backupPhone].filter(Boolean).join(' / '))}${excelRow(tr('address'),orderAddress(o))}${excelRow(tr('prescriptionSource'),p.source||'—')}${excelRow(tr('frameType'),p.frameType||'—')}${excelRow(tr('customerNote'),o.customerNote||'—')}
${esc(tr('orderDetail'))}
`; const rx=`${['eye','SPH','CYL','AXIS','ADD','PD',tr('fittingHeight')].map(x=>``).join('')}${excelCell('OD','center')}${excelCell(p.od?.sph||'—','text')}${excelCell(p.od?.cyl||'—','text')}${excelCell(p.od?.axis||'—','text')}${excelCell(p.od?.add||'—','text')}${excelCell(p.od?.pd||'—','text')}${excelCell(p.od?.height||'—','text')}${excelCell('OS','center')}${excelCell(p.os?.sph||'—','text')}${excelCell(p.os?.cyl||'—','text')}${excelCell(p.os?.axis||'—','text')}${excelCell(p.os?.add||'—','text')}${excelCell(p.os?.pd||'—','text')}${excelCell(p.os?.height||'—','text')}
${esc(tr('prescription'))}
${esc(x)}
`; const lens=`${excelRow(tr('purpose'),l.purpose||'—')}${excelRow(tr('productCategory'),l.category||'—')}${excelRow(tr('selectedSeries'),orderSeriesName(o))}${excelRow(tr('priceListProduct'),orderProductName(o))}${excelRow(tr('priceRange'),l.range||'—')}${excelRow(tr('index'),l.index||'—')}${excelRow(tr('coating'),l.coating||'—')}${excelRow(tr('photochromic'),l.photochromic||'—')}${excelRow(tr('tintColor'),l.tintColor||'—')}${excelRow(tr('diameter'),l.diameter||'—')}${excelRow(tr('quantity'),l.pairs||1)}
${esc(tr('lensRequirements'))}
`; const prices=`${excelRow(tr('unitPrice'),l.unitPrice!==undefined?fmtCny(l.unitPrice):'—')}${excelRow(tr('productSubtotal'),l.subtotal!==undefined?fmtCny(l.subtotal):'—')}${excelRow(tr('shippingBreakdown'),l.shippingFee!==undefined?`${fmtCny(l.shippingFee)} / ${l.shippingKgBlocks||1}kg`:'—')}${excelRow(tr('amount'),money(o.quoteAmount,o.currency))}${excelRow(tr('currency'),o.currency||'CNY')}
${esc(tr('priceCalculation'))}
`; const seller=`${excelRow(tr('status'),statusLabel(o.status))}${excelRow(tr('trackingNo'),o.trackingNo||'—')}${excelRow(tr('sellerNote'),o.sellerNote||'—')}
${esc(tr('sellerProcessing'))}
`; return excelDocument(`${tr('orderNo')} ${o.orderNo}`,info+rx+lens+prices+seller)} function downloadBlob(content,name,type='text/plain;charset=utf-8'){const blob=new Blob([content],{type}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url)} function exportOrdersExcel(){const headers=[tr('orderNo'),tr('date'),tr('status'),tr('customer'),tr('phone'),tr('backupPhone'),tr('country'),tr('region'),tr('city'),tr('postalCode'),tr('fullAddress'),'OD SPH','OD CYL','OD AXIS','OD ADD','OD PD',`OD ${tr('fittingHeight')}`,'OS SPH','OS CYL','OS AXIS','OS ADD','OS PD',`OS ${tr('fittingHeight')}`,tr('prescriptionSource'),tr('frameType'),tr('purpose'),tr('productCategory'),tr('selectedSeries'),tr('priceListProduct'),tr('priceRange'),tr('index'),tr('coating'),tr('photochromic'),tr('tintColor'),tr('diameter'),tr('quantity'),tr('unitPrice'),tr('productSubtotal'),tr('shippingBreakdown'),tr('amount'),tr('currency'),tr('trackingNo'),tr('customerNote'),tr('sellerNote')]; const rows=orders.map(o=>{const p=o.prescription||{od:{},os:{}},l=o.lens||{},a=o.address||{};return [o.orderNo,fmtDate(o.createdAt),statusLabel(o.status),o.customerName,o.mobilePhone,o.backupPhone||'',a.country||'',a.region||'',a.city||'',a.postalCode||'',a.full||'',p.od?.sph||'',p.od?.cyl||'',p.od?.axis||'',p.od?.add||'',p.od?.pd||'',p.od?.height||'',p.os?.sph||'',p.os?.cyl||'',p.os?.axis||'',p.os?.add||'',p.os?.pd||'',p.os?.height||'',p.source||'',p.frameType||'',l.purpose||'',l.category||'',orderSeriesName(o),orderProductName(o),l.range||'',l.index||'',l.coating||'',l.photochromic||'',l.tintColor||'',l.diameter||'',l.pairs||1,l.unitPrice??'',l.subtotal??'',l.shippingFee??'',o.quoteAmount??'',o.currency||'CNY',o.trackingNo||'',o.customerNote||'',o.sellerNote||'']}); const table=`${headers.map(h=>``).join('')}${rows.map(r=>`${r.map((v,i)=>excelCell(v,[4,5,9,11,12,13,14,15,16,17,18,19,20,21,22,41].includes(i)?'text':'')).join('')}`).join('')}
${esc(h)}
`;downloadBlob('\ufeff'+excelDocument(tr('allOrders'),table),`demitko-orders-${todayISO()}.xls`,'application/vnd.ms-excel;charset=utf-8')} function exportOrdersJSON(){downloadBlob(JSON.stringify({exportedAt:new Date().toISOString(),users,orders},null,2),`demitko-backup-${todayISO()}.json`,'application/json')} document.querySelectorAll('.language-switch button').forEach(b=>b.addEventListener('click',()=>setLanguage(b.dataset.lang))); function showAuthForm(id){['customerLoginForm','registerForm','customerResetForm','sellerLoginForm','passwordRecoveryForm'].forEach(x=>document.getElementById(x)?.classList.toggle('hidden',x!==id))} document.querySelectorAll('.role-tabs button').forEach(b=>b.addEventListener('click',()=>{document.querySelectorAll('.role-tabs button').forEach(x=>x.classList.toggle('active',x===b));showAuthForm(b.dataset.role==='seller'?'sellerLoginForm':'customerLoginForm')})); document.getElementById('showRegisterBtn').onclick=()=>showAuthForm('registerForm'); document.getElementById('showLoginBtn').onclick=()=>showAuthForm('customerLoginForm'); document.getElementById('showCustomerResetBtn').onclick=()=>showAuthForm('customerResetForm'); document.getElementById('backFromCustomerResetBtn').onclick=()=>showAuthForm('customerLoginForm'); document.getElementById('registerForm').addEventListener('submit',async e=>{ e.preventDefault(); const name=regName.value.trim(),phone=regPhone.value.trim(),password=regPassword.value,pin=document.getElementById('regRecoveryPin').value.trim(); const phoneKeyValue=phoneKey(phone),phoneDisplay=internationalPhone(phone); if(password!==regPassword2.value){toast(tr('passwordMismatch'));return} if(!name||!isValidPhone(phone)||password.length<6||pin.length<6){toast(tr('fillRequired'));return} const {data:existing}=await supabaseClient.rpc('resolve_auth_email',{p_phone:phoneKeyValue}); if(existing){toast(tr('phoneExists'));return} const email=phoneAuthEmail(phone); const {data,error}=await supabaseClient.auth.signUp({email,password,options:{data:{full_name:name,phone:phoneDisplay,phone_key:phoneKeyValue,auth_email:email}}}); if(error){console.error(error);toast(error.message);return} let user=data.user; if(!data.session){const signed=await supabaseClient.auth.signInWithPassword({email,password});if(signed.error){toast(signed.error.message);return}user=signed.data.user} if(!user){toast(tr('wrongLogin'));return} const {error:profileError}=await supabaseClient.from('profiles').upsert({id:user.id,full_name:name,phone:phoneDisplay,phone_key:phoneKeyValue,auth_email:email,role:'customer'},{onConflict:'id'}); if(profileError){console.error(profileError);toast(profileError.message);return} const {error:pinError}=await supabaseClient.rpc('set_my_recovery_pin',{p_pin:pin}); if(pinError){console.error(pinError);toast(pinError.message);return} if(!await establishSession(user)){toast(tr('wrongLogin'));return} e.target.reset();showApp();toast(tr('loginSuccess')); }); document.getElementById('customerLoginForm').addEventListener('submit',async e=>{ e.preventDefault(); const phone=loginPhone.value.trim(),password=loginPassword.value; if(!isValidPhone(phone)||!password){toast(tr('fillRequired'));return} const button=e.submitter||e.target.querySelector('button[type="submit"]'); if(button){button.disabled=true;button.textContent='登录中…'} try{ await supabaseClient.auth.signOut(); const result=await signInCustomer(phone,password); if(!result.user){ console.error('Customer auth error:',result.error); const msg=(result.error?.message||'').toLowerCase(); toast(msg.includes('invalid login credentials')?tr('wrongLogin'):(result.error?.message||tr('wrongLogin'))); return; } authUser=result.user; const profileOk=await establishSession(result.user); if(profileOk && session?.role==='seller'){ await supabaseClient.auth.signOut();authUser=null;session=null;toast(tr('wrongLogin'));return; } if(!profileOk){ const meta=result.user.user_metadata||{}; const fallbackProfile={ id:result.user.id, full_name:meta.full_name||phone, phone:meta.phone||internationalPhone(phone), phone_key:meta.phone_key||phoneKey(phone), auth_email:result.user.email||result.email||phoneAuthEmail(phone), role:'customer', default_country:'',default_city:'' }; session={role:'customer',userId:result.user.id,profile:fallbackProfile}; users=[fallbackProfile]; try{ await supabaseClient.from('profiles').upsert(fallbackProfile,{onConflict:'id'}); await refreshCloudData(); }catch(syncError){console.warn('Customer profile sync deferred:',syncError)} } showApp();toast(tr('loginSuccess')); }finally{ if(button){button.disabled=false;button.textContent=tr('login')} } }); document.getElementById('customerResetForm').addEventListener('submit',async e=>{ e.preventDefault();const phone=document.getElementById('resetPhone').value.trim(),pin=document.getElementById('resetRecoveryPin').value.trim(),password=document.getElementById('resetNewPassword').value,password2=document.getElementById('resetNewPassword2').value; if(password!==password2){toast(tr('passwordMismatch'));return}if(!isValidPhone(phone)||pin.length<6||password.length<6){toast(tr('fillRequired'));return} const {data,error}=await supabaseClient.rpc('customer_reset_password',{p_phone:phoneKey(phone),p_pin:pin,p_new_password:password}); if(error){console.error(error);toast(error.message);return}if(data==='NO_PIN'){toast(tr('recoveryPinMissing'));return}if(data!=='OK'){toast(tr('recoveryPinInvalid'));return} e.target.reset();showAuthForm('customerLoginForm');loginPhone.value=phone;toast(tr('passwordResetSuccess')); }); document.getElementById('sellerLoginForm').addEventListener('submit',async e=>{ e.preventDefault(); const email=sellerAccount.value.trim().toLowerCase(); const password=sellerPassword.value; if(!email||!password){toast(tr('fillRequired'));return} const button=e.submitter||e.target.querySelector('button[type="submit"]'); if(button){button.disabled=true;button.textContent='登录中…'} try{ await supabaseClient.auth.signOut(); const {data,error}=await supabaseClient.auth.signInWithPassword({email,password}); if(error||!data?.user){ console.error('Seller auth error:',error); const msg=(error?.message||'').toLowerCase(); toast(msg.includes('invalid login credentials')?'邮箱或密码不正确,请检查密码。':(error?.message||tr('wrongLogin'))); return; } authUser=data.user; // 管理员的邮箱密码已经由 Supabase Auth 验证成功。 // profiles 查询可能会被旧版 RLS 策略拦截,因此不再因为资料读取失败而阻止进入后台。 let profile={id:data.user.id,full_name:'Demitko Admin',role:'admin',auth_email:data.user.email}; const profileResult=await supabaseClient.from('profiles').select('id,full_name,role,auth_email').eq('id',data.user.id).maybeSingle(); if(!profileResult.error&&profileResult.data){ profile=profileResult.data; if(!['admin','staff'].includes(profile.role)){ toast('该账号不是管理员账号。');await supabaseClient.auth.signOut();return; } }else{ console.warn('Profile read skipped; using authenticated admin fallback:',profileResult.error); const allowedAdminEmails=['13717941829@163.com','admin@demitko.com']; if(!allowedAdminEmails.includes((data.user.email||'').toLowerCase())){ toast('该账号没有管理员权限。');await supabaseClient.auth.signOut();return; } } session={role:'seller',userId:data.user.id,profile}; await refreshCloudData(); showApp(); toast(tr('loginSuccess')); }catch(err){console.error(err);toast('登录失败:'+(err?.message||String(err)))} finally{if(button){button.disabled=false;button.textContent=tr('enterBackend')}} }); document.getElementById('sellerForgotBtn').onclick=async()=>{const email=sellerAccount.value.trim().toLowerCase();if(!email){toast(tr('fillRequired'));return}const redirectTo=`${location.origin}${location.pathname}?password-recovery=1`;const {error}=await supabaseClient.auth.resetPasswordForEmail(email,{redirectTo});if(error){toast(error.message);return}toast(tr('sellerResetSent'))}; document.getElementById('passwordRecoveryForm').addEventListener('submit',async e=>{e.preventDefault();const p1=document.getElementById('emailRecoveryPassword').value,p2=document.getElementById('emailRecoveryPassword2').value;if(p1!==p2){toast(tr('passwordMismatch'));return}const {error}=await supabaseClient.auth.updateUser({password:p1});if(error){toast(error.message);return}toast(tr('passwordResetSuccess'));history.replaceState({},'',location.pathname);showAuthForm('sellerLoginForm')}); supabaseClient.auth.onAuthStateChange((event)=>{if(event==='PASSWORD_RECOVERY'){showAuth();showAuthForm('passwordRecoveryForm')}}); document.getElementById('logoutBtn').onclick=async()=>{await supabaseClient.auth.signOut();authUser=null;session=null;users=[];orders=[];showAuth()}; document.querySelectorAll('.nav-btn').forEach(b=>b.addEventListener('click',()=>switchPage(b.dataset.page))); document.getElementById('clearOrderBtn').onclick=clearOrderForm; productCategory.addEventListener('change',()=>renderLensSeries()); lensSeries.addEventListener('change',()=>renderLensIndices()); lensIndex.addEventListener('change',()=>renderPriceProducts()); lensDesign.addEventListener('change',syncSelectedProduct); pairs.addEventListener('input',updatePricePreview); document.getElementById('lensOrderForm').addEventListener('submit',async e=>{e.preventDefault();const u=currentUser();if(!u||!authUser)return;const required=[receiverName,mobilePhone,country,region,city,fullAddress,purpose,productCategory,lensSeries,lensDesign,lensIndex,coating,pairs,accuracyConfirm];if(required.some(x=>x.type==='checkbox'?!x.checked:!String(x.value).trim())){toast(tr('fillRequired'));return}const lens=collectLens(),calc=calculatePrice(),p=selectedPriceProduct();const payload={order_no:orderNo(),customer_id:authUser.id,receiver_name:receiverName.value.trim(),mobile_phone:internationalPhone(mobilePhone.value)||mobilePhone.value.trim(),backup_phone:backupPhone.value.trim()?(internationalPhone(backupPhone.value)||backupPhone.value.trim()):null,shipping_address:{country:country.value.trim(),region:region.value.trim(),city:city.value.trim(),postalCode:postalCode.value.trim(),full:fullAddress.value.trim()},prescription:collectPrescription(),customer_note:customerNote.value.trim()||null,status:'pending',subtotal:calc.subtotal,shipping_fee:calc.shipping,total_amount:calc.total,currency:'CNY'};const {data:created,error}=await supabaseClient.from('orders').insert(payload).select().single();if(error){console.error(error);toast(error.message);return}const {error:itemError}=await supabaseClient.from('order_items').insert({order_id:created.id,product_id:p?.id||null,product_snapshot:{lens},quantity:lens.pairs,unit_price:calc.unit,line_total:calc.subtotal});if(itemError){console.error(itemError);toast(itemError.message);return}await supabaseClient.from('profiles').update({default_country:country.value.trim(),default_city:city.value.trim(),phone:internationalPhone(mobilePhone.value)||mobilePhone.value.trim()}).eq('id',authUser.id);await loadCloudOrders();clearOrderForm();renderCustomerOrders();toast(tr('orderSubmitted'));switchPage('customerOrdersPage')}); document.getElementById('profileForm').addEventListener('submit',async e=>{e.preventDefault();if(!authUser)return;const payload={full_name:profileName.value.trim(),phone:internationalPhone(profilePhone.value),default_country:profileCountry.value.trim(),default_city:profileCity.value.trim()};const {data,error}=await supabaseClient.from('profiles').update(payload).eq('id',authUser.id).select().single();if(error){toast(error.message);return}session.profile=data;users=[dbProfileToUi(data)];renderApp();toast(tr('profileSaved'))}); document.getElementById('customerChangePasswordForm').addEventListener('submit',async e=>{e.preventDefault();const p1=document.getElementById('customerNewPassword').value,p2=document.getElementById('customerNewPassword2').value;if(p1!==p2){toast(tr('passwordMismatch'));return}const {error}=await supabaseClient.auth.updateUser({password:p1});if(error){toast(error.message);return}e.target.reset();toast(tr('passwordChanged'))}); document.getElementById('customerRecoveryPinForm').addEventListener('submit',async e=>{e.preventDefault();const pin=document.getElementById('customerRecoveryPin').value.trim();if(pin.length<6){toast(tr('fillRequired'));return}const {error}=await supabaseClient.rpc('set_my_recovery_pin',{p_pin:pin});if(error){toast(error.message);return}e.target.reset();toast(tr('recoveryPinSaved'))}); document.getElementById('systemSettingsForm').addEventListener('submit',e=>{e.preventDefault();saveSystemSettings()}); document.getElementById('resetSettingsBtn').addEventListener('click',()=>{if(confirm(tr('restoreDefaults')+'?'))resetSystemSettings()}); document.getElementById('sellerSearch').addEventListener('input',renderSellerOrders);document.getElementById('sellerStatusFilter').addEventListener('change',renderSellerOrders);document.getElementById('closeModalBtn').onclick=closeModal;document.getElementById('orderModal').addEventListener('click',e=>{if(e.target.id==='orderModal')closeModal()}); initializeCloudApp();