From 40e0b311c4bf703fe0214218f863133d29fb498f Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 27 Aug 2026 08:48:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E9=A4=90=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=A6=82=E8=A7=88=E6=96=B0=E5=A2=9E=E9=A4=90?= =?UTF-8?q?=E7=BA=BF=E8=90=A5=E5=85=BB=E7=A7=A4Tab=20+=20=E5=87=80?= =?UTF-8?q?=E8=8F=9C=E8=AE=A2=E5=8D=95=E6=96=B0=E5=A2=9E=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=8E=86=E5=8F=B2/=E7=94=9F=E6=88=90=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E5=88=97/=E5=B0=B1=E9=A4=90=E4=BA=BA=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - din-terminal.html: 设备概览新增「餐线营养秤」Tab(四维度子页切换),表头列筛选 + 人脸识别视频回放弹窗 + 运行日志报错详情 - prod-clean-order.html: 新增「净菜订单生成历史」按钮与弹窗、主列表「生成方式」列、详情抽屉自动订单「就餐人数」列 - 新增文档: 异常场景素材-秤体视角 / 加工厂硬件流程说明 / 角色场景流程图 / 营养秤多维状态矩阵 --- .../doc/异常场景素材-秤体视角.md | 179 +++++++++ .../canteen-device/doc/加工厂硬件流程说明.md | 172 ++++++++ web-admin/nutrition/din-terminal.html | 379 +++++++++++++++++- web-admin/nutrition/doc/CHANGELOG.md | 15 + web-admin/nutrition/doc/角色场景流程图.md | 159 ++++++++ web-admin/nutrition/prod-clean-order.html | 179 ++++++++- 营养秤多维状态矩阵.md | 239 +++++++++++ 7 files changed, 1314 insertions(+), 8 deletions(-) create mode 100644 other-module/canteen-device/canteen-nutrition-scale/doc/异常场景素材-秤体视角.md create mode 100644 other-module/canteen-device/doc/加工厂硬件流程说明.md create mode 100644 web-admin/nutrition/doc/角色场景流程图.md create mode 100644 营养秤多维状态矩阵.md diff --git a/other-module/canteen-device/canteen-nutrition-scale/doc/异常场景素材-秤体视角.md b/other-module/canteen-device/canteen-nutrition-scale/doc/异常场景素材-秤体视角.md new file mode 100644 index 0000000..64a89b8 --- /dev/null +++ b/other-module/canteen-device/canteen-nutrition-scale/doc/异常场景素材-秤体视角.md @@ -0,0 +1,179 @@ +# 营养秤异常场景素材(秤体视角) + +> 为模型师提供素材,以秤体为主体视角,每条场景按「机器描述 → 场景分析」组织。 + +--- + +## 1. 夹子是否异常 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 1.1 | 初始在夹盘,取餐后仍在夹盘 | ✅ 正常 | +| 1.2 | 初始在食物盆,取餐后仍在食物盆 | ✅ 正常 | +| 1.3 | 秤检测到:夹子初始在夹盘,取餐后检测到夹子在食物盆 | 用户取餐后把夹子扔进盆里,未归位 | +| 1.4 | 秤检测到:夹子初始在食物盆,取餐后检测到夹子在夹盘 | 用户把夹子从盆里拿出来放回夹盘 | +| 1.5 | 秤检测到:夹子初始在夹盘,取餐后夹子信号消失 | 用户拿着夹子走了 | +| 1.6 | 秤检测到:夹子初始在食物盆,取餐后夹子信号消失 | 用户从盆里抓起夹子带走了 | +| 1.7 | 秤检测到:本轮取餐开始前,夹子就不在夹盘上 | 上一轮遗留的异常状态,初始态已错 | + +--- + +## 2. 是否本人取餐 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 2.1 | 人脸 ID=A,取餐动作归属 A | ✅ 正常 | +| 2.2 | 人脸 ID=A,取餐动作归属 B | B 的手伸进画面取了菜。身份冒用 | +| 2.3 | 人脸未识别到,但发生了重量减少 | 识别故障,取餐无主 | +| 2.4 | 人脸 ID=A,A 正在取餐,B 的手也伸入 | 多人混取 | +| 2.5 | 两人同时站在识别区,两笔重量同时发生 | 两人同时取餐 | +| 2.6 | 两人同时站在识别区,重量分先后两次 | 两人轮流取餐 | +| 2.7 | 人脸 ID=A 持续识别中突断(扭头),数秒后恢复,期间发生了重量变化 | 人脸丢失又恢复,中间段归属存疑 | +| 2.8 | 人脸 ID=A 持续识别中突断(摄像头被遮挡),无恢复 | 非主动丢失,可能是物品遮挡镜头 | + +--- + +## 3. 是否正常取餐 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 3.1 | 重量陡降 N 克,随后稳定,期间人脸持续识别 | ✅ 正常取餐 | +| 3.2 | 重量陡降 N 克,数秒后重量恢复 N 克,人脸持续识别 | 夹起来看了看又放回去了(反悔) | +| 3.3 | 重量连续多次小幅波动(±几克),最终稳定在一个降幅 | 夹子试探/抖动,多次夹取后最终取走 | +| 3.4 | 重量无变化,但夹子从夹盘拿起又放回 | 空夹——夹了但什么都没夹到 | +| 3.5 | 重量陡降 N 克,过程中有短暂微幅回升,最终降幅 < N | 夹起时汤汁滴落或食物部分滑落回盆,实际取走量小于夹取量 | +| 3.6 | 重量陡降 N 克,且邻盆也检测到微小重量变化 | 食物掉落台面或汤汁溅到邻盆 | +| 3.7 | 重量出现持续不规则波动,幅度较小,期间检测到夹子未动作 | 用户身体倚靠或手碰到台面 | +| 3.8 | 邻盆无夹子动作但发生微小重量变化 | 用户取餐时碰到了旁边的盆 | + +--- + +## 4. 重量变化是否合理 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 4.1 | 重量减少,幅度在合理范围内 | ✅ 正常 | +| 4.2 | 重量增加(排除补菜后) | 方向异常——可能是回退、夹子掉入盆中、或人为按压 | +| 4.3 | 重量单次减少远超单人单次合理取餐量 | 超量——可能是夹子掉入盆中被当作取餐、或传感器异常 | +| 4.4 | 重量在无操作期间持续缓慢变化(非阶跃) | 传感器漂移,或热食蒸汽导致读数偏移 | +| 4.5 | 重量瞬时跳变(毫秒级阶跃),无夹子动作伴随 | 秤体被碰撞或台面振动 | +| 4.6 | 当前盆内净重接近于零(仅剩碎屑),发生了取餐动作 | 空盆取餐——边界值易误判,可能取到的是盆重波动 | + +--- + +## 5. 是否发生回退 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 5.1 | 取餐完成后,重量出现增加,人脸 ID 与取餐时一致 | 用户从自己盘里夹回一部分到盆中 | +| 5.2 | 取餐完成后,重量增加至接近初始值,人脸 ID 与取餐时一致 | 用户全部倒回,完全反悔。全程净变化≈0 | +| 5.3 | 取餐完成后,重量增加过程中人脸信号中断(扭头)又恢复 | 回退过程中转头,中间段归属存疑 | +| 5.4 | 取餐时人脸 ID=A,回退时人脸 ID=B | A 取走的菜,B 往回放。跨用户反向操作 | +| 5.5 | 无人识别状态,重量先增后恢复(手按/抬秤面),随后人脸识别进入,正常取餐 | 恶意操作——取餐前人为改变初始重量基准,骗取重量差 | +| 5.6 | 取餐完成后,重量出现增加,但回退餐品与当前盆内菜品不一致(用户从 A 盆取了菜,却往回放到 B 盆) | 跨菜品回退——回退食物不属于当前盆,导致 B 盆重量增加但菜品成分不匹配 | + +--- + +## 6. 秤体能否检测到变化 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 6.1 | 重量持续以 1~2g/次的幅度递减,单次变化 < 传感器分辨率,累计已超阈值但全程无触发 | 逐克静默取餐——用户少量多次夹取,秤"感觉不到"但食物已大量减少 | +| 6.2 | 重量极缓慢持续下降,斜率低于触发阈值,累计变化已达显著值 | 漂移式取餐——用户缓慢倾倒食物,规避检测 | +| 6.3 | 重量先降 A 克又增 B 克,|A-B| < 阈值,净变化近似为零 | 取多补少——取走一部分又补回一部分,净值抵消 | +| 6.4 | 多次微量取餐,每次单独 < 阈值,累计远超阈值 | 分散取餐——需要累积判定才能发现 | +| 6.5 | 开餐/补餐后重量仍在波动中(食物刚倒入、盆未稳定),此时发生取餐 | 补餐后不稳定期取餐——基准未锁定,变化量计算不准 | + +--- + +## 7. 是否为服务员操作 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 7.1 | 重量陡增(幅度远超单人回退),无人脸识别 | 服务员补菜 | +| 7.2 | 重量发生整体替换式变化(先清零后陡增),无人脸识别 | 服务员换整盆菜 | +| 7.3 | 重量大幅减少(负向变化 ≈ 盆自重),读数变为负值(低于去皮基准),无人脸识别 | 服务员收走空盆 | +| 7.4 | 重量陡增期间,恰有用户在识别区取餐 | 服务员补菜与用户取餐并发——重量增和减同时发生 | +| 7.5 | 重量出现频繁小幅不规则波动,无人脸识别,无夹子动作 | 保洁擦拭台面 | + +--- + +## 8. 秤体自身是否正常 + +| # | 机器描述 | 场景分析 | +|---|---------|---------| +| 8.1 | 重量短时间内出现巨大跳变后恢复,无夹子动作 | 秤体被碰撞或搬动 | +| 8.2 | 热食蒸汽持续熏蒸秤面,重量读数持续单向漂移 | 温度干扰导致传感器漂移 | +| 8.3 | 运行中突然断电→重启,期间发生的取餐数据全部丢失 | 断电丢数——重启后状态不连续 | +| 8.4 | 网络中断,秤端本地缓存数据未上报 | 断网积压——恢复后需补传,时序可能错乱 | +| 8.5 | 本秤检测到微小振动,但夹子和重量均无变化,邻秤恰好有取餐事件 | 邻秤串扰——隔壁取餐的振动传到了本秤 | +| 8.6 | 台面上有上一轮遗留的食物残渣/汤汁,导致初始重量偏高 | 台面未清洁——初始重量含异物 | +| 8.7 | 台面上有残留物品,当前用户取餐后残留物被一并带走 | 遗留物被清走——减重包含异物重量 | + +--- + +## 9. 多人交叉场景 + +### 9.1 交叉矩阵总览 + +``` +A 的状态 × B 的动作 = 场景 + + B仅入镜 B取餐 B回退 B按压秤面 +A取餐中 M1 M2 M3 M4 +A完成未离开 M5 M6 M7 M8 +A识别未取餐 — M9 M10 — +``` + +### 9.2 A 取餐中 + B 入镜 + +| # | A 状态 | B 动作 | 机器描述 | 场景分析 | +|---|--------|--------|---------|---------| +| M1 | A 取餐中 | B 入镜不动 | A 人脸持续,画面中出现 B 人脸;重量仅 A 侧在变化 | 两人同时被识别,但只有 A 在取。订单归属以 A 为准 | +| M2 | A 取餐中 | B 取餐 | A/B 人脸交替或同框;两笔重量下降交织发生 | 两人同时取餐,需帧级对齐人脸与重量变化,拆分成两笔 | +| M3 | A 取餐中 | B 回退 | A 人脸持续,B 人脸同时出现;重量增加与 B 入镜时序吻合 | A 在减重、B 在增重,净值需解耦 | +| M4 | A 取餐中 | B 按压秤面 | A/B 人脸同框;重量突然阶跃(增/减),与任何夹子动作无关 | A 正在取餐时 B 恶意改变基准,A 的取餐量被污染 | + +### 9.3 A 完成未离开 + B 入镜 + +| # | A 状态 | B 动作 | 机器描述 | 场景分析 | +|---|--------|--------|---------|---------| +| M5 | A 完成未离开 | B 入镜不动 | A/B 双人脸同框,重量已稳定 | A 占位,B 等待。系统需判定当前订单归属仍为 A | +| M6 | A 完成未离开 | B 取餐 | A 人脸存在,B 人脸出现;新重量下降与 B 人脸时序匹配 | B 接力取餐,需将新减重从 A 名下切换到 B | +| M7 | A 完成未离开 | B 回退 | A 人脸存在,B 人脸出现;重量增加与 B 入镜时序吻合 | A 已取完,B 往回放。回退归属应为 B | +| M8 | A 完成未离开 | B 按压秤面 | A/B 双人脸同框;重量异常阶跃无夹子动作 | A 的结算基准被篡改 | + +### 9.4 A 识别未取餐 + B 入镜 + +| # | A 状态 | B 动作 | 机器描述 | 场景分析 | +|---|--------|--------|---------|---------| +| M9 | A 识别未取餐 | B 取餐 | A 人脸存在,B 人脸出现;重量下降与 B 入镜同步,A 无动作 | A 站着没动,B 先取了。A 的订单怎么处理?空单关闭还是保留? | +| M10 | A 识别未取餐 | B 回退 | A 人脸存在,B 人脸出现;重量增加与 B 入镜同步 | A 还没取餐,重量先涨了。回退归属 B,A 订单状态存疑 | + +### 9.5 A 离开后 B 继承异常状态 + +| # | A 遗留 | B 动作 | 机器描述 | 场景分析 | +|---|--------|--------|---------|---------| +| M11 | A 取餐后夹子留在盆里 | B 入镜取餐 | 夹子初始在食物盆(A 遗留);B 人脸识别正常,取餐动作正常 | B 继承异常初始态,夹子位置判定基准被 A 污染 | +| M12 | A 把夹子带走 | B 入镜取餐 | 夹盘无夹子;B 已识别,用手或其他方式取餐 | 工具缺失,B 取餐方式改变 | +| M13 | A 遗留汤汁/残渣在台面 | B 入镜取餐 | 初始重量含遗留物;B 取餐过程中可能碰掉残渣 | B 的取餐计量含遗留物干扰 | +| M14 | A 按压秤面后离开 | B 入镜取餐 | 秤未归零,初始基准被 A 篡改;B 正常取餐 | 重量绝对值不准,差值也可能偏移 | +| M15 | A 完成→离开→B 入镜取餐 | B 未识别,重量下降 | 摄像头未能识别 B,但发生了取餐 | 设备识别故障,取餐无主 | +| M16 | A 完成→离开→B 入镜回退 | B 未识别,重量增加 | 摄像头未能识别 B,但发生了回退 | 设备识别故障,回退无主 | + +--- + +## 维度统计 + +| 维度 | 条目数 | +|------|:-----:| +| 1. 夹子是否异常 | 7 | +| 2. 是否本人取餐 | 8 | +| 3. 是否正常取餐 | 8 | +| 4. 重量变化是否合理 | 6 | +| 5. 是否发生回退 | 6 | +| 6. 秤体能否检测到变化 | 5 | +| 7. 是否为服务员操作 | 5 | +| 8. 秤体自身是否正常 | 7 | +| 9. 多人交叉场景 | 16 | +| **合计** | **68** | diff --git a/other-module/canteen-device/doc/加工厂硬件流程说明.md b/other-module/canteen-device/doc/加工厂硬件流程说明.md new file mode 100644 index 0000000..c100d00 --- /dev/null +++ b/other-module/canteen-device/doc/加工厂硬件流程说明.md @@ -0,0 +1,172 @@ +# 加工厂硬件流程说明 + +> 适用范围:`other-module/canteen-device/` 下「卫生供应」分组所覆盖的加工厂硬件终端(「健康生产」「营养用餐」分组属餐厅/食堂现场,不在加工厂范畴) +> 创建时间:2026-08-13 +> 说明:本文档描述的硬件设备,以已设计(有实际原型页面)的设备为准;仅占位、未设计的设备视为"暂不存在"。 + +--- + +## 目录 + +- **一、业务描述** + - 1.1 加工厂介绍 + - 1.2 硬件设备清单 + - 1.3 设备之间的关系 + - 1.4 完整业务流程 +- **二、硬件使用文档** + - 2.1 毛菜验收秤 + +--- + +## 一、业务描述 + +### 1.1 加工厂介绍 + +加工厂承担「卫生供应」业务,将毛菜加工成可直接净配、包装、发货的净菜包。本期共 8 类硬件设备,其中 7 台已设计、1 台(毛菜卫检设备)待硬件选型。 + +### 1.2 硬件设备清单 + +#### 已设计设备(共 7 台) + +| 序号 | 设备 | 目录 | 核心职能 | 设计位置 | +|---|---|---|---|---| +| 1 | 毛菜验收秤 | `raw-veg-inspection-scale` | 毛菜到货验收、称重、AI 食材识别、质检判定、食材采样 | 设备端 8 页 + PRD / FLOW | +| 2 | 毛菜库存柜 | `raw-veg-inventory-cabinet` | 毛菜储位分配、出入库管理 | 后台 `sup-warehouse` 仓储管理 / `sup-in-raw` 毛菜入库 | +| 3 | 加工毛菜秤 | `raw-veg-processing-scale` | 毛菜加工任务、批次称重、清洗操作 | 设备端 7 页 + FLOW | +| 4 | 厨配秤 | `clean-veg-ratio-scale` | 净菜上秤识别、加工确认(含尺寸/形状/刀工规格) | 设备端 6 页 | +| 5 | 净菜库存柜 | `clean-veg-inventory-cabinet` | 净菜库存管理与盘点 | 后台 `sup-warehouse` 仓储管理 / `sup-in-cleanin` 净菜入库 | +| 6 | 净配秤 | `ratio-scale` | 净菜包 / 餐品净菜包净配(主辅材 + 调料双区称重) | 设备端 5 页 + PRD | +| 7 | 净菜包装贴标秤 | `clean-veg-packaging-labeler` | 核对实物、包装塑封、打印标签、发货 | 设备端 4 页 | + +#### 待硬件选型设备(共 1 台) + +| 设备 | 目录 | 规划职能 | 现状 | +|---|---|---|---| +| 毛菜卫检设备 | `raw-veg-hygiene-inspection` | 毛菜卫生检测与质量判定 | 占位页,待硬件选型(后台 `sup-hygiene-check` 卫生检测已设计) | + +> 另有历史遗留目录 `raw-veg-washing-scale`(毛菜清洗秤),已并入「加工毛菜秤」,未出现在导航中,不计入设备清单。 + +### 1.3 设备之间的关系 + +7 台已设计设备构成一条**毛菜 → 净菜包**的加工流水线,上游设备的产出是下游设备的输入: + +```mermaid +flowchart LR + A[毛菜验收秤
到货验收·称重·AI识别] --> B[毛菜库存柜
储位分配·出入库] + B --> C[加工毛菜秤
毛菜清洗] + C --> D[厨配秤
净菜切配·加工确认] + D --> E[净菜库存柜
净菜库存·盘点] + E --> F[净配秤
净菜包/餐品组合打包] + F --> G[净菜包装贴标秤
包装·打印·发货] +``` + +| 设备 | 上游(输入) | 下游(输出) | +|---|---|---| +| 毛菜验收秤 | 后台「绿色种采-采供管理」填好配送信息的采购订单 | 毛菜入库记录(后台建档)→ 毛菜库存柜 | +| 毛菜库存柜 | 毛菜验收秤入库的毛菜 | 按储位取出的毛菜 → 加工毛菜秤 | +| 加工毛菜秤 | 毛菜库存柜取出的毛菜 + 后台推送的毛菜加工任务 | 清洗完成的毛菜 → 厨配秤 | +| 厨配秤 | 加工毛菜秤清洗完成的毛菜 + 切配任务 | 切配好的净菜 → 净菜库存柜 | +| 净菜库存柜 | 厨配秤切配好的净菜 | 库存净菜 → 净配秤 | +| 净配秤 | 净菜库存柜取出的净菜 + 净配任务 | 净配完成的净菜包 → 净菜包装贴标秤 | +| 净菜包装贴标秤 | 净配秤净配完成的订单(净菜包 / 餐品净菜包) | 已包装、已打印标签、可发货的净菜包 | + +> 本期所有硬件设备以**场所(食堂/加工厂)为维度**组织数据,设备是场所的附属资源,不作为独立维度。任务单、验收记录、设备配置等均归属到场所。 + +### 1.4 完整业务流程 + +加工厂业务是一条「**采购 → 验收 → 入库 → 清洗 → 切配 → 入库 → 净配 → 包装 → 发货**」的完整闭环,全程由后台系统(Web 管理端「卫生供应 / 绿色种采」)驱动任务下发,硬件终端负责现场执行并回传数据。 + +```mermaid +flowchart TD + subgraph 后台系统 + P[采供管理
采购下单·填配送信息] + T[任务整合
定时推送加工任务] + end + + subgraph 加工厂现场 + S1[毛菜验收秤
到货验收·采样] + S2[毛菜库存柜
储位分配·出入库] + S3[加工毛菜秤
毛菜清洗] + S4[厨配秤
净菜切配] + S5[净菜库存柜
净菜库存·盘点] + S6[净配秤
净菜包净配] + S7[净菜包装贴标秤
包装·发货] + end + + P --> S1 + S1 --> S2 + S2 --> S3 + T -.毛菜加工任务.-> S3 + T -.切配任务.-> S4 + S3 --> S4 + S4 --> S5 + S5 --> S6 + T -.净配任务.-> S6 + S6 --> S7 + S7 --> OUT[发货/外售·内供] + + S1 --> DB[(毛菜入库
后台建档)] + S3 --> DB2[(清洗记录)] + S4 --> DB3[(加工记录)] + S6 --> DB4[(净配记录)] + S7 --> DB5[(包装·发货记录)] +``` + +| 阶段 | 执行设备 | 关键动作 | 后台联动 | +|---|---|---|---| +| ① 采购 | 后台采供管理 | 采购下单、填配送信息 | 生成采购订单 PO | +| ② 到货验收 | 毛菜验收秤 | 称重、AI 识别、质检判定、采样 | 毛菜入库建档、溯源码分配 | +| ③ 毛菜入库 | 毛菜库存柜 | 储位分配、出入库管理 | `sup-warehouse` 仓储管理、`sup-in-raw` 毛菜入库 | +| ④ 毛菜清洗 | 加工毛菜秤 | 批次称重、毛菜加工任务处理 | 毛菜加工任务推送、任务状态回传 | +| ⑤ 净菜切配 | 厨配秤 | 上秤识别、加工确认(切配规格) | 切配任务推送、加工记录 | +| ⑥ 净菜入库 | 净菜库存柜 | 净菜库存管理、盘点 | `sup-warehouse` 仓储管理、`sup-in-cleanin` 净菜入库 | +| ⑦ 净配 | 净配秤 | 净菜包 / 餐品组合打包 | 净配任务推送、净配记录 | +| ⑧ 包装发货 | 净菜包装贴标秤 | 核对、包装、打印标签、发货 | 发货状态回传、配送信息 | + +> 「毛菜卫检」环节(对应后台 `sup-hygiene-check` 卫生检测)因设备端尚待硬件选型,未纳入上述闭环;待选型完成后将插入「到货验收」与「毛菜入库」之间。 + +--- + +## 二、硬件使用文档 + +### 2.1 毛菜验收秤 + +#### 用途 + +毛菜验收秤负责毛菜到货的**验收、采样、记录查询**。 + +#### 硬件配置 + +| 组件 | 用途 | +|---|---| +| 触摸屏 | 交互操作、信息确认、不合格处理 | +| 称重传感器 + 秤盘 | 自动读取重量(kg) | +| 食材识别摄像头 | AI 自动识别食材品种 | +| 场景监控摄像头 | 后台全程静默录像,不参与前端交互 | + +#### 登录方式 + +账号密码登录,登录后保持会话;身份以登录账号为准,未登录无法操作。 + +#### 场景 + +| 场景 | 触发条件 | 说明 | +|---|---|---| +| **有订单验收** | 后台采购订单已填配送信息 | 主场景,按订单逐食材验收 | +| **食材采样** | 验收前采集样本 | 验收前置环节 | + +#### 按场景使用 + +##### 场景一:有订单验收(主场景) + +1. 在欢迎页输入账号密码登录。 +2. 点击「验收食材」,选择要验收的订单。 +3. 把食材放上秤盘,系统自动识别并称重;拿起食材即完成该食材的验收。 +4. 订单内食材全部验收完后,在弹出的汇总弹窗点「提交并继续」或「提交并关闭」。 + +##### 场景二:食材采样(验收前置) + +1. 在欢迎页点击「食材采样」,登录后进入采样页。 +2. 输入食材名称,点击拍照。 +3. 点击「保存」或「保存并继续」。 + diff --git a/web-admin/nutrition/din-terminal.html b/web-admin/nutrition/din-terminal.html index 28263f3..192f110 100644 --- a/web-admin/nutrition/din-terminal.html +++ b/web-admin/nutrition/din-terminal.html @@ -76,6 +76,7 @@ tbody tr:hover{background:#F8FBFF;} .tag-orange{background:#FFF7E6;color:var(--warning);} .tag-red{background:#FFF1F0;color:var(--danger);} .tag-gray{background:#F5F5F5;color:#999;} +.tag-purple{background:#F9F0FF;color:#722ED1;} .link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;} .pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);} .pagination span{font-size:var(--font-sm);color:var(--text-secondary);} @@ -109,7 +110,7 @@ tbody tr:hover{background:#F8FBFF;} .stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);} .action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;} /* 动态弹窗基础 */ -.dyn-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1010;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;} +.dyn-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1100;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;} .dyn-overlay.show{opacity:1;} .dyn-modal{background:#fff;border-radius:var(--radius-lg);max-height:80vh;display:flex;flex-direction:column;transform:translateY(30px);transition:transform .25s;box-shadow:0 12px 40px rgba(0,0,0,0.18);} .dyn-overlay.show .dyn-modal{transform:translateY(0);} @@ -311,6 +312,46 @@ tbody tr:hover{background:#F8FBFF;} /* Tab 内嵌区块标题 */ .ov-section-title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin:0 0 12px;display:flex;align-items:center;gap:6px;} .ov-section-title::before{content:'';display:inline-block;width:3px;height:15px;background:var(--primary);border-radius:2px;} +/* 表头列筛选 */ +.th-filter{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-left:4px;border-radius:3px;color:#c0c4cc;font-size:10px;cursor:pointer;vertical-align:-3px;transition:all .15s;} +.th-filter:hover{color:var(--primary);background:#e8f1ff;} +.th-filter--active{color:var(--primary);background:#e8f1ff;} +.th-filter-pop{position:fixed;z-index:1700;min-width:150px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 6px 20px rgba(0,0,0,.15);padding:6px;max-height:240px;overflow-y:auto;} +.th-filter-pop__item{padding:6px 10px;font-size:var(--font-sm);color:var(--text-primary);border-radius:4px;cursor:pointer;white-space:nowrap;} +.th-filter-pop__item:hover{background:#f5f8ff;} +.th-filter-pop__item.active{color:var(--primary);background:#e8f1ff;font-weight:500;} +/* ===== 餐线营养秤概览(子页切换) ===== */ +.scale-sub-tabs{display:flex;gap:8px;margin-bottom:16px;border-bottom:1px solid var(--border);} +.scale-sub-tab{padding:8px 16px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;user-select:none;transition:all .15s;} +.scale-sub-tab:hover{color:var(--primary);} +.scale-sub-tab.active{color:var(--primary);font-weight:600;border-bottom-color:var(--primary);} +/* ===== 人脸识别视频回放弹窗 ===== */ +@keyframes ovScan{0%{top:10%}50%{top:88%}100%{top:10%}} +.face-video-mask{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1600;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;} +.face-video-mask.closing{animation:ovFadeOut .2s ease forwards;} +.face-video-modal{width:680px;max-width:92vw;max-height:86vh;background:#fff;border-radius:var(--radius-md);box-shadow:0 12px 40px rgba(0,0,0,.22);display:flex;flex-direction:column;overflow:hidden;animation:ovModalIn .22s ease;} +.face-video-mask.closing .face-video-modal{animation:ovModalOut .18s ease forwards;} +.face-video-modal__header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0;} +.face-video-modal__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);} +.face-video-modal__close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;} +.face-video-modal__close:hover{background:#f5f5f5;color:var(--text-primary);} +.face-video-modal__body{padding:20px;overflow-y:auto;flex:1;} +.face-video-player{position:relative;background:#141a24;border-radius:var(--radius-md);height:280px;display:flex;align-items:center;justify-content:center;overflow:hidden;} +.face-video-player__scan{position:absolute;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#40a9ff,transparent);animation:ovScan 3s linear infinite;} +.face-video-player__badge{position:absolute;top:12px;left:14px;font-size:10px;color:#fff;background:rgba(255,77,79,.85);padding:2px 8px;border-radius:10px;z-index:1;} +.face-video-player__meta{position:absolute;bottom:12px;left:14px;right:14px;display:flex;justify-content:space-between;color:rgba(255,255,255,.65);font-size:var(--font-xs);font-family:monospace;z-index:1;} +.face-video-player__play{width:52px;height:52px;border-radius:50%;background:rgba(24,144,255,.18);border:2px solid rgba(24,144,255,.7);display:flex;align-items:center;justify-content:center;color:#40a9ff;cursor:pointer;z-index:1;font-size:18px;} +.face-video-controls{display:flex;align-items:center;gap:10px;padding:14px 4px 4px;} +.face-video-btn{height:30px;padding:0 12px;border:1px solid var(--border);background:#fff;border-radius:var(--radius-sm);cursor:pointer;font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;} +.face-video-btn:hover{border-color:var(--primary);color:var(--primary);} +.face-video-btn--primary{min-width:36px;padding:0;display:flex;align-items:center;justify-content:center;font-size:14px;border-color:var(--primary);color:var(--primary);} +.face-video-bar{flex:1;height:6px;background:#f0f0f0;border-radius:3px;overflow:hidden;cursor:pointer;} +.face-video-bar__progress{height:100%;background:var(--primary);border-radius:3px;width:0;} +.face-video-time{font-size:var(--font-xs);color:var(--text-secondary);font-family:monospace;white-space:nowrap;} +.face-video-info{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:12px;padding-top:14px;border-top:1px solid #f0f0f0;} +.face-video-info__item{font-size:var(--font-sm);} +.face-video-info__item .label{display:block;font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:4px;} +.face-video-info__item strong{color:var(--text-primary);font-weight:600;} @@ -668,6 +709,7 @@ document.querySelectorAll('tbody tr').forEach(row=>{ /* ===== 设备概览大弹窗(原型演示:Tab 集中演示各类设备,实际交付按设备类型显示对应概览) ===== */ /* Tab 定义:仅「智能货柜」已实现内容,其余为占位 */ var OV_TABS=[ + {key:'scale',name:'餐线营养秤',icon:'⚖️'}, {key:'plate',name:'餐盘柜',icon:'🍽️'}, {key:'smart',name:'智能货柜',icon:'🗄️'}, {key:'raw',name:'毛菜货柜',icon:'🥬'}, @@ -725,6 +767,7 @@ function switchOvTab(key){ /* 按 Tab 渲染内容:占位 / 智能货柜可视化 */ function renderOvTabBody(key){ + if(key==='scale')return renderCanteenScale(); if(key==='smart')return renderSmartCabinet(); if(key==='plate')return renderPlateCabinet(); if(key==='clean')return renderStockCabinet('clean'); @@ -741,6 +784,340 @@ function renderOvTabBody(key){ `; } +/* ===== 表头列筛选(餐线营养秤四个 Tab 复用) ===== */ +var SCALE_FILTERS={status:{},face:{},weight:{},log:{}}; +/* 每个 Tab 的列定义:key 列标识 / label 表头文字 / field 数据字段 */ +var SCALE_COLS={ + status:[ + {key:'time',label:'日期/时间',field:'time'}, + {key:'item',label:'监控项',field:'item'}, + {key:'event',label:'事件',field:'event'} + ], + face:[ + {key:'start',label:'开始时间',field:'start'}, + {key:'end',label:'结束时间',field:'end'}, + {key:'status',label:'状态',field:'status'}, + {key:'weight',label:'重量变化',field:'weight'}, + {key:'total',label:'总重量',field:'total'} + ], + weight:[ + {key:'start',label:'开始时间',field:'start'}, + {key:'end',label:'结束时间',field:'end'}, + {key:'target',label:'识别对象',field:'target'}, + {key:'submit',label:'提交重量',field:'submit'}, + {key:'total',label:'总重量',field:'total'} + ], + log:[ + {key:'time',label:'日期/时间',field:'time'}, + {key:'type',label:'报错类型',field:'type'}, + {key:'content',label:'报错内容',field:'code'} + ] +}; +/* 各维度数据声明(实际数据在对应渲染函数上方赋值) */ +var SCALE_STATUS_EVENTS=[]; +var SCALE_WEIGHT_RECORDS=[]; +var SCALE_LOG_RECORDS=[]; + +/* 生成带筛选图标的表头单元格 */ +function scaleTh(colKey,label,tabKey){ + var active=SCALE_FILTERS[tabKey]&&SCALE_FILTERS[tabKey][colKey]; + return label+''; +} +/* 生成表头(可选追加操作列,操作列不可筛选) */ +function scaleThead(tabKey,actionLabel){ + var ths=SCALE_COLS[tabKey].map(function(c){return ''+scaleTh(c.key,c.label,tabKey)+'';}).join(''); + if(actionLabel)ths+=''+actionLabel+''; + return ''+ths+''; +} +/* 取某 Tab 原始数据 */ +function getScaleRows(tabKey){ + if(tabKey==='status')return SCALE_STATUS_EVENTS||[]; + if(tabKey==='face')return SCALE_FACE_RECORDS||[]; + if(tabKey==='weight')return SCALE_WEIGHT_RECORDS||[]; + if(tabKey==='log')return SCALE_LOG_RECORDS||[]; + return []; +} +/* 应用当前筛选,返回过滤后的行 */ +function getFilteredScaleRows(tabKey){ + var rows=getScaleRows(tabKey); + var f=SCALE_FILTERS[tabKey]||{}; + var cols=SCALE_COLS[tabKey]||[]; + return rows.filter(function(r){ + for(var i=0;i全部'; + values.forEach(function(v){ + html+='
'+escapeHtml(v)+'
'; + }); + pop.innerHTML=html; + document.body.appendChild(pop); + var rect=icon.getBoundingClientRect(); + pop.style.left=Math.min(rect.left,window.innerWidth-170)+'px'; + pop.style.top=(rect.bottom+4)+'px'; + document.addEventListener('click',onDocClickForFilter,{once:true}); +} +function onDocClickForFilter(e){ + if(!e.target.closest('.th-filter-pop'))closeThFilter(); +} +function closeThFilter(){ + var p=document.getElementById('thFilterPop'); + if(p)p.remove(); +} +function pickThFilter(tabKey,colKey,val){ + if(!SCALE_FILTERS[tabKey])SCALE_FILTERS[tabKey]={}; + SCALE_FILTERS[tabKey][colKey]=val; + closeThFilter(); + var body=document.getElementById('scaleSubBody'); + if(body)body.innerHTML=renderScaleSubBody(SCALE_SUB_TAB); +} +function escapeHtml(s){ + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); +} +function escapeAttr(s){ + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); +} + +/* ===== 餐线营养秤:四个维度(子页切换) ===== */ +var SCALE_SUB_TABS=[ + {key:'status',name:'设备状态监控'}, + {key:'weight',name:'重量变化数据'}, + {key:'face',name:'人脸识别数据'}, + {key:'log',name:'运行日志'} +]; +var SCALE_SUB_TAB='status'; + +function renderCanteenScale(){ + const tabsHtml=SCALE_SUB_TABS.map(t=>`
${t.name}
`).join(''); + return `
${tabsHtml}
${renderScaleSubBody(SCALE_SUB_TAB)}
`; +} +function switchScaleSubTab(key){ + SCALE_SUB_TAB=key; + document.querySelectorAll('#ovTabBody .scale-sub-tab').forEach(t=>t.classList.toggle('active',t.dataset.key===key)); + const body=document.getElementById('scaleSubBody'); + if(body)body.innerHTML=renderScaleSubBody(key); +} +function renderScaleSubBody(key){ + if(key==='status')return renderScaleStatus(); + if(key==='face')return renderScaleFace(); + if(key==='weight')return renderScaleWeight(); + if(key==='log')return renderScaleLog(); + return ''; +} + +/* 维度1:设备状态监控 — 状态事件流水 */ +SCALE_STATUS_EVENTS=[ + {time:'2026-08-25 08:15:32.456',item:'网络连接',event:'重新连接成功'}, + {time:'2026-08-25 08:00:00.000',item:'运行状态',event:'开餐'}, + {time:'2026-08-25 07:40:12.511',item:'运行状态',event:'加餐'}, + {time:'2026-08-25 06:30:00.000',item:'设备开关机',event:'开机'}, + {time:'2026-08-25 04:12:33.098',item:'运行状态',event:'换餐'}, + {time:'2026-08-24 23:00:00.000',item:'设备开关机',event:'关机'}, + {time:'2026-08-24 22:30:18.764',item:'运行状态',event:'闭餐'}, + {time:'2026-08-24 21:00:02.130',item:'运行状态',event:'进入待机'} +]; +function renderScaleStatus(){ + const rows=getFilteredScaleRows('status').map(s=>`${s.time}${s.item}${s.event}`).join(''); + return `
设备状态监控
+
${scaleThead('status')}${rows}
`; +} + +/* 维度2:人脸识别数据 — 开始/结束时间 / 状态 / 用户类型 / 重量变化 + 视频回放 */ +var SCALE_FACE_RECORDS=[ + {start:'2026-08-25 08:15:23.456',end:'2026-08-25 08:15:58.210',status:'刷脸+离开',userType:'系统用户',total:'486.5g',weight:'+486.5g'}, + {start:'2026-08-25 08:14:51.203',end:'2026-08-25 08:15:02.118',status:'刷脸',userType:'系统用户',total:'462.0g',weight:'+462.0g'}, + {start:'2026-08-25 08:13:47.889',end:'2026-08-25 08:14:10.540',status:'离开',userType:'系统用户',total:'0.0g',weight:'-445.8g'}, + {start:'2026-08-25 08:12:09.310',end:'2026-08-25 08:12:30.775',status:'刷脸',userType:'陌生人',total:'0.0g',weight:'+0.0g'} +]; + +function renderScaleFace(){ + const statusTagMap={ + '刷脸':'刷脸', + '离开':'离开', + '刷脸+离开':'刷脸+离开' + }; + const rows=getFilteredScaleRows('face').map((f)=>{ + const statusTag=statusTagMap[f.status]||''+escapeHtml(f.status)+''; + const isPos=f.weight.startsWith('+'); + const weightHtml=`${f.weight}`; + return `${f.start}${f.end}${statusTag}${weightHtml}${f.total}回放视频`; + }).join(''); + return `
人脸识别数据
+
${scaleThead('face','操作')}${rows}
`; +} + +/* 录像回放弹窗:自动播放 + 快进/后退 + 进度条跳转 */ +var FACE_VIDEO={playing:false,current:0,duration:30,timer:null}; + +function showFaceVideo(start){ + const f=SCALE_FACE_RECORDS.find(x=>x.start===start); + if(!f)return; + FACE_VIDEO.current=0; + FACE_VIDEO.duration=30; + FACE_VIDEO.playing=true; + const mask=document.createElement('div'); + mask.className='face-video-mask'; + mask.id='faceVideoMask'; + mask.innerHTML=`
+
+
人脸识别视频回放 — ${f.start}
+ +
+
+
+ ● REC +
+
+
CAM-01${f.start}
+
+
+ + + +
+
00:00 / 00:30
+
+
+
识别时间${f.start}
+
状态${f.status}
+
用户类型${f.userType}
+
重量变化${f.weight}
+
+
+
`; + mask.addEventListener('click',e=>{if(e.target===mask)closeFaceVideo();}); + document.body.appendChild(mask); + updateFaceVideoUI(); + startFaceVideoTimer(); +} +function closeFaceVideo(){ + clearInterval(FACE_VIDEO.timer); + FACE_VIDEO.playing=false; + const m=document.getElementById('faceVideoMask'); + if(!m)return; + m.classList.add('closing'); + setTimeout(()=>m.remove(),200); +} +function startFaceVideoTimer(){ + clearInterval(FACE_VIDEO.timer); + FACE_VIDEO.timer=setInterval(()=>{ + if(!FACE_VIDEO.playing)return; + FACE_VIDEO.current=Math.min(FACE_VIDEO.duration,FACE_VIDEO.current+0.1); + if(FACE_VIDEO.current>=FACE_VIDEO.duration){ + FACE_VIDEO.playing=false; + clearInterval(FACE_VIDEO.timer); + } + updateFaceVideoUI(); + },100); +} +function faceVideoPlayPause(){ + FACE_VIDEO.playing=!FACE_VIDEO.playing; + if(FACE_VIDEO.playing){ + if(FACE_VIDEO.current>=FACE_VIDEO.duration)FACE_VIDEO.current=0; + startFaceVideoTimer(); + } + updateFaceVideoUI(); +} +function faceVideoSeek(delta){ + FACE_VIDEO.current=Math.max(0,Math.min(FACE_VIDEO.duration,FACE_VIDEO.current+delta)); + updateFaceVideoUI(); +} +function faceVideoSeekTo(e){ + const bar=document.getElementById('faceVideoBar'); + if(!bar)return; + const rect=bar.getBoundingClientRect(); + const pct=Math.max(0,Math.min(1,(e.clientX-rect.left)/rect.width)); + FACE_VIDEO.current=pct*FACE_VIDEO.duration; + updateFaceVideoUI(); +} +function updateFaceVideoUI(){ + const progress=document.getElementById('faceVideoProgress'); + const time=document.getElementById('faceVideoTime'); + const btn=document.getElementById('faceVideoPlayBtn'); + const big=document.querySelector('#faceVideoMask .face-video-player__play'); + const pct=FACE_VIDEO.current/FACE_VIDEO.duration*100; + if(progress)progress.style.width=pct+'%'; + if(time)time.textContent=formatFaceVideoTime(FACE_VIDEO.current)+' / '+formatFaceVideoTime(FACE_VIDEO.duration); + const icon=FACE_VIDEO.playing?'⏸':'▶'; + if(btn)btn.textContent=icon; + if(big)big.textContent=icon; +} +function formatFaceVideoTime(t){ + const m=Math.floor(t/60); + const s=Math.floor(t%60); + return (m<10?'0'+m:m)+':'+(s<10?'0'+s:s); +} + +/* 维度3:重量变化数据 — 开始/结束时间 / 识别对象 / 提交重量 / 总重量 */ +SCALE_WEIGHT_RECORDS=[ + {start:'2026-08-25 08:15:22.701',end:'2026-08-25 08:15:30.210',target:'EMP0234 · 张伟',submit:'436.5',total:'486.5'}, + {start:'2026-08-25 08:14:50.512',end:'2026-08-25 08:14:58.430',target:'无用户',submit:'412.0',total:'462.0'}, + {start:'2026-08-25 08:13:46.098',end:'2026-08-25 08:13:55.118',target:'EMP0357 · 王强',submit:'—',total:'501.2'}, + {start:'2026-08-25 08:12:08.876',end:'2026-08-25 08:12:18.775',target:'无用户',submit:'0.0',total:'0.0'} +]; +function renderScaleWeight(){ + const rows=getFilteredScaleRows('weight').map(w=>`${w.start}${w.end}${w.target}${w.submit}${w.total}`).join(''); + return `
重量变化数据
+
${scaleThead('weight')}${rows}
`; +} + +/* 维度4:运行日志 — 日期/时间 / 报错类型 / 报错内容(报错代码,20字符) / 操作 */ +SCALE_LOG_RECORDS=[ + {time:'2026-08-25 08:10:12.330',type:'异常报错',code:'E1001_WEIGHT_SENSOR_READ_TIMEOUT_RETRY',detail:'E1001_WEIGHT_SENSOR_READ_TIMEOUT_RETRY\n\n[2026-08-25 08:10:12.330] ERROR WeightSensorModule::read()\n 称重传感器读取超时,连续 3 次无响应\n 设备编号: DEV-00025 传感器通道: CH-02\n 已触发自动重试,若重试失败将上报设备故障\n TraceId: 9f3c1a7b-2e4d-4c8a-b1f0-5d6e7f8a9b0c'}, + {time:'2026-08-25 08:02:45.117',type:'性能预警',code:'W2003_RECOG_LATENCY_OVER_THRESHOLD_320MS',detail:'W2003_RECOG_LATENCY_OVER_THRESHOLD_320MS\n\n[2026-08-25 08:02:45.117] WARN RecognitionModule::detect()\n 人脸识别响应延迟 320ms,接近阈值 300ms\n 摄像头: CAM-01 帧率: 25fps\n 建议检查现场光照条件与网络带宽\n TraceId: 4b8e2c31-7a9d-4f5b-9c2e-1a3b4c5d6e7f'}, + {time:'2026-08-25 07:58:03.662',type:'性能预警',code:'W2004_MEMORY_USAGE_HIGH_82PCT',detail:'W2004_MEMORY_USAGE_HIGH_82PCT\n\n[2026-08-25 07:58:03.662] WARN SystemMonitor::check()\n 内存占用 82%,接近预警阈值 85%\n 当前进程: nutrition-scale-app\n 建议关注内存泄漏或及时清理缓存\n TraceId: c5d6e7f8-2a3b-4c4d-8e9f-0a1b2c3d4e5f'}, + {time:'2026-08-25 07:30:00.000',type:'运行日志',code:'I0000_SELF_CHECK_PASS',detail:'I0000_SELF_CHECK_PASS\n\n[2026-08-25 07:30:00.000] INFO SystemMonitor::selfCheck()\n 设备完成夜间自检,各项指标正常\n 传感器: 正常 摄像头: 正常 网络: 正常\n TraceId: 6f1a2b3c-4d5e-4f6a-8b7c-9d0e1f2a3b4c'} +]; +function truncateCode(code){ + if(!code)return ''; + return code.length>20?code.slice(0,20)+'…':code; +} +function renderScaleLog(){ + const rows=getFilteredScaleRows('log').map(l=>{ + const codeHtml=`${escapeHtml(truncateCode(l.code))}`; + return `${l.time}${l.type}${codeHtml}查看详情`; + }).join(''); + return `
运行日志
+
${scaleThead('log','操作')}${rows}
`; +} +function showLogDetail(code){ + var rec=SCALE_LOG_RECORDS.filter(function(l){return l.code===code;})[0]; + if(!rec)return; + var html='
' + +'
日期/时间
'+escapeHtml(rec.time)+'
' + +'
报错类型
'+escapeHtml(rec.type)+'
' + +'
报错代码
'+escapeHtml(rec.detail)+'
' + +'
'; + DynModal.open('报错详情',html,''); +} + /* ===== 智能货柜:柜子轮廓布局 + 储物状态(参考 prod-proc-cabinet.html 左侧柜子样式) ===== */ /* 格口 Mock:状态 occupied 储物中 / warning 临期预警 / alert 过期预警 / empty 空闲 */ var OV_SLOTS=[ diff --git a/web-admin/nutrition/doc/CHANGELOG.md b/web-admin/nutrition/doc/CHANGELOG.md index 38e3df1..4a1b957 100644 --- a/web-admin/nutrition/doc/CHANGELOG.md +++ b/web-admin/nutrition/doc/CHANGELOG.md @@ -1,5 +1,20 @@ # 营养用餐管理端 CHANGELOG +## [2026-08-27] feat: 订餐终端设备概览新增「餐线营养秤」Tab + +- `din-terminal.html` 订餐终端设备概览: + - 新增「餐线营养秤」Tab,四个维度子页切换(设备状态监控/重量变化数据/人脸识别数据/运行日志) + - 表头新增列筛选(▾ 图标下拉,按列去重取值,四个维度复用) + - 人脸识别数据支持「回放视频」:弹窗含自动播放/快进后退 10s/进度条跳转/识别信息 + - 运行日志支持「查看详情」:报错内容超 20 字符截断,详情弹窗展示完整报错日志(含 TraceId) + +## [2026-08-26] feat: 净菜订单新增生成历史 + 生成方式列 + 详情就餐人数 + +- `prod-clean-order.html` 净菜订单: + - 新增「净菜订单生成历史」按钮与弹窗(食谱日期/预定配送时间/餐次/内配调料四筛选 + 9 列表格),由食谱创建订单时自动写入生成历史 + - 主列表「订单类型」前新增「生成方式」列(手动/自动),预置自动订单 CO-009 示例 + - 详情抽屉订单明细:仅自动生成的订单展示「就餐人数」列 + ## [2026-08-25] feat: 餐线设置新增用户范围必填复选 + 食谱订单餐品明细表份数调整 - `din-canteen-line.html` 餐线管理: diff --git a/web-admin/nutrition/doc/角色场景流程图.md b/web-admin/nutrition/doc/角色场景流程图.md new file mode 100644 index 0000000..c96b7ca --- /dev/null +++ b/web-admin/nutrition/doc/角色场景流程图.md @@ -0,0 +1,159 @@ +# 营养用餐模块 - 角色泳道图 & 问题汇总 + +> 横轴:角色(平台 → 加工商 → 场所 → 用户) +> 纵轴:场景流程 +> 标注:每个场景下方附现有问题 + +--- + +## 一、角色场景泳道图 + +```mermaid +flowchart TD + subgraph 平台["🏛️ 平台(Platform)"] + direction TB + + subgraph 准备["阶段一:平台准备"] + S1["场景1:食物成分录入
──────────────
营养用餐 → 食物成分
→ 分类管理:新增多级分类
→ 点击新增食物
→ 录入食物成分信息
→ 保存"] + S2["场景2:餐品总库
──────────────
营养用餐 → 餐品总库
→ 新增餐品分类
→ 新增餐品
→ 保存"] + end + + subgraph 场所管理["阶段二:场所管理"] + S3["场景3:新增场所
──────────────
营养用餐 → 场所管理
→ 新增场所
→ 录入场所信息
→ 保存 → 场所创建完成
→ 主账号发放"] + S4["场景4:餐线管理
──────────────
场所管理 → 选择场所
→ 餐线管理
→ 绑定设备"] + end + + 准备 --> 场所管理 + end + + subgraph 加工商["🏭 加工商(Processor)"] + P_EMPTY["(待补充)"] + end + + subgraph 场所["📍 场所(Venue)"] + V_EMPTY["(待补充)"] + end + + subgraph 用户["👤 用户(User)"] + U_EMPTY["(待补充)"] + end + + 平台 --> 加工商 --> 场所 --> 用户 + + %% 平台准备 - 食物成分问题 + S1 -.->|"⚠️"| Q1["⑥ 缺少「食物功效」模块"] + S1 -.->|"⚠️"| Q2["⑦ 味道档案应改为
酸甜苦辣咸下拉菜单"] + S1 -.->|"⚠️"| Q3["⑧ 分类应内置
中国食物成分表标准分类"] + S1 -.->|"⚠️"| Q4["⑨ 口感和色泽字段不需要"] + S1 -.->|"⚠️"| Q5["⑩ 宏量营养素应为必填项"] + S1 -.->|"⚠️"| Q6["⑪ 食物类目启禁用
应展示在列表中"] + S1 -.->|"⚠️"| Q7["⑫ 食物缺少启禁用状态"] + + %% 平台准备 - 餐品总库问题 + S2 -.->|"⚠️"| Q8["⑬ 餐品分类无法维护
(增删改查)"] + S2 -.->|"⚠️"| Q9["⑭ 餐品分类无法启禁用"] + S2 -.->|"⚠️"| Q10["⑮ 基本档案中适用餐次、
过敏原建议删除"] + S2 -.->|"⚠️"| Q11["⑯ 烹饪工艺/味道档案/
口感档案/色泽档案
建议删除"] + S2 -.->|"⚠️"| Q12["⑰ 营养指标模块
应按2.0版本做"] + + %% 场所管理 - 新增场所问题 + S3 -.->|"⚠️"| Q13["① 负责人和负责人电话
应为必填项"] + S3 -.->|"⚠️"| Q14["② 「是否使用三减终端」
含义不明确"] + S3 -.->|"⚠️"| Q15["③ 详细地址应为必填项"] + S3 -.->|"⚠️"| Q16["④ 场所主账号应
发送到负责人手机"] + S3 -.->|"⚠️"| Q17["⑤ 场所管理缺少
启禁用按钮"] + + %% 场所管理 - 餐线管理问题 + S4 -.->|"⚠️"| Q18["⑥ 绑定设备数量不反显"] + S4 -.->|"⚠️"| Q19["⑦ 期望有默认餐线"] +``` + +--- + +## 二、泳道图(简化文字版) + +``` +┌──────────────┬──────────────┬──────────────┬──────────────┐ +│ 平台 │ 加工商 │ 场所 │ 用户 │ +├──────────────┼──────────────┼──────────────┼──────────────┤ +│ │ │ │ │ +│ 【平台准备】 │ │ │ │ +│ │ │ │ │ +│ 1.食物成分 │ │ │ │ +│ 分类管理 │ │ │ │ +│ 新增食物 │ │ │ │ +│ │ │ │ │ +│ 2.餐品总库 │ │ │ │ +│ 餐品分类 │ │ │ │ +│ 新增餐品 │ │ │ │ +│ │ │ │ │ +│ 【场所管理】 │ │ │ │ +│ │ │ │ │ +│ 3.新增场所 │ │ │ │ +│ 录入信息 │ │ │ │ +│ 主账号发放 │ │ │ │ +│ │ │ │ │ +│ 4.餐线管理 │ │ │ │ +│ 绑定设备 │ │ │ │ +│ │ │ │ │ +├──────────────┼──────────────┼──────────────┼──────────────┤ +│ │ (待补充) │ (待补充) │ (待补充) │ +└──────────────┴──────────────┴──────────────┴──────────────┘ +``` + +--- + +## 三、问题汇总表 + +### 平台准备 → 食物成分(#⑥ ~ #⑫) + +| # | 问题 | 期望 | +|---|------|------| +| ⑥ | 缺少「食物功效」模块 | 新增模块,后期用于匹配模型自动关联用户调理方案 | +| ⑦ | 味道档案含主观填写字段(口味描述、辅味型、味道强度、饮食禁忌等) | 改为**酸甜苦辣咸**下拉菜单,去掉其余主观字段 | +| ⑧ | 分类需手动新建 | 内置**中国食物成分表**标准分类 | +| ⑨ | 有「口感」和「色泽」字段 | 去掉,不需要 | +| ⑩ | 宏量营养素非必填 | 改为**必填项** | +| ⑪ | 食物类目启禁用在编辑页内,操作不便 | 启禁用展示在**列表中**;禁用类目时,关联餐品**全部下架** | +| ⑫ | 食物缺少启禁用状态 | 新增启禁用,逻辑同类目:禁用食物时关联餐品全部下架 | + +### 平台准备 → 餐品总库(#⑬ ~ #⑰) + +| # | 问题 | 期望 | +|---|------|------| +| ⑬ | 餐品分类无法维护(增删改查) | 支持餐品分类的增删改查 | +| ⑭ | 餐品分类无法启禁用 | 新增启禁用功能 | +| ⑮ | 基本档案中「适用餐次」「过敏原」多余 | 删除适用餐次、过敏原字段 | +| ⑯ | 烹饪工艺、味道档案、口感档案、色泽档案模块多余 | 全部删除 | +| ⑰ | 营养指标模块不符合 2.0 版本规范 | 按 2.0 版本重新设计 | + +### 场所管理 → 新增场所(#① ~ #⑤) + +| # | 问题 | 期望 | +|---|------|------| +| ① | 负责人和负责人电话非必填 | 改为**必填项** | +| ② | 「是否使用三减终端」含义不明确 | 加字段说明或提示文案 | +| ③ | 详细地址非必填 | 改为**必填项** | +| ④ | 场所主账号发放逻辑不明确 | 创建场所后主账号**发送到负责人手机** | +| ⑤ | 场所管理缺少启禁用按钮 | 新增启禁用按钮 | + +### 场所管理 → 餐线管理(#⑥ ~ #⑦) + +| # | 问题 | 期望 | +|---|------|------| +| ⑥ | 绑定设备后数量不反显 | 绑定后实时显示已绑设备数量 | +| ⑦ | 没有默认餐线 | 创建场所后自动生成一条默认餐线 | + +--- + +## 四、汇总统计 + +| 角色 | 阶段 | 场景数 | 问题数 | 状态 | +|------|------|--------|--------|------| +| 平台 | 平台准备 | 2(食物成分、餐品总库) | 12 | ✅ 已梳理 | +| 平台 | 场所管理 | 2(新增场所、餐线管理) | 7 | ✅ 已梳理 | +| **平台小计** | — | **4** | **19** | — | +| 加工商 | — | 0 | 0 | ⬜ 待补充 | +| 场所 | — | 0 | 0 | ⬜ 待补充 | +| 用户 | — | 0 | 0 | ⬜ 待补充 | +| **总计** | — | **4** | **19** | — | diff --git a/web-admin/nutrition/prod-clean-order.html b/web-admin/nutrition/prod-clean-order.html index 7d8054e..0351c34 100644 --- a/web-admin/nutrition/prod-clean-order.html +++ b/web-admin/nutrition/prod-clean-order.html @@ -268,17 +268,18 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0,
+
净菜订单
- +
订单号订单类型净菜类型菜品种类总份数总计量(kg)订单金额订单状态申领时间预定配送时间创建人物流信息操作
订单号生成方式订单类型净菜类型菜品种类总份数总计量(kg)订单金额订单状态申领时间预定配送时间创建人物流信息操作
@@ -397,6 +398,34 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0, + + +