编辑页面

修改后将从当前时间重新计算过期时间
提交后可在后台「系统管理 → 操作日志」查看审批状态;本演示中该申请状态为「待审批」。
`;}}, memberRecords:{ title:'消费记录', navbar:'back', render:()=>{ const m=findMember(state.params.id||'MEL-2026-08841'); const all=[...m.recent,{d:'2026-09-05 11:30',a:'$45.00',p:'+67',src:'小票 AI 识别'},{d:'2026-08-15 12:20',a:'$32.00',p:'+48',src:'店员双录'}]; return `
会员${m.name} · ${m.id}
累计消费${m.spend} · ${m.visits} 次到店
客单价$${(money(m.spend)/Math.max(1,m.visits)).toFixed(2)}
${all.map(r=>`
${r.a}${r.p} 积分
🕐 ${r.d}📍 ${DB.staff.shop}来源:${r.src}
`).join('')}
消费记录由「店员双录 / 小票 AI 识别」同步生成,用户在会员小程序「消费记录」中看到的为同一份数据。
`;}}, /* ==================== 录单 / 消费数据同步(核心) ==================== */ sync:{ title:'录单', navbar:'none', tab:'sync', render:()=>`
为什么需要录单:商家 POS 系统封闭、不开放接口,顾客在 POS 点单的消费数据无法自动同步,需由店员通过「双录」或「小票 AI 识别」回填到会员账户。
🧾${DB.bills.length}待入库单据
✅${DB.today.verified}今日已入库
🎯${DB.staff.accuracy}本月识别准确率

新建录单

🔗关联会员
📝双录录单
📷拍小票识别
✔️核对入库

录单流程

1关联会员
2双录 / 拍小票
3核对修正
4确认入库
提交入库后系统自动计算积分(实付金额 × 会员等级倍数)并通知会员,数据同步至后台与用户端小程序。
${DB.bills.length?DB.bills.slice(0,3).map(b=>billCard(b)).join(''):`
🎉
暂无待入库单据
`}

更多

📋
同步记录查询按订单号 / 会员 / 来源查询历史录单
›
🚩
异常处理疑似重复单 · 识别失败 · 未匹配会员 · 金额异常
${DB.exceptions.filter(e=>e.status!=='已处理').length} 待处理
📈
门店数据录单量、会员贡献、券核销概览
›
本模块与后台「消费数据同步」6 个页签(会员关联 / 双录录入 / 小票识别 / 核对入库 / 同步记录 / 异常处理)一一对应。
`}, syncLink:{ title:'关联会员', navbar:'back', render:()=>`
顾客点餐前先完成会员关联,之后录单时无需重复填写会员信息。

匹配结果

Emma Wang${lvlTag('黄金会员')}
卡号 MEL-2026-08841📱 0412 888 128最近到店 2026-09-22
可用积分 2,860
关联动作会写入后台「会员关联」操作日志;未注册顾客可先
`}, syncDouble:{ title:'双录录单', navbar:'back', render:()=>{ const m=findMember(state.params.member||'MEL-2026-08841'); return `
主同步方式:按 POS 小票内容手工录入订单,提交后系统自动按会员等级发放积分。
更换

菜品明细

从菜品库选择

金额

AUD $
AUD $
AUD $
预计发放积分:63.50 × ${m.rate} 倍 = ${Math.round(63.5*m.rate)} 积分(${m.level})

本店今日双录记录

12:12 · Jason Wu3 道菜 · $42.00 · 店员 Ken
已入库
11:20 · Cindy Liu6 道菜 · $156.00 · 店员 Ken
已入库
10:48 · 散客2 道菜 · $32.00 · 店员 Amy
未匹配会员
对应后台「消费数据同步 → 双录录入」页签。
`;}}, syncCamera:{ title:'拍小票识别', navbar:'back', render:()=>`
将 POS 小票放平,尽量完整入框,避免遮挡与反光,识别准确率更高(当前制式准确率 ${DB.staff.accuracy})。
MELBOURNE DINING CLUB
CBD Store · 258 Collins St
Date2026-09-24 12:36
OrderPOS-1208
StaffLISA
Wagyu Burger28.00
Salmon Salad26.00
Iced Latte x216.00
Fries8.50
SUBTOTAL78.50
COUPON-15.00
TOTAL63.50
对准小票,点击下方快门
识别结果会自动填充用餐时间、菜品明细与金额,低置信度字段将标红提示人工确认。
`}, syncOcr:{ title:'识别中', navbar:'back', render:()=>`
🧠
正在识别小票…
AI / OCR 引擎解析订单号、用餐时间、
菜品明细与金额字段
正在上传图像…
单据号${state.params.no||'SO-20260924-0081'}
门店${DB.staff.shop}
识别引擎Receipt-OCR v2.4(制式小票)
`}, syncOcrResult:{ title:'识别结果核对', navbar:'back', render:()=>`
识别完成,整体置信度 96%。其中 实付金额 字段为低置信度,请人工确认后再入库。
96%
MELBOURNE DINING CLUB
CBD Store · 258 Collins St
Date2026-09-24 12:36
OrderPOS-1208
StaffLISA
Wagyu Burger28.00
Salmon Salad26.00
Iced Latte x216.00
Fries8.50
SUBTOTAL78.50
COUPON-15.00
TOTAL63.50

识别字段(可修正)

99%
97%
98%
95%
78% 需确认
查看
关联依据:小票无会员信息,系统按「扫码时点最近的会员关联记录」自动匹配,可点击修改。
对应后台「小票上传识别」页签,识别结果与修正记录会一并保存。
`}, syncConfirm:{ title:'核对入库', navbar:'back', render:()=>{ const b=findBill(state.params.no||'SO-20260924-0081'); const m=b.member?findMember(b.member):null; const pay=money(b.amount); const pts=m?Math.round(pay*m.rate):0; return `
核对以下信息,确认无误后入库。入库后将生成会员消费记录并自动发放积分。
单据号${b.no}
门店 / 店员${b.shop} · ${b.staff}
用餐时间${b.time}
数据来源${b.src}${b.conf?' · 置信度 '+b.conf+'%':''}
POS 单号${b.pos||'—'}
优惠券${b.coupon||'未使用'}
关联会员${m?m.name+' · '+m.id:'散客(未匹配会员)'}
菜品小计${b.goods}
优惠金额-$${(money(b.goods)-pay).toFixed(2)}
实付金额${b.amount}
${m?`
入库后 ${m.name} 将获得 ${pts} 积分(实付 ${b.amount} × ${m.rate} 倍),积分余额 ${m.points.toLocaleString()} → ${(m.points+pts).toLocaleString()},会员小程序实时可见。
` :`
该单据暂未匹配会员,入库后记录仅作门店流水,积分无法发放。建议先补登记会员或挂起。
`}
${m?`` :`
`}
对应后台「消费数据同步 → 核对入库」,入库动作触发积分发放并写入同步记录(本演示会实时更新待入库数量与会员积分)。
`;}}, syncDone:{ title:'入库成功', navbar:'none', render:()=>{ const p=state.params; const title=p.mode==='points'?'积分发放成功':(p.mode==='double'?'录单成功':'核对入库成功'); return `
✓
${title}
数据已同步至管理后台与会员小程序
单据号${p.no||'—'}
会员${p.member||'—'}
实付金额${p.pay||'—'}
发放积分+${p.pts||0}
积分余额${(p.before||0).toLocaleString()} → ${(p.after||0).toLocaleString()}
操作店员${DB.staff.name} · ${DB.staff.shop}
已完成的同步动作:
· 生成会员消费记录,用户在「我的 - 消费记录」中可见
· 积分已发放,用户可收到积分到账提醒
· 门店今日已入库 +1,后台同步记录状态更新为「已入库」
`;}}, syncPending:{ title:'待入库单据', navbar:'back', render:()=>`
🧾${DB.bills.length}待入库
✅${DB.today.verified}今日已入库
⏱️42 秒平均核对时长
全部 ${DB.bills.length} 待核对 ${DB.bills.filter(b=>b.type==='待核对').length} 未匹配 ${DB.bills.filter(b=>b.type==='未匹配会员').length} 识别失败 ${DB.bills.filter(b=>b.type==='识别失败').length}
${DB.bills.length?DB.bills.map(b=>billCard(b)).join(''):`
🎉
全部单据已入库
暂无待处理单据
`}
点击单据卡片可进入核对入库页;「批量核对通过」会为所有已匹配会员的单据发放入库积分。
`}, syncRecords:{ title:'同步记录', navbar:'back', render:()=>`
筛选
全部来源店员双录小票 AI 识别
${DB.syncRecords.map(r=>`
${r.no}${stTag(r.status)}
👤 ${r.member}📍 ${r.shop}🕐 ${r.time} 🧾 ${r.src} · 店员 ${r.staff}
${r.amount} ${r.status==='待核对'?'去核对 ›':'查看明细 ›'}
`).join('')}
共 386 条 · 已加载最近 6 条
对应后台「消费数据同步 → 同步记录查询」,支持按订单号、会员、门店、店员、来源与时间筛选并导出。
`}, syncException:{ title:'异常处理', navbar:'back', render:()=>`
♻️4疑似重复单
🚫5识别失败单
❓9未匹配会员
🚩2金额异常
${DB.exceptions.map(e=>`
${e.no} ${e.type} ${e.status}
${e.desc}
📍 ${e.shop} · 🕐 ${e.time}
${e.act.map(a=>``).join('')}
`).join('')}
异常单据来源于录单与识别环节;处理动作(挂起 / 转双录 / 合并 / 修正金额)会留痕并同步后台「异常处理」页签。
`}, /* ==================== 券与积分 ==================== */ couponVerify:{ title:'券核销', navbar:'back', right:{t:'手册',f:'openCouponRule()'}, render:()=>`
扫描顾客小程序中的优惠券二维码

或手工输入券码

合规提示:核销前请确认券未过期、未使用,且订单满足使用门槛(如满 $60 减 $15 需订单金额 ≥ $60)。

今日核销记录

导出
${DB.verifyRecords.map(r=>`
${r.status==='成功'?'🎫':'↩️'}
${r.m} ${r.t}${r.c} · ${r.a} · 核销人 ${r.by}
${r.status}
`).join('')}
核销记录实时同步后台「优惠券管理 → 核销记录」,并计入门店经营分析中的券带动消费。
`}, /* ==================== 套餐核销(到店付款) ==================== */ pkgVerify:{ title:'套餐核销', navbar:'back', right:{t:'说明',f:'openPkgRule()'}, render:()=>{ const list=DB.pkgOrders.filter(o=>o.status!=='已完成'); return `
扫描顾客小程序中的套餐核销码

或手工输入核销码

到店付款订单:核销套餐后需当场收款(微信 / 银行卡 / 现金),收款完成订单才会标记为「已完成」;线上已支付订单只需核销,不再收款。

待到店套餐订单

${list.length} 单
${list.map(o=>{ const m=findMember(o.member); return `
${o.paid?'🎟️':'💵'}
${m.name} · ${o.title} ×${o.qty} ${o.no} · ${o.store} · ${o.paid?'线上已支付,仅需核销':'到店付款,核销后收款'}
$${o.amount.toFixed(2)}
${o.paid?'待核销':'待收款'}
`;}).join('')}

今日核销记录

导出
${DB.pkgVerifyRecords.map(r=>`
✅
${r.m} ${r.t} ${r.p} · ${r.a} · ${r.pay} · 核销人 ${r.by}
已核销
`).join('')}
核销与收款记录同步后台「营销活动 → 特价套餐」及「门店经营分析」,并计入今日营业额。
`; }}, pkgVerifyDetail:{ title:'套餐核销', navbar:'back', render:()=>{ const o=DB.pkgOrders.find(x=>x.no===state.params.no)||DB.pkgOrders[0]; const m=findMember(o.member); const paySel=state.pkgPay||'微信支付'; return `
${o.paid?'线上已支付':'到店付款 · 待收款'}
${o.paid?'本单已支付':'本单应付'}
$${o.amount.toFixed(2)}
${o.paid?o.pay+' 已支付 · 核销即可,无需重复收款':'核销后向顾客现场收取'}

套餐信息

套餐${o.title} × ${o.qty}
核销码${o.code}
使用门店${o.store}
就餐时段${o.slot}
有效期至${o.valid}
订单号${o.no}

会员信息

${m.name.slice(0,1)}
${m.name} ${lvlTag(m.level)}${m.id} · 积分 ${m.points.toLocaleString()} · 累计到店 ${m.visits} 次
›
${o.paid?'':`

现场收款方式

${['微信支付','银行卡','现金'].map(p=>`
${p}
`).join('')}
选择顾客实际使用的方式,收款完成后订单自动完成并计入营业额。
`}
核销后套餐不可再次使用;如操作有误,请在 5 分钟内到后台「同步记录」发起撤销。
`; }}, pkgVerifyDone:{ title:'', navbar:'none', render:()=>{ const o=DB.pkgOrders.find(x=>x.no===state.params.no)||DB.pkgOrders[0]; const m=findMember(o.member); return `
✓
核销成功
${state.params.pay==='已支付'?'套餐已核销,顾客可直接使用':'套餐已核销,款项已收取'}
${state.params.pay==='已支付'?'':`
$${o.amount.toFixed(2)}
${state.params.pay} · 已计入今日营业额
`}
会员${m.name} · ${m.id}
套餐${o.title} × ${o.qty}
门店 / 时段${o.store} · ${o.slot}
订单号${o.no}
`; }}, couponGrant:{ title:'优惠券发放', navbar:'back', render:()=>{ const m=findMember(state.params.id||'MEL-2026-08841'); return `
面向到店顾客的即时发券,通常用于安抚、补偿或引导复购,发放后会通知会员并同步后台券管理。
更换

选择券模板

${DB.coupons.map((c,i)=>`
🎫
${c.t} ${c.type}${c.cond} · 有效期至 ${c.exp} · 剩余 ${c.left}
${i===0?'✓':''}
`).join('')}
张
券会进入会员小程序「我的优惠券」,用券 / 过期数据回流到后台券管理看板。
`;}}, pointsGrant:{ title:'积分发放', navbar:'back', render:()=>{ const m=findMember(state.params.id||'MEL-2026-08841'); return `
用于活动奖励、服务补偿等场景的即时积分发放;日常消费积分由录单入库时自动计算,无需在此操作。
更换
发放后积分余额:${m.points.toLocaleString()} → ${(m.points+300).toLocaleString()}(演示按 300 分计算,实际以输入值为准)
积分发放会同时通知会员,并写入后台「积分管理 → 积分发放」记录与操作日志。
`;}}, /* ==================== 营销活动 ==================== */ activity:{ title:'门店活动', navbar:'back', render:()=>`
以下为总部在后台配置、当前适用于本门店的营销活动。店员可查看规则并向顾客解释、执行核销与赠品登记。
${DB.activities.map(a=>`
${a.title}${a.tag} ${a.status}
${a.sub}
🗓 ${a.range} · 🏬 ${a.stores}
${a.stat.map(s=>`
${s[0]} ${s[1]}
`).join('')}
`).join('')}
活动由后台「营销活动管理」创建与上下架,本端不做编辑,仅执行与反馈。
`}, activityDetail:{ title:'活动详情', navbar:'back', render:()=>{ const a=DB.activities.find(x=>x.id===state.params.id)||DB.activities[0]; return `
${a.tag}
${a.title}
${a.sub}
活动时间${a.range}
适用门店${a.stores}
活动状态${a.status}
门店数据${a.stat.map(s=>s[0]+' '+s[1]).join(' · ')}

活动规则

${a.rule}
执行要点:${a.need}

到店执行

核销与赠品数据会实时汇总到后台「营销活动管理」与「营销效果分析」,用于计算活动 ROI。
`;}}, giftRegister:{ title:'满赠登记', navbar:'back', render:()=>`
满赠活动:满 $120 赠招牌甜点(CBD / Docklands 店 · 2026-09-15 ~ 10-20)。本页登记后用于后台统计赠品成本与带动消费。
更换
AUD $
本桌消费 $168.00 ≥ 门槛 $120.00,符合满赠条件;每桌限 1 份,请勿重复登记。

本店今日赠品登记

12:58 · 桌 A08消费 $142.00 · 甜点拼盘 × 1
已登记
12:10 · 桌 B03消费 $126.00 · 甜点拼盘 × 1
已登记
对应后台「营销活动管理 → 满赠活动」的赠品登记与成本统计。
`}, /* ==================== 数据 ==================== */ dataBoard:{ title:'门店数据', navbar:'back', render:()=>`
今日营业额(已入库订单)
${DB.today.revenue}
客单价 ${DB.today.avgTicket} · 会员消费占比 68% · 较昨日 +12.4%
👥${DB.today.newMembers}新增会员
🧾${DB.today.doubleRec+DB.today.ocrRec}今日录单
🎫${DB.today.couponUsed}券核销

录单来源占比

店员双录 · ${DB.today.doubleRec} 单小票 AI 识别 · ${DB.today.ocrRec} 单
小票识别占比越高,手工录入成本越低;当前识别准确率 ${DB.staff.accuracy},建议引导店员优先拍小票。

近 7 日录单量

${[[30,18],[46,26],[26,14],[52,30],[38,22],[58,34],[44,24]].map((v,i)=>` ${['18','19','20','21','22','23','24'][i]} ${v[1]}`).join('')}
9/18 - 9/24 · 单位:单

本店 TOP 会员

全部 ›
${DB.members.slice().sort((a,b)=>money(b.spend)-money(a.spend)).slice(0,4).map((m,i)=>`
${i===0?'🥇':(i===1?'🥈':(i===2?'🥉':'4'))}
${m.name} ${lvlTag(m.level)}累计消费 ${m.spend} · ${m.visits} 次到店 · 积分 ${m.points.toLocaleString()}
›
`).join('')}

我的录单质量

本月录单量86 单
核对一次通过率96.4%
平均核对时长42 秒 / 单
异常单据占比2.1%
门店数据的完整分析(客户画像、营销效果、门店经营对比)在管理后台「数据分析」模块查看,本端只做门店 / 店员视角的轻量看板。
`}, /* ==================== 我的 ==================== */ profile:{ title:'我的', navbar:'none', tab:'profile', render:()=>`
LC
${DB.staff.name} ${DB.staff.role} · 工号 ${DB.staff.id}
${DB.staff.shop} · 入职 ${DB.staff.since}
切换门店
📝86本月录单
👤42引导注册
🎫214券核销

我的工作

🔔
消息通知总部通知 · 待办提醒 · 审批结果
${DB.messages.some(m=>m.unread)?`${DB.messages.filter(m=>m.unread).length} 条未读`:'
›
'}
📋
我的录单记录本月 86 单 · 待核对 ${DB.bills.filter(b=>b.staff===DB.staff.name.split(' ')[0]).length} 单
›
🎫
我的核销记录今日核销 ${DB.verifyRecords.filter(r=>r.status==='成功').length} 张
›
🎟️
套餐核销与收款今日核销 ${DB.today.pkgVerify} 单 · 待收款 ${DB.pkgOrders.filter(o=>o.status==='待付款').length} 单
›
⚖️
积分调整申请1 条待审批(+500 积分)
待审批
📈
我的业绩数据录单质量 · 一次通过率 96.4%
›

门店与账号

🏬
切换门店当前:${DB.staff.shop}
›
🕐
门店营业与打卡今日 10:00-22:00 · 已打卡 10:24
›
🔑
账号与权限${DB.staff.role} · 可执行录单 / 发券 / 核销
›
⚙️
设置通知提醒 · 打印小票 · 清除缓存
›
商户端定位:店员 / 店长的到店执行工具 —— 会员注册与查询、消费录单与核对、券核销、积分发放、活动执行。
总部级配置(等级规则、券模板、活动创建、数据分析)在管理后台完成。
`}, messages:{ title:'消息通知', navbar:'back', right:{t:'全部已读',f:'readAll'}, render:()=>` ${DB.messages.map(m=>`
${m.ic}
${m.type} ${m.time}
${m.title}
${m.body.slice(0,42)}…
`).join('')}
消息由总部在后台「消息触达管理」推送,或由系统按规则自动触发(待办、审批结果、数据提醒)。
`}, settings:{ title:'设置', navbar:'back', render:()=>`
🔔
新消息提醒待办、审批结果、总部通知
已开启
🔊
录单成功提示音入库成功后播放提示音
已开启
🖨️
小票打印机Printer-A12 · 已连接
›
⚡
录单默认方式小票 AI 识别优先(推荐)
›
🧹
清除缓存当前 2.4 MB
›
ℹ️
关于商户端v1.0.0 · 原型演示版
›
`}, }; /* ==================== 引擎 ==================== */ let state={page:'login',params:{},stack:[]}; function el(id){return document.getElementById(id);} function go(p,params){ state.stack.push({page:state.page,params:state.params}); state.page=p; state.params=params||{}; render(); el('content').scrollTop=0; } function back(){ const s=state.stack.pop(); if(s){ state.page=s.page; state.params=s.params||{}; render(); el('content').scrollTop=0; } else tabGo('workbench'); } function tabGo(id){ state.stack=[]; state.page=id; state.params={}; render(); el('content').scrollTop=0; } function render(){ const p=PAGES[state.page]||PAGES.workbench; const nb=el('navbar'); if(p.navbar==='none'){ nb.style.display='none'; } else{ nb.style.display='flex'; nb.className='navbar'+(p.blue?' blue':''); nb.innerHTML=(p.navbar==='back'?`
‹
`:'') +`
${p.title||''}
` +(p.right?`${p.right.t}`:''); } const c=el('content'); c.innerHTML=`
${p.render()}
`; c.className='content'+(p.tab?' hasTab':''); const th=el('tabbarHost'); if(p.tab){ th.innerHTML=`
${TABS.map(t=>` `).join('')}
`; } else th.innerHTML=''; const q=el('qrbox'); if(q) drawQR(q); if(state.page==='syncOcr') runOcrAnim(); else clearInterval(window._ocrT); } function runOcrAnim(){ clearInterval(window._ocrT); let v=8; const steps=['正在上传图像…','定位小票区域…','识别订单字段…','解析菜品明细…','计算金额总和…']; window._ocrT=setInterval(()=>{ v+=Math.random()*16+9; if(v>=100) v=100; const bar=el('ocrBar'), st=el('ocrStep'); if(bar) bar.style.width=v+'%'; if(st) st.textContent=steps[Math.min(steps.length-1,Math.floor(v/21))]; if(v>=100){ clearInterval(window._ocrT); setTimeout(()=>{ if(state.page==='syncOcr') ocrNext(); },420); } },300); } function readAll(){ DB.messages.forEach(m=>m.unread=false); render(); toast('已将全部消息标记为已读'); } const TABS=[ {id:'workbench',name:'工作台',icon:''}, {id:'member',name:'会员',icon:''}, {id:'sync',name:'录单',icon:''}, {id:'profile',name:'我的',icon:''} ]; function toast(msg){ const t=el('toast'); t.textContent=msg; t.classList.add('show'); clearTimeout(window._tt); window._tt=setTimeout(()=>t.classList.remove('show'),2000); } function openSheet(title,html){ el('sheetBody').innerHTML=`

${title}

${html} `; el('sheet').classList.add('show'); } function closeSheet(){ el('sheet').classList.remove('show'); } function openMsg(id){ const m=DB.messages.find(x=>x.id===id); m.unread=false; openSheet(m.type,`
${m.title}
${m.time}
${m.body}
`); render(); } function drawQR(host){ host.innerHTML=''; const N=21; let seed=20260924; const rnd=()=>{ seed=(seed*1103515245+12345)&0x7fffffff; return seed/0x7fffffff; }; const grid=Array.from({length:N},()=>Array(N).fill(0)); const finder=(r,c)=>{ for(let i=0;i<7;i++)for(let j=0;j<7;j++){ const on=(i===0||i===6||j===0||j===6)||(i>=2&&i<=4&&j>=2&&j<=4); grid[r+i][c+j]=on?1:0; } }; finder(0,0); finder(0,N-7); finder(N-7,0); for(let i=0;i=N-8)||(i>=N-8&&j<8); if(!inF) grid[i][j]=rnd()>0.5?1:0; } for(let i=0;i{ state.stack=[]; state.page='workbench'; render(); },300); } function pickShop(name){ const i=DB.shops.findIndex(s=>s.name===name); if(i>=0){ DB.staff.shop=name; DB.staff.shopIdx=i; } toast('已切换到 '+name); render(); } function logout(){ openSheet('退出登录',`
退出后需重新登录才能录单与核销,确认退出当前账号?
`); } function scanMemberDone(){ toast('已识别会员码:MEL-2026-08841'); setTimeout(()=>go('memberDetail',{id:'MEL-2026-08841'}),400); } function searchMember(){ const inp=el('searchTel'); const v=inp?String(inp.value).replace(/\s/g,''):''; if(!v){ toast('请输入手机号或会员卡号'); return; } if(v.length<4){ toast('请输入完整的手机号或卡号后 4 位'); return; } go('memberList',{q:v}); } function toggleNext(elm){ if(elm) elm.classList.toggle('on'); } function submitDouble(){ const inp=el('dAmount'); const member=state.params.member||'MEL-2026-08841'; if(inp&&!String(inp.value).trim()){ toast('请填写订单金额'); return; } const m=findMember(member); const pay=parseFloat(String(inp.value).replace(/[^0-9.]/g,''))||63.5; const pts=Math.round(pay*m.rate); m.points+=pts; DB.syncRecords.unshift({no:'SO-20260924-0082',member:m.name,shop:DB.staff.shop,time:'09-24 12:36',amount:'$'+pay.toFixed(2),src:'店员双录',status:'已入库',staff:DB.staff.name.split(' ')[0]}); DB.today.doubleRec++; DB.today.points=(1240+pts).toLocaleString(); state.page='syncDone'; state.params={mode:'double',no:'SO-20260924-0082',member:m.name,pts:pts,before:m.points-pts,after:m.points,pay:'$'+pay.toFixed(2)}; render(); el('content').scrollTop=0; } function shootReceipt(){ go('syncOcr',{no:'SO-20260924-0081'}); } function ocrNext(){ go('syncOcrResult',{no:state.params.no||'SO-20260924-0081'}); } function verifyBill(no){ const b=findBill(no); const m=b.member?findMember(b.member):null; const pay=money(b.amount); const pts=m?Math.round(pay*m.rate):0; if(m){ m.points+=pts; m.visits++; } const i=DB.bills.findIndex(x=>x.no===no); if(i>=0) DB.bills.splice(i,1); const r=DB.syncRecords.find(x=>x.no===no); if(r){ r.status='已入库'; r.amount=b.amount; } DB.today.verified++; state.page='syncDone'; state.params={mode:'verify',no:no,member:m?m.name:'散客',pts:pts,before:m?m.points-pts:0,after:m?m.points:0,pay:b.amount}; render(); el('content').scrollTop=0; } function holdBill(){ toast('已挂起,等待会员补登记录后自动关联'); } function mergeBill(){ toast('已标记为重复单并与 POS-1203 合并'); } function viewBill(){ openSheet('金额异常明细',`
订单号SO-20260922-0055
菜品明细合计$92.00
小票实付$68.00
差额-$24.00
已在 09-22 20:40 修正实付金额为 $92.00 并重新入库,会员积分已补发 24 分。
`); } function verifyCoupon(){ const m=findMember('MEL-2026-08841'); DB.today.couponUsed++; DB.verifyRecords.unshift({t:'10:24',m:m.name,c:'满 $60 减 $15',a:'-$15.00',by:DB.staff.name,status:'成功'}); DB.today.verified++; openSheet('核销成功',`
✓
券已核销
会员${m.name} · ${m.id}
券名称满 $60 减 $15
券码MEL15-0826-4102
抵扣金额-$15.00
核销人${DB.staff.name} · 10:24
核销结果已实时同步到管理后台「优惠券管理 → 核销记录」,并通知会员。
`); } function verifyByCode(){ const inp=el('couponCode'); if(inp&&!String(inp.value).trim()){ toast('请输入券码或扫描券二维码'); return; } verifyCoupon(); } /* ---------- 套餐核销 / 到店付款现场收款 ---------- */ function openPkgRule(){ openSheet('套餐核销规则',`
核销方式扫顾客核销码 或 手工输入
线上已支付核销套餐,无需收款
到店付款核销套餐后现场收款
收款方式微信 / 银行卡 / 现金
一单仅可核销一次;核销后套餐立即失效,收款完成订单自动标记为「已完成」。如操作有误,请在 5 分钟内到后台撤销。
`); } function scanPkg(){ const o=DB.pkgOrders.find(x=>x.status==='待付款')||DB.pkgOrders[0]; toast('已识别套餐核销码:'+o.code); setTimeout(()=>go('pkgVerifyDetail',{no:o.no}),400); } function findPkgOrder(){ const inp=el('pkgCode'); const code=String(inp?inp.value:'').replace(/\s/g,''); if(!code){ toast('请输入核销码或扫描顾客二维码'); return; } const o=DB.pkgOrders.find(x=>x.code.replace(/\s/g,'')===code); if(!o){ toast('未找到该核销码,请核对后重试'); return; } if(o.status==='已完成'){ toast('该订单已核销,请勿重复操作'); return; } go('pkgVerifyDetail',{no:o.no}); } function pickPkgPay(p){ state.pkgPay=p; const y=el('content').scrollTop; render(); el('content').scrollTop=y; } function verifyPkgOrder(no){ const o=DB.pkgOrders.find(x=>x.no===no); if(!o) return; if(o.status==='已完成'){ toast('该订单已核销,请勿重复操作'); return; } const wasPaid=o.paid; const pay=wasPaid?'已支付':(state.pkgPay||'微信支付'); const m=findMember(o.member); o.status='已完成'; if(wasPaid){ DB.pkgVerifyRecords.unshift({t:'12:04',m:m.name,p:o.title+' ×'+o.qty,a:'$'+o.amount.toFixed(2),pay:'线上已支付',by:DB.staff.name}); }else{ o.paid=true; o.pay=pay; DB.today.pkgVerify=(DB.today.pkgVerify||0)+1; DB.today.pkgRevenue='$'+(money(DB.today.pkgRevenue)+o.amount); DB.today.revenue='$'+(money(DB.today.revenue)+o.amount).toLocaleString(); DB.pkgVerifyRecords.unshift({t:'12:04',m:m.name,p:o.title+' ×'+o.qty,a:'$'+o.amount.toFixed(2),pay,by:DB.staff.name}); } state.page='pkgVerifyDone'; state.params={no:o.no,pay}; render(); el('content').scrollTop=0; } function grantCouponTo(id){ const m=findMember(id); const c=DB.coupons[0]; m.coupons=m.coupons||[]; m.coupons.push({t:c.t,s:'unused'}); openSheet('发券成功',`
🎫
优惠券已发放
会员${m.name} · ${m.id}
券名称${c.t}
有效期至 ${c.exp}
发放人${DB.staff.name}(${DB.staff.shop})
会员可在小程序「我的 - 我的优惠券」中查看,用券数据会回流到后台券管理。
`); render(); } function grantPointsGo(id){ state.stack.push({page:state.page,params:state.params}); state.page='pointsGrant'; state.params={id:id}; render(); el('content').scrollTop=0; } function submitPoints(){ const inp=el('pgAmount'); const v=inp?parseInt(String(inp.value).replace(/[^0-9]/g,''),10):0; if(!v){ toast('请输入发放积分数值'); return; } const m=findMember(state.params.id||'MEL-2026-08841'); m.points+=v; DB.today.points=(parseInt(DB.today.points.replace(/,/g,''),10)+v).toLocaleString(); state.page='syncDone'; state.params={mode:'points',no:'—',member:m.name,pts:v,before:m.points-v,after:m.points,pay:'—'}; render(); el('content').scrollTop=0; } function submitAdjust(){ const inp=el('adReason'); if(inp&&!String(inp.value).trim()){ toast('请填写调整原因,便于管理员审批'); return; } toast('已提交积分调整申请,等待店长 / 管理员审批'); setTimeout(back,600); } function regMember(){ const tel=el('regTel'), nm=el('regName'); if(nm&&!String(nm.value).trim()){ toast('请填写会员姓名'); return; } if(tel&&!String(tel.value).trim()){ toast('请填写手机号'); return; } const m={id:'MEL-2026-08843',name:String(nm.value).trim(),tel:String(tel.value).trim(),level:'普通会员',rate:1,points:200, spend:'$0.00',visits:0,last:'—',seg:'新客',src:'到店店员引导',join:'2026-09-24',birth:'—',taste:'—',coupons:[{t:'新人 $10 无门槛券',s:'unused'}],recent:[]}; DB.members.unshift(m); DB.today.newMembers++; openSheet('注册成功',`
✓
会员注册成功
会员卡号${m.id}
姓名 / 手机号${m.name} · ${m.tel}
初始等级普通会员
新人礼$10 无门槛券 + 200 积分
会员数据已同步至管理后台「会员管理 → 会员档案」,渠道来源记为「到店店员引导」。
`); } function giftReg(){ const amt=el('giftAmount'); if(amt&&!String(amt.value).trim()){ toast('请填写本桌消费金额'); return; } openSheet('赠品登记成功',`
🎁
满赠已登记
活动满 $120 赠招牌甜点
本桌消费$${amt?String(amt.value).replace(/[^0-9.]/g,''):'168.00'}
赠品双人甜点拼盘 × 1
登记门店 / 店员${DB.staff.shop} · ${DB.staff.name}
赠品成本与带动消费数据已回传后台「营销活动管理 → 满赠活动」统计。
`); } function actVerify(id){ const a=DB.activities.find(x=>x.id===id)||DB.activities[0]; openSheet('活动核销',`
活动${a.title}
适用门店${a.stores}
核销方式${a.need}
确认顾客已满足活动条件后点击下方按钮完成核销,核销数据实时同步至后台活动统计。
`); } function sendMsgTest(){ toast('已向 12 位目标会员发送提醒(数据同步至后台消息触达)'); } function shopOn(i){ toast(DB.shops[i].name+' 今日已营业,打卡成功'); } /* ==================== 说明弹层 ==================== */ function openCouponRule(){ openSheet('核销规则',`
可核销券满减 / 折扣 / 兑换 / 满赠
核销方式扫会员券码 或 输入券码
撤销时限核销后 5 分钟内可在后台撤销
门槛校验系统自动校验订单金额
一张券仅可核销一次;已过期券无法核销,请引导顾客在小程序中查看有效期。
`); } function openShopInfo(){ openSheet('门店今日营业',`
门店${DB.staff.shop}
营业时间10:00 - 22:00
在岗员工Lisa · Ken · Amy
今日打卡10:24 已打卡
门店营业信息与排班由管理员在后台「门店与员工」中维护。
`); } function openAccountInfo(){ openSheet('账号与权限',`
姓名${DB.staff.name}
角色${DB.staff.role}
工号${DB.staff.id}
手机号${DB.staff.phone}
可执行操作录单 / 核对入库 / 发券 / 核销 / 发积分
角色与权限由管理员在后台「门店与员工 → 角色权限」中配置:店长可直接审批积分调整,店员需提交审批。
`); } function openAbout(){ openSheet('关于商户端',`
版本v1.0.0(原型演示)
端类型商户端小程序(店员 / 店长)
配套端用户端小程序 · 管理后台
本原型为高保真交互演示,数据均为模拟数据,所有操作不会影响真实业务数据。
`); } /* 支持 ?p=页面ID 直达 */ (function(){ try{ const sp=new URLSearchParams(location.search); const q=sp.get('p'); if(q&&PAGES[q]){ state.stack=[]; state.page=q; state.params={}; const no=sp.get('no'); if(no) state.params={no}; } }catch(e){} })(); render(); el('sheet').addEventListener('click',e=>{ if(e.target.id==='sheet') closeSheet(); });
取消
访问码: zddqdvzv