diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md
index 594d758..5e44e0a 100644
--- a/.claude/memory/MEMORY.md
+++ b/.claude/memory/MEMORY.md
@@ -1,11 +1,11 @@
-_Last synced: 2026-08-10 | Base commit: `ff48f38`_
+_Last synced: 2026-08-14 | Base commit: `b4dfe31`_
# 记忆索引
| 文件 | 描述 | 类型 | 最后更新 |
|------|------|------|----------|
| project_overview.md | 项目技术栈、架构与模块结构 | project | 2026-05-14 |
-| project_progress.md | 各模块原型开发进度 | project | 2026-08-10 |
-| decisions.md | 关键设计决策与架构约定 | project | 2026-08-10 |
+| project_progress.md | 各模块原型开发进度 | project | 2026-08-14 |
+| decisions.md | 关键设计决策与架构约定 | project | 2026-08-14 |
| feedback.md | 协作规范与用户纠正记录 | feedback | 2026-06-15 |
| user_profile.md | 用户角色与偏好 | user | 2026-04-21 |
| lint_report.md | 记忆健康检查报告 | lint | 2026-08-10 |
diff --git a/.claude/memory/lint_report.md b/.claude/memory/lint_report.md
index cfa6a36..7549b98 100644
--- a/.claude/memory/lint_report.md
+++ b/.claude/memory/lint_report.md
@@ -2,12 +2,12 @@
name: 记忆健康检查报告
description: memory-lint 最新一次执行的检查结果与待处理项
type: lint
-last_updated: 2026-08-10
+last_updated: 2026-08-14
---
# 记忆健康检查报告
-> _执行时间: 2026-08-10 | Base commit: `ff48f38` | Last synced: 2026-08-10_
+> _执行时间: 2026-08-14 | Base commit: `b4dfe31` | Last synced: 2026-08-14_
## 健康概览
@@ -24,7 +24,7 @@ last_updated: 2026-08-10
**AUTO-FIX 已执行 0 项 | NEED-HUMAN 待处理 8 项**
-> 本次为 2026-08-10 第三批次 sync 后的复查。新增 1 条决策(餐次管理模块业务路径与交互约定)+ 1 条里程碑条目,交叉引用闭环验证通过,无 AUTO-FIX 需执行;前期遗留的 8 项 NEED-HUMAN 维持原判定。
+> 本次为 2026-08-14 提交(b4dfe31 毛菜验收秤 PAD 外壳 + 后厨补餐屏 + 配送H5 + 营养档案)后的 sync 检查。memory-update 更新了 project_progress.md(新增 2026-08-12~14 七条里程碑 + 模块清单补 meal-delivery-h5/后厨补餐屏);decisions.md 的 PAD grid 踩坑条目由用户侧先行写入并随代码提交。索引与磁盘一致(6 文件),全部 8 个交叉引用有效且闭环。矛盾检测确认 decisions.md「grid 不用 flex」与 project_progress.md 2026-06-12 里程碑「flex 居中」为时间演进关系非矛盾(decisions 条目内已注明与 commit 2373704 的历史差异)。前期遗留 NEED-HUMAN 维持原判定,project_overview.md 过期天数已超 90 天阈值升级为 ERROR(内容仍稳定,可仅刷新时间戳)。
---
@@ -36,38 +36,30 @@ _本次无 AUTO-FIX 执行项(所有结构性检查通过)。_
## NEED-HUMAN 待处理清单
-### [WARN] 过期 — user_profile.md
+### [ERROR] 过期 — project_overview.md(升级)
+- **文件**:`project_overview.md`(last_updated: 2026-05-14,距今 92 天,超 90 天 ERROR 阈值)
+- **说明**:项目技术栈(纯 HTML 自包含 + 墨刀兼容)与三端架构稳定,期间变更均为模块内页面迭代,内容并未失真
+- **建议**:若确认架构无变化,下次 sync 时仅刷新 frontmatter 的 `last_updated` 时间戳即可消除 ERROR
-- **文件**:`user_profile.md`(last_updated: 2026-04-21,距今 111 天,超 90 天阈值)
-- **本次降级为 WARN**:用户角色画像(产品经理/原型设计师、简体中文、分步确认)属结构性稳定内容,不随代码迭代过期;如用户角色或协作偏好无实际变化,可不更新
-- **建议**:用户确认角色/偏好是否仍准确,若有变化执行 `/memory-update` 更新;若保持现状可在下次 sync 时刷新 `last_updated` 时间戳
+### [WARN] 过期 — user_profile.md
+- **文件**:`user_profile.md`(last_updated: 2026-04-21,距今 115 天,超 90 天阈值)
+- **维持降级为 WARN**:用户角色画像(产品经理/原型设计师、简体中文、分步确认)属结构性稳定内容,不随代码迭代过期
+- **建议**:用户确认角色/偏好是否仍准确,有变化时执行 `/memory-update` 更新
### [WARN] 过期 — feedback.md
-
-- **文件**:`feedback.md`(last_updated: 2026-06-15,距今 56 天,超 30 天 WARN 阈值)
+- **文件**:`feedback.md`(last_updated: 2026-06-15,距今 60 天,超 30 天 WARN 阈值)
- **建议**:确认会话期间是否产生新的协作规范需沉淀(本次 sync 未触发新 feedback 写入)
-### [WARN] 过期 — project_overview.md
-
-- **文件**:`project_overview.md`(last_updated: 2026-05-14,距今 88 天,超 30 天 WARN 阈值,临近 90 天 ERROR)
-- **建议**:项目技术栈(纯 HTML 自包含 + 墨刀兼容)与三端架构稳定,本次变更均为模块内页面迭代;若架构未变可不更新,临近 ERROR 阈值时可刷新时间戳
-
### [WARN] 可推断内容污染 — project_progress.md
-
-- **文件**:`project_progress.md` 第 109~168 行「近期里程碑」区段(历史条目)
-- **问题**:历史里程碑条目仍含大量具体文件路径(如 `din-ingredient.html`、`prod-clean-order`、`canteen-nutrition-scale/02-dish-display.html`)与 commit hash(`445d824`、`d00208a`、`4fd44f1` 等),按 memory-update 规则「Git 历史(可用 git log 查)」不应写入记忆文件
-- **本次执行**:新增的 2026-08-10 两条里程碑仍保留文件名(属变更范围说明,便于跨模块追溯,未全量罗列),历史 110~168 行污染条目未清理
-- **建议**:人工审查 110~168 行历史里程碑,保留结论性描述、删除具体文件路径与 commit hash,让用户能通过 `git log --since=2026-07` 自查细节
+- **文件**:`project_progress.md`「近期里程碑」区段(历史条目)
+- **问题**:历史里程碑条目仍含大量具体文件路径与 commit hash,按 memory-update 规则「Git 历史(可用 git log 查)」不应写入记忆文件
+- **本次执行**:新增的 2026-08-12~14 七条里程碑延续既有格式保留文件名(属变更范围说明,便于跨模块追溯),历史污染条目未清理
+- **建议**:人工审查历史里程碑,保留结论性描述、删除具体文件路径与 commit hash
### [WARN] frontmatter 缺失 — 4 个文件
-
-- **文件**:
- - `decisions.md` — 无 frontmatter(一级标题起首)
- - `feedback.md` — 无 frontmatter
- - `project_overview.md` — 无 frontmatter
- - `user_profile.md` — 无 frontmatter
-- **问题**:按 memory-update 规则,记忆文件应有 `name/description/type/last_updated/commit` 五字段 frontmatter;目前仅 `project_progress.md` 与 `lint_report.md` 有 frontmatter
-- **建议**:MEMORY.md 索引已含类型与日期,可视为冗余备份;如无需统一可忽略。若决定统一,可在下次 sync 时一并补全
+- **文件**:`decisions.md`、`feedback.md`、`project_overview.md`、`user_profile.md`(均无 frontmatter)
+- **说明**:按 memory-update 规则应有五字段 frontmatter;目前仅 `project_progress.md` 与 `lint_report.md` 有
+- **建议**:MEMORY.md 索引已含类型与日期,可视为冗余备份;如需统一可在下次 sync 时一并补全(可与 project_overview 刷新时间戳合并处理)
---
@@ -76,7 +68,8 @@ _本次无 AUTO-FIX 执行项(所有结构性检查通过)。_
- `decisions.md#餐厅终端设备 PAD 居中外壳设计模式` → `[[feedback.md#PAD 终端页面内禁止可见滚动条]]` ✅ 目标存在,双向闭环
- `feedback.md#PAD 终端页面内禁止可见滚动条` → `[[decisions.md#餐厅终端设备 PAD 居中外壳设计模式]]` ✅ 目标存在,双向闭环
- `decisions.md#规格体系统一化改造(跨设备端 + Web 端 + 辅助页)` → `[[feedback.md]]` ✅ 目标存在(占位引用「暂无直接关联条目」,非对称但属预期)
-- `decisions.md#收银设备用户类型徽标三态体系` → `[[project_progress.md]]` ✅ 目标存在,双向闭环(前次 AUTO-FIX 完成)
-- `decisions.md#补齐明细页业务路径修正:少记来源 = 未识别取餐记录` → `[[project_progress.md]]` ✅ 目标存在,双向闭环(前次 AUTO-FIX 完成)
-- `decisions.md#待机页副屏人脸识别监控区(收银员侧实时反馈)` → `[[project_progress.md]]` ✅ 目标存在,双向闭环(本次 memory-update 同步建立)
-- `decisions.md#餐次管理模块业务路径与交互约定` → `[[project_progress.md]]` ✅ 目标存在,双向闭环(本次 memory-update 同步建立)
+- `decisions.md#收银设备用户类型徽标三态体系` → `[[project_progress.md]]` ✅ 目标存在,双向闭环
+- `decisions.md#补齐明细页业务路径修正:少记来源 = 未识别取餐记录` → `[[project_progress.md]]` ✅ 目标存在,双向闭环
+- `decisions.md#待机页副屏人脸识别监控区(收银员侧实时反馈)` → `[[project_progress.md]]` ✅ 目标存在,双向闭环
+- `decisions.md#餐次管理模块业务路径与交互约定` → `[[project_progress.md]]` ✅ 目标存在,双向闭环
+- `project_progress.md#近期里程碑`(2026-08-14 条目)→ `[[decisions.md#餐厅终端设备 PAD 居中外壳设计模式]]` ✅ 目标存在(本次新增)
diff --git a/.claude/memory/project_progress.md b/.claude/memory/project_progress.md
index de6d758..031fa4a 100644
--- a/.claude/memory/project_progress.md
+++ b/.claude/memory/project_progress.md
@@ -2,8 +2,8 @@
name: 项目进度
description: 各模块原型开发进度与里程碑
type: project
-last_updated: 2026-08-10
-commit: ff48f38
+last_updated: 2026-08-14
+commit: b4dfe31
---
# 项目进度
@@ -84,15 +84,17 @@ commit: ff48f38
### 其他终端(other-module)
- `mini-program/nutrition/` — 微信小程序营养管理用户端(28 页)✅
+- `meal-delivery-h5/` — 营养管理餐品配送 H5(配送员端 9 页:登录/工作台/派单来电/抢单池/订单详情/配送中/完成签收/历史/个人中心)✅ + CHANGELOG;称谓统一"配送员",派单不设倒计时
- `canteen-device/` — 餐厅终端设备(多设备实页持续补齐)🔨 设计中
- `canteen-nutrition-scale/` — 餐线营养秤(11 页:含登录页、欢迎页,双屏取餐合并为单页 03-meal-pickup-dual 同页左右双屏布局)+ PRD + FLOW + CHANGELOG
- - `checkout-device/` — 结算设备(14 页:收银员屏 9 页覆盖登录/待机/收银/支付成功/订单/详情/统计/对账/小票 + 用户屏 4 页覆盖待机/同步/支付/成功 + 双屏导航入口)+ index 左右双屏布局
+ - `checkout-device/` — 结算设备(14 页:收银员屏 9 页覆盖登录/待机/收银/支付成功/订单/详情/统计/对账/小票 + 用户屏 4 页覆盖待机/同步/支付/成功 + 双屏导航入口)+ index 左右双屏布局;登录改为用户名密码方式
- `clean-veg-packaging-labeler/` — 净菜包装秤(5 页:登录/订单/包装/历史/标签预览)+ CHANGELOG;术语由"贴标"统一为"包装",包装作业按单份拆分
- `clean-veg-ratio-scale/` — 净菜组配秤(原净菜配比秤重构,纵向 3:4 布局 + 调料组配区 + 调料位置设置页)
- - `ratio-scale/` — 配比秤(含 04-seasoning-settings.html 调料位置设置页)
- - `raw-veg-inspection-scale/` — 毛菜验收秤(8 页:登录/欢迎/验收/设置/历史/采样等)+ PRD + UI-SPEC + FLOW
- - `raw-veg-processing-scale/` — 加工毛菜秤(10 页:登录/欢迎/清洗任务/批次称重/批次清洗/历史/设置等)
- - 父级 `canteen-device/index.html` 已恢复员工登录入口卡片并同步菜单状态
+ - `ratio-scale/` — 配比秤(含 04-seasoning-settings.html 调料位置设置页;调料组配区支持展开/折叠,应称/已称 X/Yg 格式)
+ - `raw-veg-inspection-scale/` — 毛菜验收秤(8 页:登录/欢迎/验收/设置/历史/采样等)+ PRD + UI-SPEC + FLOW;2026-08-14 全页面完成 PAD 居中外壳改造(grid 方案),验收列表动态列(有订单 10 列/无订单 8 列),导航统一经登录页进入
+ - `raw-veg-processing-scale/` — 加工毛菜秤(10 页:登录/欢迎/清洗任务/批次称重/批次清洗/历史/设置等);清洗任务支持新增弹窗与补录功能
+ - `processing-prompt-screen/` — 后厨补餐屏(原"加工提示屏"更名,1 页首版:16 格菜品余量监控四态看板 + 补餐/食谱来源标签)
+ - 父级 `canteen-device/index.html` 已恢复员工登录入口卡片并同步菜单状态;模块级 `doc/CHANGELOG.md` 已建立
- 其余设备页仍有部分占位,待继续补齐
- `emergency-screen/` — 应急大屏(西安/标准版/九厂)🔨 设计中
- `xian-center/index.html` — 西安应急中心大屏(应急指挥大屏,含 KPI 指标/穿戴告警/心脑血管 AI 预警/地理地图/资源调度/急救路线;新增穿戴告警/心脑血管预警/应急求助三个业务记录弹窗含筛选与分页,地图资源标记弹窗,告警弹窗红色闪烁边框)
@@ -102,10 +104,17 @@ commit: ff48f38
Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、健康评估、知识普及、健康数据、心脑血管/糖尿病/癌症预防等)均为待开发状态。
## 总计
-- 已完成/设计中:568+ 个 HTML 原型页面
+- 已完成/设计中:580+ 个 HTML 原型页面
- 覆盖模块:18 个(含三端+其他终端)
## 近期里程碑
+- 2026-08-14:毛菜验收秤 PAD 外壳改造 + 后厨补餐屏首版 + 配送H5流程降噪 + 营养档案检索录入强化(commit b4dfe31)——①毛菜验收秤全部 9 页统一 PAD 居中外壳(body grid 居中方案,详见 [[decisions.md#餐厅终端设备 PAD 居中外壳设计模式]]),验收页食材列表按"有订单 10 列/无订单 8 列"动态渲染(采购单价只读),返回导航统一 referer 参数"从哪里来回哪里去",导航页卡片统一指向登录页;②加工提示屏更名「后厨补餐屏」并完成首版设计(16 格菜品余量监控四态看板 + 补餐/食谱来源标签 + 餐次时段栏);③配送H5 移除派单倒计时与异常按钮、配送中页新增商家联系条/备注展示/清单卡片化;④营养管理餐品库高级搜索弹窗 + 筛选汇总条 + 操作历史 Tab、食材录入改行内选择器 + 净菜包毛利率字段、食材详情操作历史、蔬菜净菜类型结构化表格、进货明细补金额/单价列。新建 canteen-device 模块级 CHANGELOG。
+- 2026-08-14:体检模块完善 — 员工端胃肠镜流程 + 管理端配置页(commit 44e7f32,远程合入)——员工端新增 `my-appointments-detail-gastro.html` 与 `my-appointments-detail-gastro-done.html`(胃肠镜体检流程详情与完成态);管理端 `pre-hospital-scope`/`pre-item-combo-template`/`pre-item-exception`/`pre-item-hospital-fee`/`pre-item-unit-fee`/`pre-plan`/`pre-quota-group`/`pre-quota-personal` 各页新增筛选/弹窗等功能。
+- 2026-08-13:体检模块完善 — 员工端操作流程讲解页 + 管理端配置页(commit 6dacd91)——员工端新增 `process-guide.html`/`process-guide-detail.html`(体检操作流程讲解);管理端 `config-*` 系列配置页新增/完善功能;`index.html` 导航同步。
+- 2026-08-12:配送H5 新模块 + 净菜三页物流改造 + 餐次历史内聚(commit 2346465)——①新建 `other-module/meal-delivery-h5/`(配送员端 9 页:登录/工作台/派单来电/抢单池/订单详情/配送中/完成签收/历史/个人中心 + CHANGELOG);②净菜三页(`prod-clean-order`/`sup-clean-internal`/`sup-clean-external`)物流列简化为徽标 + 物流详情弹窗(4 节点配送时间轴),收货码仅买家视角可见;③结算台 `07-statistics.html` 将餐次历史内聚为统计页 Tab。
+- 2026-08-12:健康画像与历史咨询摘要 + 专家统计明细(commit 5761ce7、a165000)——①员工端新增 `profile/health-profile.html`(健康画像);②应急专家端新增 `history-summary.html`(历史咨询摘要),咨询结束页增加 AI 摘要卡片;③西安应急大屏 `help-emergency.html` 增加生活习惯画像;④Web 管理端 `expert-statistics.html` 增加咨询记录明细弹窗。
+- 2026-08-12:毛菜加工秤与配比秤迭代(commit dbdc189、b522ce5、55b88b2)——①加工毛菜秤 `01-washing-tasks.html` 新增任务新增弹窗(重新排版)与补录功能;②配比秤调料组配区增加展开/折叠开关,应称/已称改为 X/Yg 格式,`04-seasoning-settings.html` 调料位置设置交互优化。
+- 2026-08-12:营养管理杂项 + 结算台登录改版(commit 39998f4、b07dd05)——①消费管理支持退款(`din-data-consume.html`);②净菜订单支持选择内配调料;③采供管理增加待确认状态(`farm-purchase.html`);④结算台登录改为用户名密码方式(`01-login.html`)。
- 2026-08-10:结算设备餐次管理模块新增 — 开餐/补餐/闭餐/余量处置/历史四页联动(commit ff48f38)——①新增 4 个页面:`10-meal-center.html`(餐次管理入口,左右双卡/开餐开关+历史入口)、`11-meal-active.html`(开餐中详情,餐线Tab+餐品图文卡+余量阈值三态+通知补餐+结束用餐二次确认)、`12-meal-end-surplus.html`(余量处置引导,全选+批量处置方式下拉+表格化余量列表+提交二次确认,四选项对齐后台营养管理余量处置体系)、`13-meal-history.html`(历史记录,左餐次列表+右流程时间轴六类节点+底部统计);②`02-standby.html` 底部入口卡片由「用餐管理」改名为「餐次管理」并跳转 `10-meal-center.html`;③`index.html` 收银员屏区追加 4 张导航卡片。(相关决策:[[decisions.md#餐次管理模块业务路径与交互约定]])
- 2026-08-10:结算设备待机页新增副屏人脸识别监控区 + 多页面冗余元素精简(commit 32bd018)——①`02-standby.html` 左下「开始结算」按钮区改为「副屏人脸识别监控区」三态轮播演示(成功/光线不足/位置不对),含模拟副屏画面(人形剪影+检测框+LIVE 指示)与时钟精简、底部「小票打印」入口改名为「用餐管理」(功能待开发占位);②`03-checkout.html` 删除顶栏「本单应付」、余额信息卡「会员等级」、清空按钮及相关 CSS/JS;③`07-statistics.html` KPI 对比文案统一为「较昨日午餐」、支付方式柱状图标签语义化(余额支付/现金支付/餐券抵扣);④`08-reconcile.html` 删除差额汇总「企业补贴优惠」行;⑤`user-02-checkout-sync.html` 删除热量环「略少 76%」提示;⑥`user-04-success.html` 删除二维码「今日已有 1,284 人查看」营销文案。(相关决策:[[decisions.md#待机页副屏人脸识别监控区(收银员侧实时反馈)]])
- 2026-08-10:收银设备新增用户类型徽标体系 + 补齐明细页重构(commit deb4752)——①主屏(03/04/05/06/07)与副屏(user-02/03/04)统一用户类型徽标三态配色(会员=蓝金渐变金边 / 临时=灰底 / 内部员工=蓝底),订单列表(05)新增「用户类型」列、订单详情(06)新增独立顾客信息卡片并清理语义陈旧的"顾客类型=企业员工"项;②订单详情(06)移除「补齐明细」操作入口(支付后改走退款流程,避免操作歧义);③补齐明细页(08)重构:左侧订单由卡片堆叠改为表格化(对齐 03-checkout),「添加商品」Tab 重构为「补齐未识别」从未匹配取餐记录直接加入(业务路径修正:少记来源=未识别记录而非菜品库);④导航页"结算设备"改名为"结算台"。(相关决策:[[decisions.md#收银设备用户类型徽标三态体系]]、[[decisions.md#补齐明细页业务路径修正:少记来源 = 未识别取餐记录]])
diff --git a/.idea/claudeCodeTabState.xml b/.idea/claudeCodeTabState.xml
index 381792d..76c161a 100644
--- a/.idea/claudeCodeTabState.xml
+++ b/.idea/claudeCodeTabState.xml
@@ -7,67 +7,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -77,6 +17,5 @@
-
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
index 0a98661..4c1e288 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,4 +1,4 @@
-
+
# 项目约束
本项目所有工作必须严格遵守 `项目背景.md` 中定义的全部规范,包括:
diff --git a/other-module/canteen-device/checkout-device/07-statistics.html b/other-module/canteen-device/checkout-device/07-statistics.html
index a1d3a89..9eaa5a4 100644
--- a/other-module/canteen-device/checkout-device/07-statistics.html
+++ b/other-module/canteen-device/checkout-device/07-statistics.html
@@ -1102,6 +1102,7 @@
.mh-dmethod-card--donate { background: rgba(74, 222, 128, 0.08); border-color: rgba(74, 222, 128, 0.2); }
.mh-dmethod-card--waste { background: rgba(239, 68, 68, 0.08); border-color: rgba(239, 68, 68, 0.2); }
.mh-dmethod-card--reprocess { background: rgba(168, 85, 247, 0.08); border-color: rgba(168, 85, 247, 0.2); }
+ .mh-dmethod-card--cabinet { background: rgba(34, 211, 238, 0.08); border-color: rgba(34, 211, 238, 0.2); }
.mh-dmethod-card__label { font-size: 10px; color: rgba(255, 255, 255, 0.55); }
.mh-dmethod-card__value { font-size: 20px; font-weight: 700; color: #fff; }
.mh-dmethod-card__count { font-size: 10px; color: rgba(255, 255, 255, 0.45); }
@@ -1109,6 +1110,7 @@
.mh-dmethod-card--donate .mh-dmethod-card__value { color: #4ade80; }
.mh-dmethod-card--waste .mh-dmethod-card__value { color: #ef4444; }
.mh-dmethod-card--reprocess .mh-dmethod-card__value { color: #c084fc; }
+ .mh-dmethod-card--cabinet .mh-dmethod-card__value { color: #22d3ee; }
/* 筛选 tab */
.mh-filter-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
@@ -1142,6 +1144,7 @@
.mh-method-badge--donate { background: rgba(74, 222, 128, 0.18); color: #4ade80; }
.mh-method-badge--waste { background: rgba(239, 68, 68, 0.18); color: #ef4444; }
.mh-method-badge--reprocess { background: rgba(168, 85, 247, 0.18); color: #c084fc; }
+ .mh-method-badge--cabinet { background: rgba(34, 211, 238, 0.18); color: #22d3ee; }
/* 节点卡片内的处置方式紧凑网格 */
.mh-dispose-compact {
@@ -1741,7 +1744,7 @@
var MH_NODE_TYPES = {
start: { dotCls: 'mh-tl-node__dot--start', dotIcon: '▶', badgeCls: 'mh-detail-subhead__badge--start', label: '开餐' },
add: { dotCls: 'mh-tl-node__dot--add', dotIcon: '➕', badgeCls: 'mh-detail-subhead__badge--add', label: '补餐' },
- close: { dotCls: 'mh-tl-node__dot--close', dotIcon: '✕', badgeCls: 'mh-detail-subhead__badge--close', label: '闭餐' },
+ close: { dotCls: 'mh-tl-node__dot--close', dotIcon: '✕', badgeCls: 'mh-detail-subhead__badge--close', label: '收餐' },
dispose: { dotCls: 'mh-tl-node__dot--dispose', dotIcon: '♻', badgeCls: 'mh-detail-subhead__badge--dispose', label: '余量处置' }
};
@@ -1749,7 +1752,8 @@
welfare: { label: '员工福利', cls: 'mh-method-badge--welfare', cardCls: 'mh-dmethod-card--welfare' },
donate: { label: '捐赠', cls: 'mh-method-badge--donate', cardCls: 'mh-dmethod-card--donate' },
waste: { label: '废弃', cls: 'mh-method-badge--waste', cardCls: 'mh-dmethod-card--waste' },
- reprocess: { label: '加工再利用', cls: 'mh-method-badge--reprocess', cardCls: 'mh-dmethod-card--reprocess' }
+ reprocess: { label: '加工再利用', cls: 'mh-method-badge--reprocess', cardCls: 'mh-dmethod-card--reprocess' },
+ cabinet: { label: '存入智能货柜', cls: 'mh-method-badge--cabinet', cardCls: 'mh-dmethod-card--cabinet' }
};
var MH_MEAL_HISTORY = [
@@ -1762,8 +1766,7 @@
{ type: 'start', time: '11:00:23', title: '开餐', desc: '操作人 张三 · CS001 开启午餐餐次,涉及 28 道菜', tags: [{ cls: 'mh-tl-tag--green', text: '28 道菜' }], detail: { firstOrder: { time: '11:32:10', line: 'A', customer: '张*明', amount: 27.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '11:01:12', maker: '王师傅', operator: '张三', planned: 8.5, sold: 7.8 }, { name: '鱼香肉丝', line: 'A', serveTime: '11:01:18', maker: '王师傅', operator: '张三', planned: 7.0, sold: 6.2 }, { name: '番茄炒蛋', line: 'B', serveTime: '11:00:45', maker: '李师傅', operator: '张三', planned: 6.0, sold: 4.5 }, { name: '白米饭', line: 'A', serveTime: '11:00:30', maker: '赵师傅', operator: '张三', planned: 15.0, sold: 12.0 }, { name: '清炒西兰花', line: 'B', serveTime: '11:01:05', maker: '李师傅', operator: '张三', planned: 5.0, sold: 4.0 }, { name: '红烧肉', line: 'A', serveTime: '11:01:22', maker: '王师傅', operator: '张三', planned: 6.5, sold: 5.8 }, { name: '麻婆豆腐', line: 'B', serveTime: '11:01:30', maker: '李师傅', operator: '张三', planned: 4.5, sold: 3.9 }, { name: '糖醋排骨', line: 'A', serveTime: '11:02:00', maker: '王师傅', operator: '张三', planned: 5.0, sold: 4.2 }, { name: '酸辣土豆丝', line: 'B', serveTime: '11:02:10', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.3 }, { name: '回锅肉', line: 'C', serveTime: '11:00:55', maker: '陈师傅', operator: '张三', planned: 5.5, sold: 4.8 }, { name: '水煮鱼', line: 'A', serveTime: '11:02:20', maker: '王师傅', operator: '张三', planned: 4.0, sold: 3.2 }, { name: '干煸豆角', line: 'C', serveTime: '11:01:40', maker: '陈师傅', operator: '张三', planned: 3.5, sold: 2.8 }, { name: '蒜蓉生菜', line: 'B', serveTime: '11:01:50', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.1 }, { name: '紫菜蛋花汤', line: 'A', serveTime: '11:00:40', maker: '王师傅', operator: '张三', planned: 10.0, sold: 8.0 }] } },
{ type: 'add', time: '12:05:45', title: '补餐 · 番茄炒蛋', desc: '番茄炒蛋 余量降至 0.8kg(预警阈值),后厨补做 3.0kg', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '番茄炒蛋', line: 'B', dishEmoji: '🍳', triggerLeftover: 0.8, addAmount: 3.0, reason: '自动预警', operator: '张三', status: '已补做' } },
{ type: 'add', time: '12:48:20', title: '补餐 · 白米饭', desc: '白米饭 余量降至 3.0kg,后厨补做 5.0kg', tags: [{ cls: 'mh-tl-tag--gold', text: '手动通知' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '白米饭', line: 'A', dishEmoji: '🍚', triggerLeftover: 3.0, addAmount: 5.0, reason: '手动通知', operator: '张三', status: '已补做' } },
- { type: 'close', time: '13:15:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '闭餐' }], closeDetail: { lines: [{ line: 'B', name: 'B 区中餐线', closeTime: '13:15:00', orders: 32, revenue: 870.00, avgPer: 27.19 }] } },
- { type: 'close', time: '13:45:30', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区中餐线', closeTime: '13:45:30', orders: 41, revenue: 1130.50, avgPer: 27.57 }] } }
+ { type: 'close', time: '13:45:30', title: '收餐', desc: '部分餐线已收餐,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '部分收餐' }], closeDetail: { operator: '张三', closeTime: '13:45:30', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 73, revenue: 2000.50, avgPer: 27.40, surplus: '5.0' } }
],
footer: { totalOrders: 96, totalRevenue: 2614.50, avgPer: 27.23, surplus: '进行中' }
},
@@ -1775,8 +1778,8 @@
nodes: [
{ type: 'start', time: '06:30:00', title: '开餐', desc: '操作人 李四 · CS002 开启早餐餐次,涉及 14 道菜', tags: [{ cls: 'mh-tl-tag--green', text: '14 道菜' }], detail: { firstOrder: { time: '06:48:15', line: 'A', customer: '王*强', amount: 12.50 }, dishes: [{ name: '豆浆', line: 'A', serveTime: '06:30:10', maker: '孙师傅', operator: '李四', planned: 8.0, sold: 6.5 }, { name: '油条', line: 'A', serveTime: '06:30:20', maker: '孙师傅', operator: '李四', planned: 5.0, sold: 4.2 }, { name: '鲜肉包', line: 'B', serveTime: '06:30:05', maker: '周师傅', operator: '李四', planned: 6.0, sold: 5.0 }, { name: '鸡蛋饼', line: 'A', serveTime: '06:30:35', maker: '孙师傅', operator: '李四', planned: 4.0, sold: 3.3 }, { name: '小米粥', line: 'B', serveTime: '06:30:15', maker: '周师傅', operator: '李四', planned: 6.0, sold: 5.1 }, { name: '馒头', line: 'B', serveTime: '06:30:25', maker: '周师傅', operator: '李四', planned: 5.0, sold: 4.0 }, { name: '花卷', line: 'A', serveTime: '06:30:45', maker: '孙师傅', operator: '李四', planned: 3.5, sold: 2.8 }, { name: '茶叶蛋', line: 'A', serveTime: '06:30:08', maker: '孙师傅', operator: '李四', planned: 4.0, sold: 3.5 }] } },
{ type: 'add', time: '07:25:30', title: '补餐 · 豆浆', desc: '豆浆 余量降至 1.2kg,后厨补做 3.0kg', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '豆浆', line: 'A', dishEmoji: '🥛', triggerLeftover: 1.2, addAmount: 3.0, reason: '自动预警', operator: '李四', status: '已补做' } },
- { type: 'close', time: '08:50:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '全部闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区早餐线', closeTime: '08:50:00', orders: 78, revenue: 995.00, avgPer: 12.76 }, { line: 'B', name: 'B 区早餐线', closeTime: '08:50:00', orders: 64, revenue: 825.00, avgPer: 12.89 }] } },
- { type: 'dispose', time: '09:32:18', title: '余量处置完成', desc: '本餐次共处置余量 12.4kg,操作人 李四', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 12.4, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 5.2, count: 3 }, { key: 'donate', label: '捐赠', amount: 4.8, count: 2 }, { key: 'waste', label: '废弃', amount: 1.4, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }], records: [{ time: '09:33:10', dish: '油条', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '09:34:05', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.8, receiver: '员工 · 陈小红', operator: '李四', note: '值班餐' }, { time: '09:35:20', dish: '鸡蛋饼', line: 'A', method: 'welfare', amount: 1.4, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '09:36:45', dish: '小米粥', line: 'B', method: 'donate', amount: 3.0, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '09:37:30', dish: '馒头', line: 'B', method: 'donate', amount: 1.8, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '09:38:15', dish: '花卷', line: 'A', method: 'waste', amount: 0.9, receiver: '—', operator: '李四', note: '临期食品' }, { time: '09:39:00', dish: '茶叶蛋', line: 'A', method: 'waste', amount: 0.5, receiver: '—', operator: '李四', note: '破壳损耗' }, { time: '09:40:10', dish: '豆浆', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '李四', note: '员工餐再利用' }] } }
+ { type: 'close', time: '08:50:00', title: '收餐', desc: '全部餐线收餐完成,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '李四', closeTime: '08:50:00', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 142, revenue: 1820.00, avgPer: 12.82, surplus: '12.4' } },
+ { type: 'dispose', time: '09:32:18', title: '余量处置', desc: '本餐次共处置余量 12.9kg,操作人 李四', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 12.9, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 5.2, count: 3 }, { key: 'donate', label: '捐赠', amount: 4.8, count: 2 }, { key: 'waste', label: '废弃', amount: 1.4, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }, { key: 'cabinet', label: '存入智能货柜', amount: 0.5, count: 1 }], records: [{ time: '09:33:10', dish: '油条', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '09:34:05', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.8, receiver: '员工 · 陈小红', operator: '李四', note: '值班餐' }, { time: '09:35:20', dish: '鸡蛋饼', line: 'A', method: 'welfare', amount: 1.4, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '09:36:45', dish: '小米粥', line: 'B', method: 'donate', amount: 3.0, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '09:37:30', dish: '馒头', line: 'B', method: 'donate', amount: 1.8, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '09:38:15', dish: '花卷', line: 'A', method: 'waste', amount: 0.9, receiver: '—', operator: '李四', reason: '临期食品', note: '' }, { time: '09:39:00', dish: '茶叶蛋', line: 'A', method: 'waste', amount: 0.5, receiver: '—', operator: '李四', reason: '破壳损耗', note: '' }, { time: '09:40:10', dish: '豆浆', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '李四', processing: '员工餐再利用', note: '' }, { time: '09:42:00', dish: '鸡蛋饼', line: 'A', method: 'cabinet', amount: 0.5, cabinetId: 'SCG-01-03', operator: '李四', note: '保鲜盒存储' }] } }
],
footer: { totalOrders: 142, totalRevenue: 1820.00, avgPer: 12.82, surplus: '12.4 kg' }
},
@@ -1788,8 +1791,8 @@
nodes: [
{ type: 'start', time: '17:00:00', title: '开餐', desc: '操作人 张三 · CS001 开启晚餐餐次,涉及 24 道菜', tags: [{ cls: 'mh-tl-tag--green', text: '24 道菜' }], detail: { firstOrder: { time: '17:21:50', line: 'C', customer: '陈*芳', amount: 8.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '17:00:30', maker: '王师傅', operator: '张三', planned: 7.0, sold: 6.3 }, { name: '鱼香肉丝', line: 'A', serveTime: '17:00:45', maker: '王师傅', operator: '张三', planned: 6.5, sold: 5.7 }, { name: '番茄炒蛋', line: 'B', serveTime: '17:00:20', maker: '李师傅', operator: '张三', planned: 6.0, sold: 5.0 }, { name: '白米饭', line: 'A', serveTime: '17:00:10', maker: '赵师傅', operator: '张三', planned: 12.0, sold: 10.5 }, { name: '红烧肉', line: 'A', serveTime: '17:01:00', maker: '王师傅', operator: '张三', planned: 5.5, sold: 4.8 }, { name: '牛肉面', line: 'C', serveTime: '17:00:50', maker: '陈师傅', operator: '张三', planned: 8.0, sold: 6.5 }, { name: '酸辣粉', line: 'C', serveTime: '17:01:10', maker: '陈师傅', operator: '张三', planned: 5.0, sold: 4.2 }, { name: '蛋炒饭', line: 'C', serveTime: '17:00:35', maker: '陈师傅', operator: '张三', planned: 4.5, sold: 3.8 }, { name: '清炒西兰花', line: 'B', serveTime: '17:01:20', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.2 }, { name: '麻婆豆腐', line: 'B', serveTime: '17:01:30', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.5 }] } },
{ type: 'add', time: '18:15:22', title: '补餐 · 牛肉面', desc: '牛肉面 余量降至 0.5kg(预警阈值),后厨补做 2.5kg', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '牛肉面', line: 'C', dishEmoji: '🍜', triggerLeftover: 0.5, addAmount: 2.5, reason: '自动预警', operator: '张三', status: '已补做' } },
- { type: 'close', time: '19:30:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '全部闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区中餐线', closeTime: '19:30:00', orders: 58, revenue: 1340.00, avgPer: 23.10 }, { line: 'B', name: 'B 区中餐线', closeTime: '19:30:00', orders: 49, revenue: 1150.00, avgPer: 23.47 }, { line: 'C', name: 'C 区快餐线', closeTime: '19:30:00', orders: 61, revenue: 1360.00, avgPer: 22.30 }] } },
- { type: 'dispose', time: '20:25:10', title: '余量处置完成', desc: '本餐次共处置余量 22.6kg,操作人 张三', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--red', text: '损耗超标 5.1kg' }], dispose: { total: 22.6, operator: '张三', methods: [{ key: 'welfare', label: '员工福利', amount: 8.0, count: 4 }, { key: 'donate', label: '捐赠', amount: 6.5, count: 3 }, { key: 'waste', label: '废弃', amount: 5.1, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.0, count: 2 }], records: [{ time: '20:26:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '张三', note: '加班餐' }, { time: '20:27:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 李小红', operator: '张三', note: '' }, { time: '20:27:50', dish: '白米饭', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '张三', note: '' }, { time: '20:28:40', dish: '清炒西兰花', line: 'B', method: 'donate', amount: 2.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '20:29:15', dish: '番茄炒蛋', line: 'B', method: 'donate', amount: 2.2, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '20:29:50', dish: '麻婆豆腐', line: 'B', method: 'donate', amount: 1.8, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '20:30:05', dish: '酸辣粉', line: 'C', method: 'waste', amount: 2.1, receiver: '—', operator: '张三', note: '临期食品' }, { time: '20:30:40', dish: '蛋炒饭', line: 'C', method: 'waste', amount: 1.5, receiver: '—', operator: '张三', note: '' }, { time: '20:31:10', dish: '红烧肉', line: 'A', method: 'waste', amount: 0.8, receiver: '—', operator: '张三', note: '过夜损耗' }, { time: '20:31:45', dish: '牛肉面', line: 'C', method: 'waste', amount: 0.7, receiver: '—', operator: '张三', note: '' }, { time: '20:32:30', dish: '番茄炒蛋', line: 'B', method: 'reprocess', amount: 1.5, receiver: '后厨 · 李师傅', operator: '李四', note: '次日员工餐' }, { time: '20:33:10', dish: '白米饭', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 赵师傅', operator: '张三', note: '员工餐再利用' }, { time: '20:34:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '张三', note: '' }] } }
+ { type: 'close', time: '19:30:00', title: '收餐', desc: '全部餐线收餐完成,进入余量处置流程', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '张三', closeTime: '19:30:00', method: 'surplus_dispose', methodLabel: '余量处置', orders: 168, revenue: 3850.00, avgPer: 22.92, surplus: '22.6' } },
+ { type: 'dispose', time: '20:25:10', title: '余量处置', desc: '本餐次共处置余量 24.1kg,操作人 张三', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 24.1, operator: '张三', methods: [{ key: 'welfare', label: '员工福利', amount: 8.0, count: 4 }, { key: 'donate', label: '捐赠', amount: 6.5, count: 3 }, { key: 'waste', label: '废弃', amount: 5.1, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.0, count: 2 }, { key: 'cabinet', label: '存入智能货柜', amount: 1.5, count: 2 }], records: [{ time: '20:26:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '张三', note: '加班餐' }, { time: '20:27:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 李小红', operator: '张三', note: '' }, { time: '20:27:50', dish: '白米饭', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '张三', note: '' }, { time: '20:28:40', dish: '清炒西兰花', line: 'B', method: 'donate', amount: 2.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '20:29:15', dish: '番茄炒蛋', line: 'B', method: 'donate', amount: 2.2, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '20:29:50', dish: '麻婆豆腐', line: 'B', method: 'donate', amount: 1.8, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '20:30:05', dish: '酸辣粉', line: 'C', method: 'waste', amount: 2.1, receiver: '—', operator: '张三', reason: '临期食品', note: '' }, { time: '20:30:40', dish: '蛋炒饭', line: 'C', method: 'waste', amount: 1.5, receiver: '—', operator: '张三', reason: '剩余变质', note: '' }, { time: '20:31:10', dish: '红烧肉', line: 'A', method: 'waste', amount: 0.8, receiver: '—', operator: '张三', reason: '过夜损耗', note: '' }, { time: '20:31:45', dish: '牛肉面', line: 'C', method: 'waste', amount: 0.7, receiver: '—', operator: '张三', reason: '剩余变质', note: '' }, { time: '20:32:30', dish: '番茄炒蛋', line: 'B', method: 'reprocess', amount: 1.5, receiver: '后厨 · 李师傅', operator: '李四', processing: '次日员工餐', note: '' }, { time: '20:33:10', dish: '白米饭', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 赵师傅', operator: '张三', processing: '员工餐再利用', note: '' }, { time: '20:34:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '张三', note: '' }, { time: '20:35:00', dish: '宫保鸡丁', line: 'A', method: 'cabinet', amount: 1.0, cabinetId: 'SCG-02-05', operator: '张三', note: '次日复热' }, { time: '20:35:40', dish: '鱼香肉丝', line: 'A', method: 'cabinet', amount: 0.5, cabinetId: 'SCG-02-06', operator: '张三', note: '' }] } }
],
footer: { totalOrders: 168, totalRevenue: 3850.00, avgPer: 22.92, surplus: '22.6 kg' }
},
@@ -1800,7 +1803,8 @@
timeRange: '11:00:00 - 14:05:20', endTime: '14:05:20',
nodes: [
{ type: 'start', time: '11:00:00', title: '开餐', desc: '操作人 李四 · CS002 开启午餐餐次,涉及 28 道菜', tags: [{ cls: 'mh-tl-tag--green', text: '28 道菜' }], detail: { firstOrder: { time: '11:25:10', line: 'A', customer: '刘*华', amount: 32.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '11:00:30', maker: '王师傅', operator: '李四', planned: 8.0, sold: 7.5 }, { name: '糖醋排骨', line: 'A', serveTime: '11:00:50', maker: '王师傅', operator: '李四', planned: 6.0, sold: 5.2 }, { name: '红烧狮子头', line: 'B', serveTime: '11:01:10', maker: '李师傅', operator: '李四', planned: 5.0, sold: 4.3 }, { name: '番茄炒蛋', line: 'B', serveTime: '11:00:40', maker: '李师傅', operator: '李四', planned: 7.0, sold: 6.0 }, { name: '白米饭', line: 'A', serveTime: '11:00:15', maker: '赵师傅', operator: '李四', planned: 14.0, sold: 12.0 }, { name: '葱爆羊肉', line: 'C', serveTime: '11:01:25', maker: '陈师傅', operator: '李四', planned: 4.5, sold: 3.8 }, { name: '蚝油生菜', line: 'C', serveTime: '11:01:40', maker: '陈师傅', operator: '李四', planned: 3.5, sold: 2.9 }, { name: '辣子鸡', line: 'C', serveTime: '11:02:00', maker: '陈师傅', operator: '李四', planned: 5.0, sold: 4.1 }, { name: '麻婆豆腐', line: 'B', serveTime: '11:01:55', maker: '李师傅', operator: '李四', planned: 4.0, sold: 3.4 }, { name: '紫菜蛋花汤', line: 'A', serveTime: '11:00:20', maker: '王师傅', operator: '李四', planned: 8.0, sold: 7.0 }] } },
- { type: 'dispose', time: '14:28:42', title: '余量处置完成', desc: '本餐次共处置余量 28.6kg,操作人 李四', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 28.6, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 10.0, count: 5 }, { key: 'donate', label: '捐赠', amount: 8.5, count: 3 }, { key: 'waste', label: '废弃', amount: 6.6, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.5, count: 2 }], records: [{ time: '14:29:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 3.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '14:29:50', dish: '糖醋排骨', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 陈小红', operator: '李四', note: '' }, { time: '14:30:30', dish: '番茄炒蛋', line: 'B', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '14:31:10', dish: '白米饭', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '李四', note: '' }, { time: '14:32:00', dish: '麻婆豆腐', line: 'B', method: 'welfare', amount: 1.0, receiver: '员工 · 刘洋', operator: '李四', note: '' }, { time: '14:33:20', dish: '红烧狮子头', line: 'B', method: 'donate', amount: 3.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '14:34:00', dish: '辣子鸡', line: 'C', method: 'donate', amount: 3.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:34:40', dish: '蚝油生菜', line: 'C', method: 'donate', amount: 2.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:35:30', dish: '葱爆羊肉', line: 'C', method: 'waste', amount: 2.5, receiver: '—', operator: '李四', note: '临期食品' }, { time: '14:36:10', dish: '紫菜蛋花汤', line: 'A', method: 'waste', amount: 2.1, receiver: '—', operator: '李四', note: '' }, { time: '14:36:50', dish: '白米饭', line: 'A', method: 'waste', amount: 1.2, receiver: '—', operator: '李四', note: '' }, { time: '14:37:30', dish: '番茄炒蛋', line: 'B', method: 'waste', amount: 0.8, receiver: '—', operator: '李四', note: '' }, { time: '14:38:20', dish: '白米饭', line: 'A', method: 'reprocess', amount: 2.0, receiver: '后厨 · 赵师傅', operator: '李四', note: '员工餐' }, { time: '14:39:00', dish: '宫保鸡丁', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 王师傅', operator: '李四', note: '' }] } }
+ { type: 'close', time: '14:05:20', title: '收餐', desc: '全部餐线收餐完成,进入余量处置流程', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '李四', closeTime: '14:05:20', method: 'surplus_dispose', methodLabel: '余量处置', orders: 132, revenue: 3050.00, avgPer: 23.10, surplus: '28.6' } },
+ { type: 'dispose', time: '14:28:42', title: '余量处置', desc: '本餐次共处置余量 30.6kg,操作人 李四', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 30.6, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 10.0, count: 5 }, { key: 'donate', label: '捐赠', amount: 8.5, count: 3 }, { key: 'waste', label: '废弃', amount: 6.6, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.5, count: 2 }, { key: 'cabinet', label: '存入智能货柜', amount: 2.0, count: 2 }], records: [{ time: '14:29:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 3.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '14:29:50', dish: '糖醋排骨', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 陈小红', operator: '李四', note: '' }, { time: '14:30:30', dish: '番茄炒蛋', line: 'B', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '14:31:10', dish: '白米饭', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '李四', note: '' }, { time: '14:32:00', dish: '麻婆豆腐', line: 'B', method: 'welfare', amount: 1.0, receiver: '员工 · 刘洋', operator: '李四', note: '' }, { time: '14:33:20', dish: '红烧狮子头', line: 'B', method: 'donate', amount: 3.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '14:34:00', dish: '辣子鸡', line: 'C', method: 'donate', amount: 3.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:34:40', dish: '蚝油生菜', line: 'C', method: 'donate', amount: 2.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:35:30', dish: '葱爆羊肉', line: 'C', method: 'waste', amount: 2.5, receiver: '—', operator: '李四', reason: '临期食品', note: '' }, { time: '14:36:10', dish: '紫菜蛋花汤', line: 'A', method: 'waste', amount: 2.1, receiver: '—', operator: '李四', reason: '剩余变质', note: '' }, { time: '14:36:50', dish: '白米饭', line: 'A', method: 'waste', amount: 1.2, receiver: '—', operator: '李四', reason: '剩余变质', note: '' }, { time: '14:37:30', dish: '番茄炒蛋', line: 'B', method: 'waste', amount: 0.8, receiver: '—', operator: '李四', reason: '外观不佳', note: '' }, { time: '14:38:20', dish: '白米饭', line: 'A', method: 'reprocess', amount: 2.0, receiver: '后厨 · 赵师傅', operator: '李四', processing: '员工餐', note: '' }, { time: '14:39:00', dish: '宫保鸡丁', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 王师傅', operator: '李四', processing: '员工餐再利用', note: '' }, { time: '14:40:00', dish: '紫菜蛋花汤', line: 'A', method: 'cabinet', amount: 1.2, cabinetId: 'SCG-01-08', operator: '李四', note: '冷藏保存' }, { time: '14:40:30', dish: '麻婆豆腐', line: 'B', method: 'cabinet', amount: 0.8, cabinetId: 'SCG-01-09', operator: '李四', note: '' }] } }
],
footer: { totalOrders: 132, totalRevenue: 3050.00, avgPer: 23.10, surplus: '28.6 kg' }
},
@@ -1811,7 +1815,8 @@
timeRange: '06:30:00 - 09:18:00', endTime: '09:18:00',
nodes: [
{ type: 'start', time: '06:30:00', title: '开餐', desc: '操作人 王五 · CS003 开启早餐餐次,涉及 12 道菜', tags: [{ cls: 'mh-tl-tag--green', text: '12 道菜' }], detail: { dishes: [{ name: '豆浆', line: 'A', serveTime: '06:30:10', maker: '孙师傅', operator: '王五', planned: 6.0, sold: 5.2 }, { name: '油条', line: 'A', serveTime: '06:30:25', maker: '孙师傅', operator: '王五', planned: 4.0, sold: 3.5 }, { name: '鲜肉包', line: 'B', serveTime: '06:30:05', maker: '周师傅', operator: '王五', planned: 5.0, sold: 4.2 }, { name: '鸡蛋饼', line: 'A', serveTime: '06:30:40', maker: '孙师傅', operator: '王五', planned: 3.5, sold: 2.8 }, { name: '小米粥', line: 'B', serveTime: '06:30:15', maker: '周师傅', operator: '王五', planned: 5.0, sold: 4.5 }, { name: '馒头', line: 'B', serveTime: '06:30:30', maker: '周师傅', operator: '王五', planned: 4.0, sold: 3.3 }, { name: '花卷', line: 'A', serveTime: '06:30:50', maker: '孙师傅', operator: '王五', planned: 3.0, sold: 2.4 }, { name: '茶叶蛋', line: 'A', serveTime: '06:30:08', maker: '孙师傅', operator: '王五', planned: 3.5, sold: 2.9 }] } },
- { type: 'dispose', time: '09:35:24', title: '余量处置完成', desc: '本餐次共处置余量 8.4kg,操作人 王五', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 8.4, operator: '王五', methods: [{ key: 'welfare', label: '员工福利', amount: 4.0, count: 2 }, { key: 'donate', label: '捐赠', amount: 2.4, count: 2 }, { key: 'waste', label: '废弃', amount: 1.0, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }], records: [{ time: '09:36:10', dish: '豆浆', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '王五', note: '加班餐' }, { time: '09:37:00', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.5, receiver: '员工 · 陈小红', operator: '王五', note: '' }, { time: '09:38:20', dish: '小米粥', line: 'B', method: 'donate', amount: 1.5, receiver: '浐灞社区服务站', operator: '王五', note: '捐赠凭证已上传' }, { time: '09:39:00', dish: '馒头', line: 'B', method: 'donate', amount: 0.9, receiver: '浐灞社区服务站', operator: '王五', note: '' }, { time: '09:40:10', dish: '油条', line: 'A', method: 'waste', amount: 0.6, receiver: '—', operator: '王五', note: '临期' }, { time: '09:40:50', dish: '花卷', line: 'A', method: 'waste', amount: 0.4, receiver: '—', operator: '王五', note: '' }, { time: '09:41:30', dish: '鸡蛋饼', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '王五', note: '员工餐' }] } }
+ { type: 'close', time: '09:18:00', title: '收餐', desc: '全部餐线收餐完成,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '王五', closeTime: '09:18:00', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 98, revenue: 1250.00, avgPer: 12.76, surplus: '8.4' } },
+ { type: 'dispose', time: '09:35:24', title: '余量处置', desc: '本餐次共处置余量 9.0kg,操作人 王五', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 9.0, operator: '王五', methods: [{ key: 'welfare', label: '员工福利', amount: 4.0, count: 2 }, { key: 'donate', label: '捐赠', amount: 2.4, count: 2 }, { key: 'waste', label: '废弃', amount: 1.0, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }, { key: 'cabinet', label: '存入智能货柜', amount: 0.6, count: 1 }], records: [{ time: '09:36:10', dish: '豆浆', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '王五', note: '加班餐' }, { time: '09:37:00', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.5, receiver: '员工 · 陈小红', operator: '王五', note: '' }, { time: '09:38:20', dish: '小米粥', line: 'B', method: 'donate', amount: 1.5, receiver: '浐灞社区服务站', operator: '王五', note: '捐赠凭证已上传' }, { time: '09:39:00', dish: '馒头', line: 'B', method: 'donate', amount: 0.9, receiver: '浐灞社区服务站', operator: '王五', note: '' }, { time: '09:40:10', dish: '油条', line: 'A', method: 'waste', amount: 0.6, receiver: '—', operator: '王五', reason: '临期', note: '' }, { time: '09:40:50', dish: '花卷', line: 'A', method: 'waste', amount: 0.4, receiver: '—', operator: '王五', reason: '剩余变质', note: '' }, { time: '09:41:30', dish: '鸡蛋饼', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '王五', processing: '员工餐', note: '' }, { time: '09:42:00', dish: '豆浆', line: 'A', method: 'cabinet', amount: 0.6, cabinetId: 'SCG-01-02', operator: '王五', note: '保鲜存储' }] } }
],
footer: { totalOrders: 98, totalRevenue: 1250.00, avgPer: 12.76, surplus: '8.4 kg' }
}
@@ -1820,7 +1825,7 @@
var mhCurrentMealId = null;
var mhCurrentView = 'timeline';
var mhViewNodeType = null;
- var mhDisposeFilter = 'all';
+ var mhDisposeFilter = 'welfare';
function mhRenderMealList(filterDate) {
var groups = {};
@@ -1881,7 +1886,7 @@
mhCurrentMealId = mealId;
mhCurrentView = 'timeline';
mhViewNodeType = null;
- mhDisposeFilter = 'all';
+ mhDisposeFilter = 'welfare';
document.querySelectorAll('.mh-meal-item').forEach(function (el) {
el.classList.toggle('is-active', el.getAttribute('data-id') === mealId);
});
@@ -1904,7 +1909,6 @@
+ ''
+ '
'
+ '';
@@ -1922,7 +1926,7 @@
var html = '';
- // 固定四阶段:开餐 → 补餐 → 闭餐 → 余量处置
+ // 固定四阶段:开餐 → 补餐 → 收餐 → 余量处置
// 1. 开餐
if (startNode) {
@@ -1941,27 +1945,19 @@
html += mhBuildTlCard('add', lastAddTime, '补餐 · ' + addNodes.length + ' 次', addDesc, addTags, true, 'mhOpenNodeDetail(\'add\')');
}
- // 3. 闭餐(汇总视图)
+ // 3. 收餐
if (closeNodes.length > 0) {
var lastCloseTime = closeNodes[closeNodes.length - 1].time;
- var closeDesc = '本餐次共
' + closeNodes.length + ' 次闭餐';
+ var closeDesc = '本餐次共
' + closeNodes.length + ' 次收餐';
var closeTags = [
- { cls: 'mh-tl-tag--gray', text: closeNodes.length + ' 次闭餐' }
+ { cls: 'mh-tl-tag--gray', text: closeNodes.length + ' 次收餐' }
];
- html += mhBuildTlCard('close', lastCloseTime, '闭餐 · ' + closeNodes.length + ' 次', closeDesc, closeTags, true, 'mhOpenNodeDetail(\'close\')');
+ html += mhBuildTlCard('close', lastCloseTime, '收餐 · ' + closeNodes.length + ' 次', closeDesc, closeTags, true, 'mhOpenNodeDetail(\'close\')');
}
// 4. 余量处置
if (disposeNode) {
- var disposeCompactHtml = '';
- if (disposeNode.dispose && disposeNode.dispose.methods) {
- var items = disposeNode.dispose.methods.map(function (d) {
- return '
' + d.label + ''
- + '' + d.amount + ' kg
';
- }).join('');
- disposeCompactHtml = '
' + items + '
';
- }
- html += mhBuildTlCard('dispose', disposeNode.time, disposeNode.title, disposeNode.desc, disposeNode.tags, true, 'mhOpenNodeDetail(\'dispose\')', disposeCompactHtml);
+ html += mhBuildTlCard('dispose', disposeNode.time, disposeNode.title, disposeNode.desc, disposeNode.tags, true, 'mhOpenNodeDetail(\'dispose\')');
}
html += '
';
@@ -2014,7 +2010,7 @@
if (!meal) return;
mhViewNodeType = nodeType;
mhCurrentView = 'detail';
- mhDisposeFilter = 'all';
+ mhDisposeFilter = 'welfare';
var typeInfo = MH_NODE_TYPES[nodeType] || MH_NODE_TYPES.start;
var subhead = document.getElementById('mhDetailSubhead');
subhead.classList.add('mh-detail-subhead--show');
@@ -2032,7 +2028,7 @@
document.getElementById('mhSubheadTime').textContent = addNodes.length > 0 ? addNodes[addNodes.length - 1].time : '';
} else if (nodeType === 'close') {
var clNodes = meal.nodes.filter(function(n) { return n.type === 'close'; });
- document.getElementById('mhSubheadTitle').textContent = '闭餐 · ' + clNodes.length + ' 次';
+ document.getElementById('mhSubheadTitle').textContent = '收餐';
document.getElementById('mhSubheadTime').textContent = clNodes.length > 0 ? clNodes[clNodes.length - 1].time : '';
} else if (nodeType === 'dispose') {
var dn = meal.nodes.find(function(n) { return n.type === 'dispose'; });
@@ -2057,7 +2053,7 @@
var meal = mhGetMeal();
if (!meal) return;
mhCurrentView = 'timeline';
- mhDisposeFilter = 'all';
+ mhDisposeFilter = 'welfare';
document.getElementById('mhDetailSubhead').classList.remove('mh-detail-subhead--show');
mhRenderTimeline(meal);
document.querySelector('.mh-timeline-wrap').scrollTop = 0;
@@ -2068,30 +2064,39 @@
if (!node) return '暂无开餐数据
';
var d = node.detail || {};
var dishes = d.dishes || [];
- var fo = d.firstOrder;
+ // 收集本餐次所有补餐量,按菜品名分组(用于反推初始上餐量)
+ var supplyMap = {};
+ meal.nodes.forEach(function (n) {
+ if (n.type === 'add' && n.supply) {
+ var dn = n.supply.dish;
+ supplyMap[dn] = (supplyMap[dn] || 0) + n.supply.addAmount;
+ }
+ });
+ // 计算总制作量(累计上餐量之和)
+ var totalMade = 0;
+ dishes.forEach(function (it) { totalMade += (it.planned || 0); });
var html = '';
html += '
开餐时间' + node.time + '
';
html += '
菜品数' + dishes.length + ' 道
';
- if (fo) {
- html += '
首单 · ' + fo.time + ''
- + '¥' + fo.amount.toFixed(2)
- + ' ' + fo.customer + '
';
- } else {
- html += '
首单—
';
- }
+ html += '
总制作量' + totalMade.toFixed(1) + ' kg
';
html += '
';
html += '';
- html += '';
- html += '| 菜品 | 上餐时间 | 制作人 | 操作人 | 计划量 (kg) | 已售 (kg) |
';
+ html += '';
+ html += '| 菜品 | 上餐时间 | 制作人 | 操作人 | 初始上餐量 (kg) | 累计上餐量 (kg) | 已售 (kg) |
';
html += '';
dishes.forEach(function (d) {
+ // 累计上餐量=planned;初始上餐量=planned − 该菜品补餐总量
+ var accumulated = d.planned || 0;
+ var addAmount = supplyMap[d.name] || 0;
+ var initial = +(accumulated - addAmount).toFixed(1);
html += ''
+ '| ' + d.name + ' | '
+ '' + d.serveTime + ' | '
+ '' + d.maker + ' | '
+ '' + d.operator + ' | '
- + '' + d.planned.toFixed(1) + ' | '
- + '' + d.sold.toFixed(1) + ' | '
+ + '' + initial.toFixed(1) + ' | '
+ + '' + accumulated.toFixed(1) + ' | '
+ + '' + (d.sold || 0).toFixed(1) + ' | '
+ '
';
});
html += '
';
@@ -2103,31 +2108,31 @@
var supplies = [];
addNodes.forEach(function (n) { if (n.supply) supplies.push(n.supply); });
if (supplies.length === 0) return '暂无补餐记录
';
- var totalAmount = 0, autoCount = 0, manualCount = 0;
+ var totalAmount = 0;
+ var dishSet = {};
supplies.forEach(function (s) {
totalAmount += s.addAmount;
- if (s.reason === '自动预警') autoCount++;
- else manualCount++;
+ dishSet[s.dish] = (dishSet[s.dish] || 0) + 1;
});
+ var dishCount = Object.keys(dishSet).length;
var html = '';
- html += '
补餐次数' + supplies.length + ' 次
';
- html += '
累计补做量' + totalAmount.toFixed(1) + ' kg
';
- html += '
自动预警' + autoCount + ' 次
';
- html += '
手动通知' + manualCount + ' 次
';
+ html += '
补餐菜品数' + dishCount + ' 种
';
+ html += '
补餐次数' + supplies.length + ' 次
';
+ html += '
补餐总量' + totalAmount.toFixed(1) + ' kg
';
html += '
';
html += '';
- html += '';
- html += '| 时间 | 餐品 | 触发余量 (kg) | 补做量 (kg) | 原因 | 操作人 | 状态 |
';
+ html += '';
+ html += '| 补餐时间 | 餐品 | 补餐量 (kg) | 补餐前余量 (kg) | 补餐后余量 (kg) | 操作人 | 状态 |
';
html += '';
supplies.forEach(function (s, i) {
- var reasonCls = s.reason === '自动预警' ? 'mh-tl-tag--gold' : 'mh-tl-tag--gray';
var statusCls = s.status === '已补做' ? 'mh-tl-tag--green' : 'mh-tl-tag--gold';
+ var afterLeftover = +(s.triggerLeftover + s.addAmount).toFixed(1);
html += ''
+ '| ' + (addNodes[i] ? addNodes[i].time : '—') + ' | '
+ '' + (s.dishEmoji || '') + ' ' + s.dish + ' | '
- + '' + s.triggerLeftover.toFixed(1) + ' | '
+ '' + s.addAmount.toFixed(1) + ' | '
- + '' + s.reason + ' | '
+ + '' + s.triggerLeftover.toFixed(1) + ' | '
+ + '' + afterLeftover.toFixed(1) + ' | '
+ '' + s.operator + ' | '
+ '' + s.status + ' | '
+ '
';
@@ -2138,50 +2143,25 @@
function mhRenderCloseDetail(meal) {
var closeNodes = meal.nodes.filter(function (n) { return n.type === 'close'; });
- if (closeNodes.length === 0) return '暂无闭餐记录
';
-
- // 汇总各闭餐事件的订单与营收
- var closeEvents = closeNodes.map(function (cn) {
- var orders = 0, revenue = 0;
- if (cn.closeDetail && cn.closeDetail.lines) {
- cn.closeDetail.lines.forEach(function (l) { orders += l.orders; revenue += l.revenue; });
- }
- return { time: cn.time, orders: orders, revenue: revenue, avgPer: orders > 0 ? (revenue / orders) : 0 };
- });
-
- var totalOrders = 0, totalRevenue = 0;
- closeEvents.forEach(function (e) { totalOrders += e.orders; totalRevenue += e.revenue; });
- var overallAvg = totalOrders > 0 ? (totalRevenue / totalOrders) : 0;
-
- var endInfo = meal.endTime
- ? ('' + meal.endTime + ' · ' + meal.operator + '')
- : '进行中';
-
+ if (closeNodes.length === 0) return '暂无收餐记录
';
+ // 收餐是一次性动作,取最新一条作为收餐事件
+ var last = closeNodes[closeNodes.length - 1];
+ var cd = last.closeDetail || {};
+ var methodLabel = cd.methodLabel || '—';
+ var methodCls = cd.method === 'staff_meal' ? 'mh-tl-tag--blue' : 'mh-tl-tag--purple';
+ // 第一组:收餐主信息(3 卡片)
var html = '';
- html += '
结束用餐' + endInfo + '
';
- html += '
闭餐次数' + closeEvents.length + ' 次
';
- html += '
总订单' + totalOrders + ' 单
';
- html += '
总营收¥' + totalRevenue.toFixed(2) + '
';
+ html += '
收餐时间' + (cd.closeTime || last.time) + '
';
+ html += '
收餐人' + (cd.operator || '—') + '
';
+ html += '
收餐方式' + methodLabel + '
';
+ html += '
';
+ // 第二组:截止收餐时主要统计数据(4 卡片)
+ html += '';
+ html += '
截止收餐订单数' + (cd.orders || 0) + ' 单
';
+ html += '
截止收餐营收¥' + (cd.revenue || 0).toFixed(2) + '
';
+ html += '
客单价¥' + (cd.avgPer || 0).toFixed(2) + '
';
+ html += '
截止收餐余量' + (cd.surplus || '—') + ' kg
';
html += '
';
- html += '';
- html += '';
- html += '| 闭餐时间 | 订单数 | 营收 | 客单价 |
';
- html += '';
- closeEvents.forEach(function (e) {
- html += ''
- + '| ' + e.time + ' | '
- + '' + e.orders + ' | '
- + '¥' + e.revenue.toFixed(2) + ' | '
- + '¥' + e.avgPer.toFixed(2) + ' | '
- + '
';
- });
- html += ''
- + '| 合计 · ' + closeEvents.length + ' 次闭餐 | '
- + '' + totalOrders + ' | '
- + '¥' + totalRevenue.toFixed(2) + ' | '
- + '¥' + overallAvg.toFixed(2) + ' | '
- + '
';
- html += '
';
return html;
}
@@ -2191,6 +2171,7 @@
var d = node.dispose;
if (!d) return '暂无处置记录
';
var html = '';
+ // 处置方式统计卡片(5种方式)
html += '';
(d.methods || []).forEach(function (m) {
var mInfo = MH_METHOD_MAP[m.key] || {};
@@ -2206,41 +2187,131 @@
+ '操作人 · ' + d.operator + ''
+ '
';
html += '';
- html += '';
+ // 5 种方式过滤 Tab(无"全部",每种方式独立查看)
+ var allRecords = d.records || [];
var filters = [
- { key: 'all', label: '全部 (' + (d.records || []).length + ')' },
- { key: 'welfare', label: '员工福利' },
- { key: 'donate', label: '捐赠' },
- { key: 'waste', label: '废弃' },
- { key: 'reprocess', label: '加工再利用' }
+ { key: 'welfare', label: '员工福利' },
+ { key: 'donate', label: '捐赠' },
+ { key: 'waste', label: '废弃' },
+ { key: 'reprocess', label: '加工再利用' },
+ { key: 'cabinet', label: '存入智能货柜' }
];
+ html += '
';
filters.forEach(function (f) {
- html += '' + f.label + '';
+ var count = allRecords.filter(function (r) { return r.method === f.key; }).length;
+ html += '' + f.label + ' (' + count + ')';
});
html += '
';
- var records = d.records || [];
- if (mhDisposeFilter !== 'all') records = records.filter(function (r) { return r.method === mhDisposeFilter; });
- html += '
';
- html += '';
- html += '| 时间 | 餐品 | 处置方式 | 处置量 (kg) | 接收方 · 领用人 | 操作人 | 备注 |
';
- html += '';
- if (records.length === 0) {
- html += '| 暂无该类型的处置记录 |
';
- } else {
- records.forEach(function (r) {
- var mInfo = MH_METHOD_MAP[r.method] || {};
- html += ''
- + '| ' + r.time + ' | '
- + '' + r.dish + ' | '
- + '' + (mInfo.label || r.method) + ' | '
- + '' + r.amount.toFixed(1) + ' | '
- + '' + r.receiver + ' | '
- + '' + r.operator + ' | '
- + '' + (r.note || '—') + ' | '
- + '
';
- });
+ // 当前方式的记录
+ var records = allRecords.filter(function (r) { return r.method === mhDisposeFilter; });
+ // 按方式渲染独立列结构
+ if (mhDisposeFilter === 'welfare') {
+ // 员工福利: 时间/餐品/处置量/领用人/操作人/备注
+ html += '';
+ html += '';
+ html += '| 时间 | 餐品 | 处置量 (kg) | 领用人 | 操作人 | 备注 |
';
+ html += '';
+ if (records.length === 0) {
+ html += '| 暂无员工福利处置记录 |
';
+ } else {
+ records.forEach(function (r) {
+ html += ''
+ + '| ' + r.time + ' | '
+ + '' + r.dish + ' | '
+ + '' + r.amount.toFixed(1) + ' | '
+ + '' + (r.receiver || '—') + ' | '
+ + '' + r.operator + ' | '
+ + '' + (r.note || '—') + ' | '
+ + '
';
+ });
+ }
+ html += '
';
+ } else if (mhDisposeFilter === 'donate') {
+ // 捐赠: 时间/餐品/处置量/接收机构/操作人/备注
+ html += '';
+ html += '';
+ html += '| 时间 | 餐品 | 处置量 (kg) | 接收机构 | 操作人 | 备注 |
';
+ html += '';
+ if (records.length === 0) {
+ html += '| 暂无捐赠处置记录 |
';
+ } else {
+ records.forEach(function (r) {
+ html += ''
+ + '| ' + r.time + ' | '
+ + '' + r.dish + ' | '
+ + '' + r.amount.toFixed(1) + ' | '
+ + '' + (r.receiver || '—') + ' | '
+ + '' + r.operator + ' | '
+ + '' + (r.note || '—') + ' | '
+ + '
';
+ });
+ }
+ html += '
';
+ } else if (mhDisposeFilter === 'waste') {
+ // 废弃: 时间/餐品/处置量/废弃原因/操作人/备注
+ html += '';
+ html += '';
+ html += '| 时间 | 餐品 | 处置量 (kg) | 废弃原因 | 操作人 | 备注 |
';
+ html += '';
+ if (records.length === 0) {
+ html += '| 暂无废弃处置记录 |
';
+ } else {
+ records.forEach(function (r) {
+ html += ''
+ + '| ' + r.time + ' | '
+ + '' + r.dish + ' | '
+ + '' + r.amount.toFixed(1) + ' | '
+ + '' + (r.reason || '—') + ' | '
+ + '' + r.operator + ' | '
+ + '' + (r.note || '—') + ' | '
+ + '
';
+ });
+ }
+ html += '
';
+ } else if (mhDisposeFilter === 'reprocess') {
+ // 加工再利用: 时间/餐品/处置量/加工用途/接收人/操作人/备注
+ html += '';
+ html += '';
+ html += '| 时间 | 餐品 | 处置量 (kg) | 加工用途 | 接收人 | 操作人 | 备注 |
';
+ html += '';
+ if (records.length === 0) {
+ html += '| 暂无加工再利用处置记录 |
';
+ } else {
+ records.forEach(function (r) {
+ html += ''
+ + '| ' + r.time + ' | '
+ + '' + r.dish + ' | '
+ + '' + r.amount.toFixed(1) + ' | '
+ + '' + (r.processing || '—') + ' | '
+ + '' + (r.receiver || '—') + ' | '
+ + '' + r.operator + ' | '
+ + '' + (r.note || '—') + ' | '
+ + '
';
+ });
+ }
+ html += '
';
+ } else if (mhDisposeFilter === 'cabinet') {
+ // 存入智能货柜: 时间/餐品/处置量/货柜编号/操作人/备注
+ html += '';
+ html += '';
+ html += '| 时间 | 餐品 | 处置量 (kg) | 货柜编号 | 操作人 | 备注 |
';
+ html += '';
+ if (records.length === 0) {
+ html += '| 暂无存入智能货柜处置记录 |
';
+ } else {
+ records.forEach(function (r) {
+ html += ''
+ + '| ' + r.time + ' | '
+ + '' + r.dish + ' | '
+ + '' + r.amount.toFixed(1) + ' | '
+ + '' + (r.cabinetId || '—') + ' | '
+ + '' + r.operator + ' | '
+ + '' + (r.note || '—') + ' | '
+ + '
';
+ });
+ }
+ html += '
';
}
- html += '
';
return html;
}
diff --git a/other-module/canteen-device/doc/CHANGELOG.md b/other-module/canteen-device/doc/CHANGELOG.md
index b721198..cf1034f 100644
--- a/other-module/canteen-device/doc/CHANGELOG.md
+++ b/other-module/canteen-device/doc/CHANGELOG.md
@@ -1,5 +1,17 @@
# 餐厅终端设备模块变更日志
+## 2026-08-18 · 结算设备统计页收餐记录 + 毛菜验收秤实收规格可编辑
+
+### 变更一:结算设备统计页收餐与余量处置记录(`checkout-device/07-statistics.html`)
+- 新增收餐记录:全部收餐/全部餐线收餐完成事件,取最新一条作为收餐时间基准
+- 新增余量处置记录:临期食品·冷藏保存·加工再利用·值班餐/加班餐等处置方式
+- 各记录弹窗明细含原因/用途/接收人/操作人/备注等字段,初始上餐量·制作人·上餐时间展示
+
+### 变更二:毛菜验收秤实收规格增强(`raw-veg-inspection-scale/01-verify-inspection.html`)
+- 实收规格列拆分为「克重 + 单位」组合编辑:单位组合字符串展示,支持分类过滤(叶菜类等)
+- 采购单价为数据字典值只读展示(灰字),实收量/实收单价/实收金额可编辑
+- 编辑时仅持久化到本次订单,输入不重渲染避免光标丢失
+
## 2026-08-14 · 毛菜验收秤 PAD 外壳改造 + 后厨补餐屏首版设计
### 变更一:毛菜验收秤全页面 PAD 居中外壳改造(`raw-veg-inspection-scale/` 全部 9 页)
diff --git a/other-module/canteen-device/raw-veg-inspection-scale/01-verify-inspection.html b/other-module/canteen-device/raw-veg-inspection-scale/01-verify-inspection.html
index 6942c19..859a373 100644
--- a/other-module/canteen-device/raw-veg-inspection-scale/01-verify-inspection.html
+++ b/other-module/canteen-device/raw-veg-inspection-scale/01-verify-inspection.html
@@ -308,6 +308,33 @@
}
.food-row__spec-select:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
.food-row__spec-select option { background: #1a2b4a; color: #fff; }
+ /* 采购规格列(只读):克重+单位组合字符串展示,不可编辑 */
+ .food-row__spec-readonly {
+ display: inline-block; padding: 2px 6px; border-radius: 4px;
+ background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.55);
+ font-size: 11px; font-weight: 500; white-space: nowrap;
+ }
+ /* 实收规格列:克重 input + 单位 select 连体容器(参考 sup-in-raw.html 的 .spec-input-group) */
+ .food-row__spec-input-group { display: flex; align-items: stretch; width: 100%; height: 24px; }
+ .food-row__spec-input-group input {
+ flex: 1; min-width: 0; padding: 0 4px;
+ background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 4px 0 0 4px; border-right: none;
+ color: rgba(255,255,255,0.85); font-size: 11px; text-align: center;
+ outline: none; -moz-appearance: textfield;
+ }
+ .food-row__spec-input-group input::-webkit-outer-spin-button,
+ .food-row__spec-input-group input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
+ .food-row__spec-input-group input:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
+ .food-row__spec-input-group input::placeholder { color: rgba(255,255,255,0.25); }
+ .food-row__spec-input-group select {
+ width: 44px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 0 4px 4px 0; padding: 2px 2px;
+ color: rgba(255,255,255,0.7); font-size: 11px; text-align: center;
+ outline: none; appearance: auto; cursor: pointer;
+ }
+ .food-row__spec-input-group select:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
+ .food-row__spec-input-group select option { background: #1a2b4a; color: #fff; }
.food-row__amount { font-size: 12px; color: #faad14; font-weight: 500; }
.food-row__actual { color: #4a90d9; font-weight: 600; }
.food-row__status { font-size: 11px; }
@@ -1035,39 +1062,39 @@
/* ===== 食材目录(称重验收,存储单位克,展示层用 formatGramForCanteen 还原"斤") ===== */
/* verifyType:'weight' 表示称重验收,可用 ['斤','公斤','克','千克'] */
var FOOD_CATALOG = {
- '大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001', verifyType: 'weight' },
- '圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002', verifyType: 'weight' },
- '菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003', verifyType: 'weight' },
- '油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004', verifyType: 'weight' },
- '生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005', verifyType: 'weight' },
- '芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006', verifyType: 'weight' },
- '西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001', verifyType: 'weight' },
- '花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002', verifyType: 'weight' },
- '黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001', verifyType: 'weight' },
- '西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002', verifyType: 'weight' },
- '茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003', verifyType: 'weight' },
- '青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004', verifyType: 'weight' },
- '豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005', verifyType: 'weight' },
- '土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001', verifyType: 'weight' },
- '胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002', verifyType: 'weight' },
- '白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007', verifyType: 'weight' },
- '猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001', verifyType: 'weight' },
- '五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002', verifyType: 'weight' },
- '草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001', verifyType: 'weight' },
- '虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002', verifyType: 'weight' }
+ '大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001', verifyType: 'weight', specGram: 1000, specUnit: '袋' },
+ '胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
+ '虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002', verifyType: 'weight', specGram: 250, specUnit: '袋' }
};
/* ===== 调料目录(点收验收,按规格/数量验收) ===== */
/* verifyType:'count' 表示点收验收,可用 ['袋','瓶','箱','桶','罐','份','个','只','条','把'] */
var SPICE_CATALOG = {
- '酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001', verifyType: 'count' },
- '醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002', verifyType: 'count' },
- '料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003', verifyType: 'count' },
- '盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004', verifyType: 'count' },
- '白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005', verifyType: 'count' },
- '鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006', verifyType: 'count' },
- '食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007', verifyType: 'count' },
- '花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008', verifyType: 'count' }
+ '酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001', verifyType: 'count', specGram: 500, specUnit: '瓶' },
+ '醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002', verifyType: 'count', specGram: 500, specUnit: '瓶' },
+ '料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003', verifyType: 'count', specGram: 500, specUnit: '瓶' },
+ '盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004', verifyType: 'count', specGram: 500, specUnit: '袋' },
+ '白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005', verifyType: 'count', specGram: 1000, specUnit: '袋' },
+ '鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006', verifyType: 'count', specGram: 200, specUnit: '罐' },
+ '食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007', verifyType: 'count', specGram: 5000, specUnit: '桶' },
+ '花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008', verifyType: 'count', specGram: 100, specUnit: '袋' }
};
/* 判断食材是否为调料(按规格验收) */
@@ -1442,12 +1469,12 @@
var isDirect = !!selectedOrder._direct;
/* 列宽(单位 px,首列 1fr 自适应)
- * 有订单(10列): 食材/采购规格/采购量/采购单价/采购单金额/实收规格/实收量/实收单价/实收总价/操作
- * 无订单(6列): 食材/实收规格/实收量/实收单价/实收总价/操作
+ * 有订单(10列): 食材/采购规格(只读)/采购量/采购单价/采购单金额/实收规格(input+select)/实收量/实收单价/实收总价/操作
+ * 无订单(6列): 食材/实收规格(input+select)/实收量/实收单价/实收总价/操作
*/
var gridCols = isDirect
- ? '1fr 50px 50px 56px 60px 52px'
- : '1fr 56px 50px 56px 60px 50px 50px 56px 60px 52px';
+ ? '1fr 96px 50px 56px 60px 52px'
+ : '1fr 60px 50px 56px 60px 96px 50px 56px 60px 52px';
headEl.style.gridTemplateColumns = gridCols;
/* 表头文字 */
headEl.innerHTML = isDirect
@@ -1493,22 +1520,31 @@
? '
--'
: '
' + (spice ? orderQty : orderQty.toFixed(1)) + '';
- /* 规格下拉选项(按 verifyType 分类过滤) */
+ /* 规格单位下拉选项(按 verifyType 分类过滤,用于实收规格单位 select) */
var UNIT_OPTIONS_WEIGHT = ['斤','公斤','克','千克'];
var UNIT_OPTIONS_COUNT = ['袋','瓶','箱','桶','罐','份','个','只','条','把'];
var verifyType = info.verifyType || 'weight';
var UNIT_OPTIONS = (verifyType === 'count') ? UNIT_OPTIONS_COUNT : UNIT_OPTIONS_WEIGHT;
- var currentUnit = (f._customUnit !== undefined) ? f._customUnit : info.unit;
- var actualUnit = (f._actualUnit !== undefined) ? f._actualUnit : currentUnit;
- function buildUnitSelect(selected, onChangeFn) {
- var opts = '';
- for (var u = 0; u < UNIT_OPTIONS.length; u++) {
- var unit = UNIT_OPTIONS[u];
- opts += '
';
- }
- return '
';
+ /* 采购规格克重/单位(数据字典值,只读展示;格式 "500g/袋") */
+ var specGram = info.specGram || 0;
+ var specUnit = info.specUnit || info.unit || '';
+ /* 采购规格列(只读):克重+单位组合字符串 */
+ var specHtml = '
' + specGram + 'g/' + specUnit + '';
+ /* 实收规格:默认代入采购规格数据(克重/单位),用户可编辑 */
+ var actualSpecGram = (f._actualSpecGram !== undefined) ? f._actualSpecGram : specGram;
+ var actualSpecUnit = (f._actualSpecUnit !== undefined) ? f._actualSpecUnit : specUnit;
+ var unitOpts = '';
+ for (var u = 0; u < UNIT_OPTIONS.length; u++) {
+ var unit = UNIT_OPTIONS[u];
+ unitOpts += '
';
}
- var specHtml = buildUnitSelect(currentUnit, 'updateFoodUnit(' + i + ', this.value)');
+ var actualSpecHtml = '
'
+ + ''
+ + ''
+ + '
';
/* 采购单价列(只读) + 采购单金额列(只读,= 采购单价 × 采购量) */
var priceReadOnlyHtml = '
¥' + currentPrice.toFixed(1) + '';
@@ -1571,8 +1607,6 @@
+ '
';
var isCurrent = (i === currentFoodIdx) ? ' current' : '';
- /* 实收规格列(下拉可选,默认跟随采购规格) */
- var actualSpecHtml = buildUnitSelect(actualUnit, 'updateActualUnit(' + i + ', this.value)');
/* 组装行(按订单类型选不同列顺序) */
var rowHtml = ''
@@ -1638,20 +1672,20 @@
}
}
- /* 更新食材规格/单位(用户编辑) */
- function updateFoodUnit(idx, newUnit) {
+ /* 更新实收规格克重(用户编辑) — 仅持久化到本次订单的食材状态,不重渲染避免光标丢失 */
+ function updateActualSpecGram(idx, newVal) {
if (!selectedOrder || idx < 0) return;
- var unit = (newUnit || '').trim();
- if (!unit) return;
- selectedOrder.foods[idx]._customUnit = unit;
+ var gram = parseFloat(newVal);
+ if (isNaN(gram) || gram < 0) gram = 0;
+ selectedOrder.foods[idx]._actualSpecGram = gram;
}
- /* 更新实收规格(用户在下拉中切换) */
+ /* 更新实收规格单位(用户在下拉中切换) */
function updateActualUnit(idx, newUnit) {
if (!selectedOrder || idx < 0) return;
var unit = (newUnit || '').trim();
if (!unit) return;
- selectedOrder.foods[idx]._actualUnit = unit;
+ selectedOrder.foods[idx]._actualSpecUnit = unit;
}
/* 验收不通过(从操作列触发) */
@@ -2343,7 +2377,7 @@
var orderQty = spice ? (food._orderQty || food.weight) : food.weight;
var refundQty = (ngScope === 'full') ? orderQty : Math.ceil(orderQty / 2);
var refundAmount = currentPrice * refundQty;
- var unitLabel = food._customUnit || (spice ? findFoodInfo(food.name).unit : '斤');
+ var unitLabel = spice ? findFoodInfo(food.name).unit : '斤';
var refundDisplay = document.getElementById('ngRefundAmountDisplay');
var refundInput = document.getElementById('ngRefundAmountInput');
diff --git a/web-admin/nutrition/ano-result.html b/web-admin/nutrition/ano-result.html
index cc0ec99..686e0c0 100644
--- a/web-admin/nutrition/ano-result.html
+++ b/web-admin/nutrition/ano-result.html
@@ -146,6 +146,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
+
@@ -162,7 +163,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
-
共 89 条异常记录
+
共 91 条异常记录
@@ -282,13 +283,33 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
2026-05-11 08:40 |
详情 |
+
+ | ERR-2026-011 |
+ 设备告警 |
+ 人脸识别 |
+ 档口秤 FCD-B007(2 号档口)于 2026-05-10 11:30~11:55 期间人脸识别连续失败 12 次,触发设备告警 |
+ 中 |
+ v1.5.0 |
+ 2026-05-10 11:55 |
+ 详情 |
+
+
+ | ERR-2026-012 |
+ 设备告警 |
+ 人脸识别 |
+ 餐线秤 FCD-A012(3 号窗口)于 2026-05-09 17:20~18:10 期间人脸识别累计失败 28 次(阈值 20 次),触发设备告警 |
+ 高 |
+ v1.5.0 |
+ 2026-05-09 18:10 |
+ 详情 |
+
@@ -482,8 +643,726 @@ document.querySelectorAll('tbody tr').forEach(row=>{
if(link.textContent.includes('远程重启')){
link.addEventListener('click',()=>showRebootConfirm(row));
}
+ if(link.textContent.includes('设备概览')){
+ link.addEventListener('click',()=>showDeviceOverview(row));
+ }
});
});
+
+/* ===== 设备概览大弹窗(原型演示:Tab 集中演示各类设备,实际交付按设备类型显示对应概览) ===== */
+/* Tab 定义:仅「智能货柜」已实现内容,其余为占位 */
+var OV_TABS=[
+ {key:'plate',name:'餐盘柜',icon:'🍽️'},
+ {key:'smart',name:'智能货柜',icon:'🗄️'},
+ {key:'raw',name:'毛菜货柜',icon:'🥬'},
+ {key:'clean',name:'净菜货柜',icon:'🧺'},
+ {key:'meter-e',name:'智能电表',icon:'⚡'},
+ {key:'meter-w',name:'智能水表',icon:'💧'},
+ {key:'meter-g',name:'智能气表',icon:'🔥'}
+];
+var OV_CURRENT_TAB='smart';
+
+function showDeviceOverview(row){
+ const cells=row.querySelectorAll('td');
+ const devCode=cells[0].textContent.trim();
+ const devName=cells[1].textContent.trim();
+ const devType=cells[2].textContent.trim();
+
+ const tabsHtml=OV_TABS.map(t=>`${t.icon} ${t.name}
`).join('');
+
+ const overlay=document.createElement('div');
+ overlay.className='ov-modal-mask';
+ overlay.id='ovModalMask';
+ overlay.innerHTML=`
+
+
+
+ ⚠️
+ 原型演示说明:以下 Tab 仅用于集中演示各设备类型的概览效果,实际交付时根据设备类型自动显示对应的概览内容。
+
+
${tabsHtml}
+
${renderOvTabBody(OV_CURRENT_TAB)}
+
`;
+ overlay.addEventListener('click',e=>{if(e.target===overlay)closeDeviceOverview();});
+ document.body.appendChild(overlay);
+}
+function closeDeviceOverview(){
+ const m=document.getElementById('ovModalMask');
+ if(!m)return;
+ closeSlotHistory();
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+function switchOvTab(key){
+ OV_CURRENT_TAB=key;
+ document.querySelectorAll('#ovModalMask .ov-tab').forEach(t=>t.classList.toggle('active',t.dataset.key===key));
+ document.getElementById('ovTabBody').innerHTML=renderOvTabBody(key);
+ /* 水电气表 Tab 内容含分页列表,需在 DOM 插入后再渲染 */
+ if(key==='meter-e')renderEnergyDaily('elec');
+ if(key==='meter-w')renderEnergyDaily('water');
+ if(key==='meter-g')renderEnergyDaily('gas');
+}
+
+/* 按 Tab 渲染内容:占位 / 智能货柜可视化 */
+function renderOvTabBody(key){
+ if(key==='smart')return renderSmartCabinet();
+ if(key==='plate')return renderPlateCabinet();
+ if(key==='clean')return renderStockCabinet('clean');
+ if(key==='raw')return renderStockCabinet('raw');
+ if(key==='meter-e')return renderEnergyTab('elec');
+ if(key==='meter-w')return renderEnergyTab('water');
+ if(key==='meter-g')return renderEnergyTab('gas');
+ const tab=OV_TABS.find(t=>t.key===key);
+ return `
+
${tab?tab.icon:'📺'}
+
${tab?tab.name:''}概览待设计
+
原型演示阶段暂未展开,实际交付时根据设备类型展示对应概览效果
+
`;
+}
+
+/* ===== 智能货柜:柜子轮廓布局 + 储物状态(参考 prod-proc-cabinet.html 左侧柜子样式) ===== */
+/* 格口 Mock:状态 occupied 储物中 / warning 临期预警 / alert 过期预警 / empty 空闲 */
+var OV_SLOTS=[
+ {id:'B1-01',name:'宫保鸡丁餐品包',remain:'2.4kg',expire:'08-19 到期',status:'occupied',badge:'green'},
+ {id:'B1-02',name:'红烧肉餐品包',remain:'3.1kg',expire:'08-19 到期',status:'occupied',badge:'green'},
+ {id:'B1-03',name:'清蒸鱼餐品包',remain:'1.8kg',expire:'08-18 即将到期',status:'warning',badge:'orange',expireColor:'var(--warning)'},
+ {id:'B1-04',name:'净西兰花',remain:'4.5kg',expire:'08-20 到期',status:'occupied',badge:'green'},
+ {id:'B1-05',name:'净胡萝卜',remain:'5.2kg',expire:'08-20 到期',status:'occupied',badge:'green'},
+ {id:'B1-06',name:'净白菜',remain:'6.0kg',expire:'08-10 已过期',status:'alert',badge:'red',expireColor:'var(--danger)'},
+ {id:'B1-07',name:'净香菇',remain:'2.2kg',expire:'08-19 到期',status:'occupied',badge:'green'},
+ {id:'B1-08',status:'empty'},
+ {id:'B1-09',status:'empty'},
+ {id:'B1-10',status:'empty'}
+];
+
+function renderSmartCabinet(){
+ const slotsHtml=OV_SLOTS.map((s,i)=>{
+ const cls=s.status==='empty'?'ov-slot ov-slot--empty':'ov-slot '+s.status;
+ if(s.status==='empty'){
+ return ``;
+ }
+ return `
+
+
${s.id}
+
${s.name}
+
${s.remain}
+
${s.expire}
+
`;
+ }).join('');
+ return `
+
+ ${slotsHtml}
+ `;
+}
+
+/* ===== 餐盘柜:格口 + 会员绑定状态 ===== */
+/* 格口 Mock:状态 bound 已绑定 / using 使用中(盘子被取出) / free 未绑定 / disabled 异常停用 */
+var PLATE_SLOTS=[
+ {id:'P1-01',status:'bound',member:{name:'张*明',level:'黄金会员',levelCls:'',phone:'138****5621',code:'U20250311',dept:'生产部·一车间'},bindTime:'2026-06-01 09:30',lastUse:'2026-08-18 12:05 · 已归还',useCount:128},
+ {id:'P1-02',status:'using',member:{name:'李*华',level:'铂金会员',levelCls:'vip',phone:'139****3387',code:'U20240827',dept:'技术部·平台组'},bindTime:'2026-05-12 10:15',lastUse:'2026-08-18 12:20 · 使用中(已取出)',useCount:203},
+ {id:'P1-03',status:'bound',member:{name:'王*强',level:'普通会员',levelCls:'',phone:'137****8845',code:'U20260702',dept:'后勤部·餐饮科'},bindTime:'2026-07-20 14:00',lastUse:'2026-08-18 11:50 · 已归还',useCount:45},
+ {id:'P1-04',status:'bound',member:{name:'赵*敏',level:'黄金会员',levelCls:'',phone:'136****2210',code:'U20230915',dept:'财务部·核算组'},bindTime:'2026-04-08 09:00',lastUse:'2026-08-18 12:10 · 已归还',useCount:312},
+ {id:'P1-05',status:'using',member:{name:'陈*芳',level:'VIP会员',levelCls:'vip',phone:'135****7732',code:'U20250422',dept:'人事部·招聘组'},bindTime:'2026-06-25 11:20',lastUse:'2026-08-18 12:30 · 使用中(已取出)',useCount:97},
+ {id:'P1-06',status:'bound',member:{name:'刘*洋',level:'普通会员',levelCls:'',phone:'188****4406',code:'U20260801',dept:'生产部·二车间'},bindTime:'2026-08-01 08:40',lastUse:'2026-08-18 11:45 · 已归还',useCount:23},
+ {id:'P1-07',status:'disabled',member:null,bindTime:'—',lastUse:'2026-08-15 09:12 · 格口门锁故障',useCount:0},
+ {id:'P1-08',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0},
+ {id:'P1-09',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0},
+ {id:'P1-10',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0}
+];
+
+/* 可分配会员 Mock(原型演示) */
+var PLATE_MEMBERS=[
+ {name:'孙*丽',level:'黄金会员',levelCls:'',phone:'133****9012',code:'U20260418',dept:'生产部·三车间'},
+ {name:'周*军',level:'普通会员',levelCls:'',phone:'132****5566',code:'U20251130',dept:'技术部·运维组'},
+ {name:'吴*霞',level:'铂金会员',levelCls:'vip',phone:'131****7789',code:'U20240612',dept:'质检部·化验组'},
+ {name:'郑*涛',level:'普通会员',levelCls:'',phone:'130****3344',code:'U20260220',dept:'后勤部·保卫科'},
+ {name:'何*静',level:'黄金会员',levelCls:'',phone:'159****8823',code:'U20250805',dept:'财务部·出纳组'}
+];
+
+var PLATE_STATUS_META={
+ bound:{text:'已绑定',cls:'occupied',badge:'green'},
+ using:{text:'使用中',cls:'using',badge:'blue'},
+ free:{text:'未绑定',cls:'ov-slot--empty',badge:null},
+ disabled:{text:'异常停用',cls:'disabled',badge:'gray'}
+};
+
+function renderPlateCabinet(){
+ const boundCount=PLATE_SLOTS.filter(s=>s.status==='bound'||s.status==='using').length;
+ const totalCount=PLATE_SLOTS.length;
+ const slotsHtml=PLATE_SLOTS.map((s,i)=>{
+ const meta=PLATE_STATUS_META[s.status];
+ if(s.status==='free'){
+ return ``;
+ }
+ const memberHtml=s.member
+ ?`${s.member.name}
${s.member.level}
`
+ :`停用
`;
+ return ``;
+ }).join('');
+ return `
+
+ ${slotsHtml}
+
+
+
+
+
+
点击格口可查看绑定详情并分配/解绑
+
`;
+}
+
+/* ===== 餐盘柜格口详情弹窗(叠加在设备概览之上) ===== */
+function showPlateSlot(idx){
+ const s=PLATE_SLOTS[idx];
+ const meta=PLATE_STATUS_META[s.status];
+ const m=s.member;
+ const statusTag=s.status==='bound'?'已绑定':s.status==='using'?'使用中':s.status==='free'?'未绑定':'异常停用';
+ let html=`格口信息
+
使用状态${statusTag}
+
所属柜1号餐盘柜
+
格口${s.id}
+
绑定时间${s.bindTime}
+
`;
+ if(m){
+ html+=`绑定会员信息
+
会员姓名${m.name}
+
会员等级${m.level}
+
手机号${m.phone}
+
用户编号${m.code}
+
`;
+ html+=`使用情况
+
最后使用状态${s.lastUse}
+
累计使用次数${s.useCount} 次
+
`;
+ }else{
+ html+=`使用情况
+
最后使用状态${s.lastUse}
+
累计使用次数${s.useCount} 次
+
`;
+ }
+ /* 操作按钮:未绑定可分配;已绑定/使用中可解绑;异常停用不可操作 */
+ let footer='';
+ if(s.status==='free'){
+ footer=``;
+ }else if(m){
+ footer=``;
+ }else{
+ footer=`格口异常停用中,维修恢复后方可分配`;
+ }
+ const mask=document.createElement('div');
+ mask.className='slot-his-mask';
+ mask.id='plateSlotMask';
+ mask.innerHTML=``;
+ mask.addEventListener('click',e=>{if(e.target===mask)closePlateSlot();});
+ document.body.appendChild(mask);
+}
+function closePlateSlot(){
+ const m=document.getElementById('plateSlotMask');
+ if(!m)return;
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+function refreshPlateTab(){
+ if(OV_CURRENT_TAB==='plate'&&document.getElementById('ovTabBody')){
+ document.getElementById('ovTabBody').innerHTML=renderPlateCabinet();
+ }
+}
+
+/* ===== 格口分配:会员选择层 ===== */
+var PLATE_ASSIGN_IDX=-1;
+var PLATE_ASSIGN_SELECTED=null;
+function openPlateAssign(idx){
+ PLATE_ASSIGN_IDX=idx;
+ PLATE_ASSIGN_SELECTED=null;
+ const mask=document.createElement('div');
+ mask.className='member-picker-mask';
+ mask.id='plateAssignMask';
+ mask.innerHTML=``;
+ mask.addEventListener('click',e=>{if(e.target===mask)closePlateAssign();});
+ document.body.appendChild(mask);
+ renderPlateMemberList(PLATE_MEMBERS);
+}
+function closePlateAssign(){
+ const m=document.getElementById('plateAssignMask');
+ if(!m)return;
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+function renderPlateMemberList(list){
+ const wrap=document.getElementById('plateMemberList');
+ if(!wrap)return;
+ if(!list.length){
+ wrap.innerHTML='未找到匹配的会员
';
+ return;
+ }
+ wrap.innerHTML=list.map(m=>{
+ const globalIdx=PLATE_MEMBERS.indexOf(m);
+ const selected=PLATE_ASSIGN_SELECTED===m;
+ return `
+
${m.name.charAt(0)}
+
+
${m.name} ${m.level}
+
${m.phone} · ${m.dept}
+
+ ${selected?'
✓':''}
+
`;
+ }).join('');
+}
+function filterPlateMembers(kw){
+ kw=(kw||'').trim();
+ if(!kw){renderPlateMemberList(PLATE_MEMBERS);return;}
+ renderPlateMemberList(PLATE_MEMBERS.filter(m=>m.name.includes(kw)||m.phone.includes(kw)));
+}
+function selectPlateMember(idx){
+ PLATE_ASSIGN_SELECTED=PLATE_MEMBERS[idx];
+ renderPlateMemberList(PLATE_MEMBERS);
+}
+function confirmPlateAssign(){
+ if(!PLATE_ASSIGN_SELECTED){
+ showOvToast('请先选择要分配的会员');
+ return;
+ }
+ const s=PLATE_SLOTS[PLATE_ASSIGN_IDX];
+ s.status='bound';
+ s.member=PLATE_ASSIGN_SELECTED;
+ s.bindTime='2026-08-18 16:50';
+ s.lastUse='— · 分配后待首次使用';
+ s.useCount=0;
+ closePlateAssign();
+ closePlateSlot();
+ refreshPlateTab();
+ showOvToast('已将格口 '+s.id+' 分配给会员 '+s.member.name+'(原型演示)');
+}
+
+/* ===== 用户解绑:二次确认 ===== */
+function openPlateUnbind(idx){
+ const s=PLATE_SLOTS[idx];
+ const mask=document.createElement('div');
+ mask.className='ov-confirm-mask';
+ mask.id='plateUnbindMask';
+ mask.innerHTML=`
+
⚠️
+
确认解绑该格口的会员?
+
格口 ${s.id} · ${s.member.name}(${s.member.level}),解绑后该会员餐盘回收,格口恢复为未绑定状态
+
+
+
+
+
`;
+ mask.addEventListener('click',e=>{if(e.target===mask)closePlateUnbind();});
+ document.body.appendChild(mask);
+}
+function closePlateUnbind(){
+ const m=document.getElementById('plateUnbindMask');
+ if(!m)return;
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+function confirmPlateUnbind(idx){
+ const s=PLATE_SLOTS[idx];
+ s.status='free';
+ s.member=null;
+ s.bindTime='—';
+ s.lastUse='—';
+ s.useCount=0;
+ closePlateUnbind();
+ closePlateSlot();
+ refreshPlateTab();
+ showOvToast('格口 '+s.id+' 已解绑,恢复为未绑定状态(原型演示)');
+}
+
+/* ===== 净菜货柜 / 毛菜货柜(参考 sup-warehouse.html 智能货柜功能属性,重新布局) ===== */
+/* 格口状态:occupied 储物中 / idle 空闲 / offline 离线 */
+var STOCK_CABINETS={
+ clean:{name:'3号净菜柜',code:'CAB-NC-001',type:'净菜柜',typeTag:'tag-blue',status:'在线 · 冷藏 2.4℃',
+ slots:[
+ {id:'NC3-01',status:'occupied',itemName:'净土豆丝',qty:10.2,op:'张厨师',inTime:'2026-08-18 09:15',inOrder:'IN-20260818-001',
+ history:[{time:'2026-08-18 14:20',type:'取出',before:11.0,after:10.2,op:'王厨师',order:'IN-20260818-001',remark:'做土豆丝炒肉'},{time:'2026-08-18 11:30',type:'取出',before:11.8,after:11.0,op:'李主管',order:'IN-20260818-001',remark:'做宫保鸡丁'},{time:'2026-08-18 09:15',type:'存入',before:0,after:12.5,op:'张厨师',order:'IN-20260818-001',remark:'首次入柜'}]},
+ {id:'NC3-02',status:'occupied',itemName:'净胡萝卜',qty:15.6,op:'李主管',inTime:'2026-08-18 09:20',inOrder:'IN-20260818-002',
+ history:[{time:'2026-08-18 15:40',type:'取出',before:16.8,after:15.6,op:'王厨师',order:'IN-20260818-002',remark:'做胡萝卜炒肉'},{time:'2026-08-18 09:20',type:'存入',before:0,after:18.0,op:'李主管',order:'IN-20260818-002',remark:'首次入柜'}]},
+ {id:'NC3-03',status:'occupied',itemName:'净香菇',qty:6.8,op:'王厨师',inTime:'2026-08-18 09:30',inOrder:'IN-20260818-018',
+ history:[{time:'2026-08-18 16:10',type:'取出',before:7.5,after:6.8,op:'张厨师',order:'IN-20260818-018',remark:'做香菇青菜'},{time:'2026-08-18 09:30',type:'存入',before:0,after:9.0,op:'王厨师',order:'IN-20260818-018',remark:'首次入柜'}]},
+ {id:'NC3-04',status:'occupied',itemName:'净西兰花',qty:8.5,op:'张厨师',inTime:'2026-08-18 09:40',inOrder:'IN-20260818-022',
+ history:[{time:'2026-08-18 15:30',type:'取出',before:9.0,after:8.5,op:'李主管',order:'IN-20260818-022',remark:'做西兰花炒虾仁'},{time:'2026-08-18 09:40',type:'存入',before:0,after:10.0,op:'张厨师',order:'IN-20260818-022',remark:'首次入柜'}]},
+ {id:'NC3-05',status:'occupied',itemName:'净白菜',qty:18.0,op:'王厨师',inTime:'2026-08-18 08:50',inOrder:'IN-20260818-006',
+ history:[{time:'2026-08-18 11:10',type:'取出',before:18.7,after:18.0,op:'王厨师',order:'IN-20260818-006',remark:'做白菜炖豆腐'},{time:'2026-08-18 08:50',type:'存入',before:0,after:23.0,op:'王厨师',order:'IN-20260818-006',remark:'首次入柜'}]},
+ {id:'NC3-06',status:'idle'},{id:'NC3-07',status:'idle'},{id:'NC3-08',status:'idle'},{id:'NC3-09',status:'idle'},{id:'NC3-10',status:'idle'}
+ ]},
+ raw:{name:'8号毛菜柜',code:'CAB-RW-008',type:'毛菜柜',typeTag:'tag-green',status:'在线 · 常温通风',
+ slots:[
+ {id:'RW8-A1',status:'occupied',itemName:'菠菜',qty:225.0,op:'张仓管',inTime:'2026-08-15 10:30',inOrder:'IN-20260815-001',
+ history:[{time:'2026-08-17 09:00',type:'取出',before:261.0,after:225.0,op:'李仓管',order:'IN-20260815-001',remark:'净菜加工领用'},{time:'2026-08-15 10:30',type:'存入',before:0,after:286.0,op:'张仓管',order:'IN-20260815-001',remark:'毛菜入库'}]},
+ {id:'RW8-A2',status:'occupied',itemName:'油麦菜',qty:60.0,op:'张仓管',inTime:'2026-08-15 11:10',inOrder:'IN-20260815-012',
+ history:[{time:'2026-08-16 15:20',type:'取出',before:80.0,after:60.0,op:'李仓管',order:'IN-20260815-012',remark:'净菜加工领用'},{time:'2026-08-15 11:10',type:'存入',before:0,after:80.0,op:'张仓管',order:'IN-20260815-012',remark:'毛菜入库'}]},
+ {id:'RW8-B1',status:'occupied',itemName:'小白菜',qty:80.0,op:'张仓管',inTime:'2026-08-16 08:20',inOrder:'IN-20260816-008',
+ history:[{time:'2026-08-16 08:20',type:'存入',before:0,after:80.0,op:'张仓管',order:'IN-20260816-008',remark:'毛菜入库'}]},
+ {id:'RW8-B2',status:'occupied',itemName:'生菜',qty:42.5,op:'张仓管',inTime:'2026-08-16 09:00',inOrder:'IN-20260816-009',
+ history:[{time:'2026-08-17 14:30',type:'取出',before:50.0,after:42.5,op:'李仓管',order:'IN-20260816-009',remark:'晚餐备菜领用'},{time:'2026-08-16 09:00',type:'存入',before:0,after:50.0,op:'张仓管',order:'IN-20260816-009',remark:'毛菜入库'}]},
+ {id:'RW8-B3',status:'occupied',itemName:'空心菜',qty:30.0,op:'张仓管',inTime:'2026-08-15 08:30',inOrder:'IN-20260815-015',
+ history:[{time:'2026-08-16 10:10',type:'取出',before:36.0,after:30.0,op:'李仓管',order:'IN-20260815-015',remark:'净菜加工领用'},{time:'2026-08-15 08:30',type:'存入',before:0,after:36.0,op:'张仓管',order:'IN-20260815-015',remark:'毛菜入库'}]},
+ {id:'RW8-C1',status:'occupied',itemName:'芹菜',qty:12.0,op:'张仓管',inTime:'2026-08-14 16:00',inOrder:'IN-20260814-003',
+ history:[{time:'2026-08-17 08:50',type:'取出',before:15.0,after:12.0,op:'王仓管',order:'IN-20260814-003',remark:'凉拌芹菜领用'},{time:'2026-08-14 16:00',type:'存入',before:0,after:15.0,op:'张仓管',order:'IN-20260814-003',remark:'毛菜入库'}]},
+ {id:'RW8-C2',status:'idle'},{id:'RW8-C3',status:'idle'}
+ ]}
+};
+
+function renderStockCabinet(kind){
+ const cab=STOCK_CABINETS[kind];
+ const occupied=cab.slots.filter(s=>s.status==='occupied').length;
+ const slotsHtml=cab.slots.map((s,i)=>{
+ if(s.status==='idle'){
+ return ``;
+ }
+ const cls=s.status==='offline'?'ov-slot offline':'ov-slot occupied';
+ const badge=s.status==='offline'?'':'';
+ const qtyHtml=s.status==='offline'?'离线
':`${s.qty}kg
入库 ${s.inTime.slice(5,10)}
`;
+ return `
+ ${badge}
+
${s.id}
+
${s.itemName}
+ ${qtyHtml}
+
`;
+ }).join('');
+ return `
+
+ ${slotsHtml}
+
+
+
+
+
点击格口可查看存放详情与存取历史
+
`;
+}
+
+/* 净菜/毛菜格口详情弹窗:存放信息 + 存取历史 */
+function showStockSlot(kind,idx){
+ const cab=STOCK_CABINETS[kind];
+ const s=cab.slots[idx];
+ if(s.status==='idle'){
+ showOvToast('该格口为空闲状态,暂无存放信息');
+ return;
+ }
+ const statusTag=s.status==='occupied'?'储物中':'离线';
+ let html=`存放信息
+
所属柜${cab.name}(${cab.code})
+
格口${s.id}
+
格口状态${statusTag}
+
存放物品${s.itemName}${cab.type==='净菜柜'?'净菜':'毛菜'}
+
当前数量(kg)${s.qty.toFixed(1)}
+
操作人${s.op}
+
入库时间${s.inTime}
+
关联入库单${s.inOrder}
+
`;
+ const rows=(s.history||[]).slice().sort((a,b)=>b.time.localeCompare(a.time));
+ if(rows.length){
+ html+=`存取历史
+
| 操作时间 | 操作类型 | 操作前(kg) | 操作后(kg) | 余量变化 | 操作人 | 关联单号 | 备注 |
+ ${rows.map(r=>{
+ const isPos=r.type==='存入';
+ const delta=Math.abs(r.after-r.before).toFixed(1);
+ const typeHtml=isPos?'存入':'取出';
+ return `| ${r.time} | ${typeHtml} | ${r.before.toFixed(1)} | ${r.after.toFixed(1)} | ${isPos?'+':'-'}${delta} ${isPos?'↑':'↓'} | ${r.op} | ${r.order} | ${r.remark||'—'} |
`;
+ }).join('')}
+
`;
+ }
+ const mask=document.createElement('div');
+ mask.className='slot-his-mask';
+ mask.id='stockSlotMask';
+ mask.innerHTML=``;
+ mask.addEventListener('click',e=>{if(e.target===mask)closeStockSlot();});
+ document.body.appendChild(mask);
+}
+function closeStockSlot(){
+ const m=document.getElementById('stockSlotMask');
+ if(!m)return;
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+
+/* ===== 智能电表 / 智能水表 / 智能气表(参考 prod-qc-daily.html 水电气表:余量展示 + 数据录入 + 日使用量统计) ===== */
+var ENERGY_STATE={
+ elec:{title:'智能电表',icon:'⚡',meter:'EM-A01',unit:'kWh',remain:28734.6,todayUse:186,todayCost:167.4,rate:'当前功率',rateVal:'3.2 kW',hint:'当前功率正常,较昨日同时段下降 4.2%',color:'#1677ff'},
+ water:{title:'智能水表',icon:'💧',meter:'WM-A01',unit:'m³',remain:12468.3,todayUse:22,todayCost:79.2,rate:'当前流量',rateVal:'1.8 L/min',hint:'当前流速正常,无异常用水告警',color:'#13c2c2'},
+ gas:{title:'智能气表',icon:'🔥',meter:'GM-A01',unit:'m³',remain:8421.5,todayUse:68,todayCost:425.0,rate:'当前流量',rateVal:'4.6 m³/h',hint:'当前燃气流量正常,无泄漏告警',color:'#fa8c16'}
+};
+/* 日使用量 Mock:每表 12 天数据(时间倒序) */
+var ENERGY_DAILY={
+ elec:[
+ {date:'2026-08-17',use:372,cost:334.8,peak:'11:30',op:'自动采集'},{date:'2026-08-16',use:398,cost:358.2,peak:'11:45',op:'自动采集'},
+ {date:'2026-08-15',use:386,cost:347.4,peak:'11:20',op:'自动采集'},{date:'2026-08-14',use:362,cost:325.8,peak:'12:00',op:'自动采集'},
+ {date:'2026-08-13',use:405,cost:364.5,peak:'11:40',op:'自动采集'},{date:'2026-08-12',use:380,cost:342.0,peak:'11:50',op:'自动采集'},
+ {date:'2026-08-11',use:358,cost:322.2,peak:'11:10',op:'自动采集'},{date:'2026-08-10',use:341,cost:306.9,peak:'10:55',op:'自动采集'},
+ {date:'2026-08-09',use:329,cost:296.1,peak:'11:15',op:'自动采集'},{date:'2026-08-08',use:412,cost:370.8,peak:'12:10',op:'自动采集'},
+ {date:'2026-08-07',use:396,cost:356.4,peak:'11:35',op:'自动采集'},{date:'2026-08-06',use:375,cost:337.5,peak:'11:25',op:'自动采集'}
+ ],
+ water:[
+ {date:'2026-08-17',use:40,cost:144.0,peak:'12:10',op:'自动采集'},{date:'2026-08-16',use:45,cost:162.0,peak:'12:20',op:'自动采集'},
+ {date:'2026-08-15',use:42,cost:151.2,peak:'11:50',op:'自动采集'},{date:'2026-08-14',use:38,cost:136.8,peak:'12:00',op:'自动采集'},
+ {date:'2026-08-13',use:46,cost:165.6,peak:'12:15',op:'自动采集'},{date:'2026-08-12',use:41,cost:147.6,peak:'11:45',op:'自动采集'},
+ {date:'2026-08-11',use:36,cost:129.6,peak:'11:40',op:'自动采集'},{date:'2026-08-10',use:33,cost:118.8,peak:'11:30',op:'自动采集'},
+ {date:'2026-08-09',use:31,cost:111.6,peak:'11:35',op:'自动采集'},{date:'2026-08-08',use:48,cost:172.8,peak:'12:30',op:'自动采集'},
+ {date:'2026-08-07',use:44,cost:158.4,peak:'12:05',op:'自动采集'},{date:'2026-08-06',use:39,cost:140.4,peak:'11:55',op:'自动采集'}
+ ],
+ gas:[
+ {date:'2026-08-17',use:120,cost:750.0,peak:'11:20',op:'自动采集'},{date:'2026-08-16',use:130,cost:812.5,peak:'11:30',op:'自动采集'},
+ {date:'2026-08-15',use:126,cost:787.5,peak:'11:10',op:'自动采集'},{date:'2026-08-14',use:115,cost:718.8,peak:'11:40',op:'自动采集'},
+ {date:'2026-08-13',use:132,cost:825.0,peak:'11:25',op:'自动采集'},{date:'2026-08-12',use:122,cost:762.5,peak:'11:15',op:'自动采集'},
+ {date:'2026-08-11',use:110,cost:687.5,peak:'11:05',op:'自动采集'},{date:'2026-08-10',use:104,cost:650.0,peak:'10:50',op:'自动采集'},
+ {date:'2026-08-09',use:98,cost:612.5,peak:'10:45',op:'自动采集'},{date:'2026-08-08',use:138,cost:862.5,peak:'11:55',op:'自动采集'},
+ {date:'2026-08-07',use:128,cost:800.0,peak:'11:35',op:'自动采集'},{date:'2026-08-06',use:117,cost:731.3,peak:'11:20',op:'自动采集'}
+ ]
+};
+var ENERGY_PAGE_SIZE=5;
+var ENERGY_CURRENT_PAGE={elec:1,water:1,gas:1};
+
+function renderEnergyTab(type){
+ const e=ENERGY_STATE[type];
+ const unit=e.unit;
+ return `
+
+
✏️ 录入
+
+
${e.remain} ${unit}
+
+
今日用量${e.todayUse} ${unit}
+
今日费用¥${e.todayCost.toFixed(1)}
+
状态正常
+
${e.rate}${e.rateVal}
+
+
${e.hint}
+
+
当前余量(${unit})
+
今日用量(${unit})
+
今日费用(元)
+
+
+
+
+
+
+ 日使用量统计
+ `;
+}
+function renderEnergyDaily(type){
+ const wrap=document.getElementById('energyDaily_'+type);
+ if(!wrap)return;
+ const list=ENERGY_DAILY[type];
+ const e=ENERGY_STATE[type];
+ const total=list.length;
+ const totalPages=Math.max(1,Math.ceil(total/ENERGY_PAGE_SIZE));
+ let page=ENERGY_CURRENT_PAGE[type];
+ if(page>totalPages)page=totalPages;
+ if(page<1)page=1;
+ ENERGY_CURRENT_PAGE[type]=page;
+ const start=(page-1)*ENERGY_PAGE_SIZE;
+ const pageRows=list.slice(start,start+ENERGY_PAGE_SIZE);
+ const unit=e.unit;
+ let html='| 记录日期 | 用量('+unit+') | 费用(元) | 峰值时段 | 采集方式 | 操作 |
';
+ pageRows.forEach(r=>{
+ html+=`| ${r.date} | ${r.use} | ¥${r.cost.toFixed(1)} | ${r.peak} | ${r.op} | 详情 |
`;
+ });
+ html+='
';
+ html+=``;
+ wrap.innerHTML=html;
+}
+function goEnergyPage(type,p){
+ ENERGY_CURRENT_PAGE[type]=p;
+ renderEnergyDaily(type);
+}
+function toggleEnergyEditOv(type){
+ const box=document.getElementById('energyEdit_'+type);
+ if(box)box.style.display=box.style.display==='none'?'grid':'none';
+}
+function cancelEnergyEditOv(type){
+ const e=ENERGY_STATE[type];
+ const box=document.getElementById('energyEdit_'+type);
+ if(box){
+ box.style.display='none';
+ document.getElementById('energyInputRemain_'+type).value=e.remain;
+ document.getElementById('energyInputUse_'+type).value=e.todayUse;
+ document.getElementById('energyInputCost_'+type).value=e.todayCost;
+ }
+}
+function saveEnergyEditOv(type){
+ const e=ENERGY_STATE[type];
+ const remain=parseFloat(document.getElementById('energyInputRemain_'+type).value);
+ const use=parseFloat(document.getElementById('energyInputUse_'+type).value);
+ const cost=parseFloat(document.getElementById('energyInputCost_'+type).value);
+ if(isNaN(remain)||isNaN(use)||isNaN(cost)){showOvToast('请填写完整的录入数据');return;}
+ e.remain=remain;e.todayUse=use;e.todayCost=cost;
+ document.getElementById('energyValue_'+type).innerHTML=`${remain} ${e.unit}`;
+ document.getElementById('energyUse_'+type).textContent=`${use} ${e.unit}`;
+ document.getElementById('energyCost_'+type).textContent=`¥${cost.toFixed(1)}`;
+ document.getElementById('energyEdit_'+type).style.display='none';
+ showOvToast(e.title+'数据已保存(原型演示)');
+}
+
+/* ===== 格口历史记录弹窗(叠加在设备概览之上,参考净菜进柜历史记录弹窗) ===== */
+var OV_SLOT_HISTORY={
+ 0:{cabinet:'B区智能柜-1号',slot:'B1-01',rows:[
+ {time:'2026-08-18 14:20',name:'宫保鸡丁餐品包',type:'取出',before:2.8,after:2.4,op:'取餐人 张*明',order:'QC-2026-0818-021',remark:'扫码取餐'},
+ {time:'2026-08-18 12:05',name:'宫保鸡丁餐品包',type:'取出',before:3.3,after:2.8,op:'取餐人 李*华',order:'QC-2026-0818-015',remark:'扫码取餐'},
+ {time:'2026-08-18 09:15',name:'宫保鸡丁餐品包',type:'存入',before:0.0,after:3.3,op:'李主管',order:'JC-2026-0818-001',remark:'烹制完入柜'}
+ ]},
+ 1:{cabinet:'B区智能柜-1号',slot:'B1-02',rows:[
+ {time:'2026-08-18 15:40',name:'红烧肉餐品包',type:'取出',before:3.6,after:3.1,op:'取餐人 王*强',order:'QC-2026-0818-030',remark:'扫码取餐'},
+ {time:'2026-08-18 11:00',name:'红烧肉餐品包',type:'取出',before:4.2,after:3.6,op:'取餐人 赵*敏',order:'QC-2026-0818-009',remark:'扫码取餐'},
+ {time:'2026-08-18 09:20',name:'红烧肉餐品包',type:'存入',before:0.0,after:4.2,op:'李主管',order:'JC-2026-0818-002',remark:'烹制完入柜'}
+ ]},
+ 2:{cabinet:'B区智能柜-1号',slot:'B1-03',rows:[
+ {time:'2026-08-18 16:10',name:'清蒸鱼餐品包',type:'取出',before:2.2,after:1.8,op:'取餐人 陈*芳',order:'QC-2026-0818-035',remark:'扫码取餐'},
+ {time:'2026-08-18 13:20',name:'清蒸鱼餐品包',type:'取出',before:2.6,after:2.2,op:'取餐人 刘*洋',order:'QC-2026-0818-024',remark:'扫码取餐'},
+ {time:'2026-08-18 10:30',name:'清蒸鱼餐品包',type:'存入',before:0.0,after:2.6,op:'王厨师',order:'JC-2026-0818-003',remark:'烹制完入柜'}
+ ]},
+ 3:{cabinet:'B区智能柜-1号',slot:'B1-04',rows:[
+ {time:'2026-08-18 15:30',name:'净西兰花',type:'取用',before:5.0,after:4.5,op:'张厨师',order:'JC-2026-0818-012',remark:'做西兰花炒虾仁'},
+ {time:'2026-08-18 13:10',name:'净西兰花',type:'取用',before:5.5,after:5.0,op:'张厨师',order:'JC-2026-0818-012',remark:'做西兰花炒虾仁'},
+ {time:'2026-08-18 09:40',name:'净西兰花',type:'存入',before:0.0,after:5.5,op:'张厨师',order:'JC-2026-0818-004',remark:'首次入柜'}
+ ]},
+ 4:{cabinet:'B区智能柜-1号',slot:'B1-05',rows:[
+ {time:'2026-08-18 15:40',name:'净胡萝卜',type:'取用',before:5.7,after:5.2,op:'王厨师',order:'JC-2026-0818-016',remark:'做胡萝卜炒肉'},
+ {time:'2026-08-18 11:00',name:'净胡萝卜',type:'取用',before:6.2,after:5.7,op:'王厨师',order:'JC-2026-0818-016',remark:'做胡萝卜炒肉'},
+ {time:'2026-08-18 09:20',name:'净胡萝卜',type:'存入',before:0.0,after:6.2,op:'李主管',order:'JC-2026-0818-005',remark:'首次入柜'}
+ ]},
+ 5:{cabinet:'B区智能柜-1号',slot:'B1-06',rows:[
+ {time:'2026-08-12 11:10',name:'净白菜',type:'取用',before:6.5,after:6.0,op:'王厨师',order:'JC-2026-0812-008',remark:'做白菜炖豆腐'},
+ {time:'2026-08-12 10:00',name:'净白菜',type:'取用',before:7.2,after:6.5,op:'王厨师',order:'JC-2026-0812-008',remark:'做醋溜白菜'},
+ {time:'2026-08-11 17:30',name:'净白菜',type:'取用',before:7.8,after:7.2,op:'李主管',order:'JC-2026-0811-005',remark:'晚餐备菜'},
+ {time:'2026-08-11 11:50',name:'净白菜',type:'取用',before:8.4,after:7.8,op:'张厨师',order:'JC-2026-0811-005',remark:'午餐备菜'},
+ {time:'2026-08-10 14:30',name:'净白菜',type:'存入',before:0.0,after:8.4,op:'王厨师',order:'JC-2026-0810-003',remark:'首次入柜'}
+ ]},
+ 6:{cabinet:'B区智能柜-1号',slot:'B1-07',rows:[
+ {time:'2026-08-18 16:30',name:'净香菇',type:'取用',before:2.6,after:2.2,op:'张厨师',order:'JC-2026-0818-018',remark:'做香菇青菜'},
+ {time:'2026-08-18 14:00',name:'净香菇',type:'取用',before:3.0,after:2.6,op:'张厨师',order:'JC-2026-0818-018',remark:'做香菇青菜'},
+ {time:'2026-08-18 09:30',name:'净香菇',type:'存入',before:0.0,after:3.0,op:'王厨师',order:'JC-2026-0818-006',remark:'首次入柜'}
+ ]}
+};
+var OV_HIS_PAGE_SIZE=5;
+var OV_HIS_CURRENT={idx:0,page:1};
+
+function showSlotHistory(idx){
+ const d=OV_SLOT_HISTORY[idx];
+ if(!d){
+ showOvToast('该格口暂无历史记录(空闲格口)');
+ return;
+ }
+ OV_HIS_CURRENT={idx:idx,page:1};
+ const cinCount=d.rows.filter(r=>r.type==='存入').length;
+ const coutCount=d.rows.filter(r=>r.type!=='存入').length;
+ const summary=`
+
所属柜:${d.cabinet}
+
格口:${d.slot}
+
累计存入:${cinCount} 次
+
累计取出/取用:${coutCount} 次
+
`;
+ const mask=document.createElement('div');
+ mask.className='slot-his-mask';
+ mask.id='slotHisMask';
+ mask.innerHTML=``;
+ mask.addEventListener('click',e=>{if(e.target===mask)closeSlotHistory();});
+ document.body.appendChild(mask);
+ renderOvHistoryPage();
+}
+function closeSlotHistory(){
+ const m=document.getElementById('slotHisMask');
+ if(!m)return;
+ m.classList.add('closing');
+ setTimeout(()=>m.remove(),200);
+}
+function renderOvHistoryPage(){
+ const wrap=document.getElementById('ovHisTableWrap');
+ const footer=document.getElementById('ovHisFooterWrap');
+ if(!wrap)return;
+ const d=OV_SLOT_HISTORY[OV_HIS_CURRENT.idx];
+ /* 时间倒序展示(最新在前) */
+ const sortedRows=d.rows.slice().sort((a,b)=>b.time.localeCompare(a.time));
+ const total=sortedRows.length;
+ const totalPages=Math.max(1,Math.ceil(total/OV_HIS_PAGE_SIZE));
+ if(OV_HIS_CURRENT.page>totalPages)OV_HIS_CURRENT.page=totalPages;
+ if(OV_HIS_CURRENT.page<1)OV_HIS_CURRENT.page=1;
+ const start=(OV_HIS_CURRENT.page-1)*OV_HIS_PAGE_SIZE;
+ const pageRows=sortedRows.slice(start,start+OV_HIS_PAGE_SIZE);
+ let html='| 操作时间 | 存放物品 | 操作类型 | 操作前(kg) | 操作后(kg) | 余量变化 | 操作人 | 关联单号 | 备注 |
';
+ pageRows.forEach(r=>{
+ const isPos=r.type==='存入';
+ const delta=Math.abs(r.after-r.before).toFixed(1);
+ const deltaHtml=`${isPos?'+':'-'}${delta} ${isPos?'↑':'↓'}`;
+ const typeHtml=isPos?'存入':(r.type==='取出'?'取出':'取用');
+ const orderHtml=r.order==='—'?'—':`${r.order}`;
+ html+=`| ${r.time} | ${r.name} | ${typeHtml} | ${r.before.toFixed(1)} | ${r.after.toFixed(1)} | ${deltaHtml} | ${r.op} | ${orderHtml} | ${r.remark||'—'} |
`;
+ });
+ html+='
';
+ wrap.innerHTML=html;
+ if(footer){
+ let pg=``;
+ footer.innerHTML=pg;
+ }
+}
+function goOvHistoryPage(p){OV_HIS_CURRENT.page=p;renderOvHistoryPage();}
+
+/* 轻量 toast */
+function showOvToast(msg){
+ const old=document.getElementById('ovToast');
+ if(old)old.remove();
+ const t=document.createElement('div');
+ t.id='ovToast';
+ t.textContent=msg;
+ t.style.cssText='position:fixed;top:80px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.78);color:#fff;padding:10px 20px;border-radius:6px;font-size:14px;z-index:3000;box-shadow:0 4px 12px rgba(0,0,0,.2);';
+ document.body.appendChild(t);
+ setTimeout(()=>{if(t)t.remove();},2200);
+}