小草奶茶铺_H5/app.js · 约 8300 字符
1// 小草奶茶铺 · 逻辑层
2const $=s=>document.querySelector(s), $$=s=>document.querySelectorAll(s);
3const store={
4 get cart(){return JSON.parse(localStorage.getItem('mt_cart')||'[]')},
5 set cart(v){localStorage.setItem('mt_cart',JSON.stringify(v))},
6 get coupons(){return JSON.parse(localStorage.getItem('mt_coupons')||JSON.stringify(COUPONS))},
7 set coupons(v){localStorage.setItem('mt_coupons',JSON.stringify(v))},
8 get orders(){return JSON.parse(localStorage.getItem('mt_orders')||'[]')},
9 set orders(v){localStorage.setItem('mt_orders',JSON.stringify(v))}
10};
11function fmt(n){return '¥'+n.toFixed(2)}
12function cartCount(){return store.cart.reduce((s,i)=>s+i.num,0)}
13function cartTotal(){return store.cart.reduce((s,i)=>s+i.num*i.price,0)}
14function goodsById(id){return GOODS.find(g=>g.id==id)}
15function toast(msg){const t=document.createElement('div');t.textContent=msg;t.style.cssText='position:fixed;top:20%;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.78);color:#fff;padding:10px 20px;border-radius:20px;font-size:13px;z-index:999;transition:opacity .3s';document.body.appendChild(t);setTimeout(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},1200)}
16function addToCart(g,spec,qty){
17 const cart=store.cart;
18 const exist=cart.find(i=>i.id===g.id&&JSON.stringify(i.spec)===JSON.stringify(spec));
19 if(exist)exist.num+=qty;else cart.push({id:g.id,name:g.name,emoji:g.emoji,price:g.price,spec,num:qty});
20 store.cart=cart;toast('已加入购物车');
21 const c=cartCount();const el=$('.cb-count');if(el){el.textContent=c;el.style.display=c?'flex':'none'}
22 const t=$('.cb-total b');if(t)t.textContent=fmt(cartTotal());
23}
24/* 规格弹层 */
25function openSpec(gid){
26 const g=goodsById(gid);if(!g)return;
27 $('#ss-img').textContent=g.emoji;$('#ss-name').textContent=g.name;$('#ss-price').textContent=fmt(g.price);
28 $('#ss-note').textContent='默认规格:大杯/五分糖/正常冰';
29 const sel={cup:'大杯',sugar:'五分糖',ice:'正常冰',add:[]};
30 renderSpecs(sel);
31 $('#ss-qty').textContent='1';window._specSel=sel;window._specGood=g;
32 $('.mask').classList.add('show');$('.spec-sheet').classList.add('show');
33}
34function renderSpecs(sel){
35 const keys=[['cup','杯型'],['sugar','糖度'],['ice','冰度'],['add','加料(可多选)']];
36 let html='';
37 keys.forEach(([k,label])=>{
38 html+='<div class="spec-group"><h4>'+label+'</h4><div class="spec-options">';
39 SPECS[k].forEach(o=>{
40 const on=Array.isArray(sel[k])?sel[k].includes(o):sel[k]===o;
41 html+='<span class="spec-option'+(on?' sel':'')+'" data-k="'+k+'" data-v="'+o+'">'+o+(k==='add'&&o==='珍珠'?'(+2元)':'')+'</span>';
42 });
43 html+='</div></div>';
44 });
45 $('#spec-body').innerHTML=html;
46 $$('.spec-option').forEach(el=>el.onclick=()=>{
47 const k=el.dataset.k,v=el.dataset.v;
48 if(k==='add'){
49 const arr=window._specSel.add;
50 if(arr.includes(v)){arr.splice(arr.indexOf(v),1);el.classList.remove('sel')}
51 else{arr.push(v);el.classList.add('sel')}
52 $('#ss-price').textContent=fmt(window._specGood.price+(arr.includes('珍珠')?2:0));
53 }else{
54 window._specSel[k]=v;renderSpecs(window._specSel);
55 }
56 });
57}
58function closeSpec(){$('.mask').classList.remove('show');$('.spec-sheet').classList.remove('show')}
59function confirmSpec(){
60 const qty=parseInt($('#ss-qty').textContent)||1;
61 addToCart(window._specGood,{...window._specSel},qty);closeSpec();
62}
63/* 渲染通用 */
64function renderCartPage(){
65 const wrap=$('#cart-items');const cart=store.cart;
66 if(!cart.length){wrap.innerHTML='<div class="empty"><span class="e-icon">🧋</span>购物车还是空的<br>去点一杯吧~</div>';$('#cart-submit').style.display='none';return}
67 $('#cart-submit').style.display='block';
68 wrap.innerHTML=cart.map((i,idx)=>`
69 <div class="order-item">
70 <div class="oi-img">${i.emoji}</div>
71 <div class="oi-body">
72 <div class="oi-name">${i.name}</div>
73 <div class="oi-spec">${i.spec.cup} / ${i.spec.sugar} / ${i.spec.ice} ${i.spec.add.length?'+'+i.spec.add.join('+'):''}</div>
74 <div class="oi-foot"><span class="oi-price">${fmt(i.price*i.num)}</span>
75 <span style="display:flex;align-items:center;gap:10px">
76 <span class="sq-btn" style="width:24px;height:24px;font-size:14px" onclick="chgQty(${idx},-1)">−</span>
77 <b style="font-size:14px">${i.num}</b>
78 <span class="sq-btn" style="width:24px;height:24px;font-size:14px" onclick="chgQty(${idx},1)">+</span>
79 </span>
80 </div>
81 </div>
82 </div>`).join('');
83 $('#cart-total').textContent=fmt(cartTotal());
84 const c=cartCount();$('#cart-count').textContent=c;
85}
86function chgQty(idx,d){
87 const cart=store.cart;cart[idx].num+=d;
88 if(cart[idx].num<=0)cart.splice(idx,1);
89 store.cart=cart;renderCartPage();
90}
91function pickCoupon(){
92 const coupons=store.coupons;
93 if(!coupons.length){toast('暂无可用优惠券');return}
94 $('#coupon-name').textContent=coupons[0].name+'(-'+coupons[0].amt+'元)';
95 $('#coupon-val').textContent='-'+coupons[0].amt+'.00';
96 toast('已使用'+coupons[0].name);
97}
98function submitOrder(){
99 const cart=store.cart;
100 if(!cart.length){toast('购物车为空');return}
101 const mode=$('#mode-pick').classList.contains('sel')?'到店自取':'外卖配送';
102 const coupon=parseInt($('#coupon-val').textContent)||0;
103 const code='6'+Math.floor(1000+Math.random()*9000);
104 const o={id:'MT'+Date.now().toString().slice(-8),items:cart.map(i=>({name:i.name,emoji:i.emoji,spec:i.spec,num:i.num,price:i.price})),total:cartTotal()-coupon,mode,code,time:new Date().toLocaleString('zh-CN',{hour:'2-digit',minute:'2-digit'}),status:1};
105 store.orders=[o,...store.orders];
106 store.cart=[];store.coupons=store.coupons.slice(1);
107 toast('下单成功,取餐码 '+code);
108 setTimeout(()=>location.href='orders.html',800);
109}
110function renderOrdersPage(){
111 const wrap=$('#orders-list');const orders=store.orders;
112 if(!orders.length){wrap.innerHTML='<div class="empty"><span class="e-icon">📋</span>还没有订单<br>去点一杯吧~</div>';return}
113 wrap.innerHTML=orders.map(o=>`
114 <div class="card">
115 <div style="display:flex;justify-content:space-between;font-size:12px;color:#a08c6f;margin-bottom:10px">
116 <span>${o.mode} · ${o.time}</span><span style="color:var(--primary);font-weight:600">${['待取餐','制作中','已完成'][o.status]}</span>
117 </div>
118 ${o.items.map(i=>'<div style="display:flex;justify-content:space-between;font-size:13px;padding:3px 0"><span>'+i.emoji+' '+i.name+' ×'+i.num+'<br><small style="color:#b9a887">'+i.spec.cup+'/'+i.spec.sugar+'/'+i.spec.ice+'</small></span><b>'+fmt(i.price*i.num)+'</b></div>').join('')}
119 <div style="text-align:right;font-size:14px;font-weight:700;margin-top:8px;border-top:1px dashed #eee;padding-top:8px">合计 <span style="color:var(--red)">${fmt(o.total)}</span></div>
120 <div class="status-steps">
121 ${[['已下单','✓'],['制作中','🔥'],['待取餐','☕']].map((s,i)=>'<div class="st '+(i<=o.status?'done':'')+'"><span class="dot">'+s[1]+'</span>'+s[0]+'</div>').join('')}
122 </div>
123 ${o.code?'<div class="pickup-code"><div class="pc-tip">取餐码</div><div class="pc-num">'+o.code+'</div><div class="pc-tip">到店报取餐码即可取餐</div></div>':''}
124 </div>`).join('');
125}
126function renderMine(){
127 $('#u-name').textContent=USER.name;$('#u-level').textContent=USER.level;
128 $('#u-points').textContent=USER.points;$('#u-balance').textContent=USER.balance.toFixed(1);
129 $('#u-coupons').textContent=USER.coupons+store.coupons.length;
130 $('#m-points').textContent=USER.points;$('#m-coupons').textContent=USER.coupons+store.coupons.length;
131}
132function renderMember(){
133 $('#mh-name').textContent=USER.name;$('#mh-level').textContent=USER.level;
134 $('#mh-points').textContent=USER.points;$('#mh-balance').textContent=USER.balance.toFixed(1);
135 $('#mh-coupons').textContent=USER.coupons+store.coupons.length;
136 $('#lt-name').textContent=USER.level;
137 const idx=USER.levelIdx;const next=LEVELS[Math.min(idx+1,3)];
138 $('#lt-next').textContent='距'+(idx>=3?'最高等级':next.name)+'还差 '+(idx>=3?0:600)+' 积分';
139}
140