From 792567ac03615a7d29c577672d6a8f510660a8ce Mon Sep 17 00:00:00 2001 From: fengpu <18691763612@163.com> Date: Tue, 18 Aug 2026 17:36:17 +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=E5=BC=B9=E7=AA=97=20+=20?= =?UTF-8?q?=E4=BC=9A=E5=91=98=E6=9D=83=E7=9B=8A/=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E6=8A=B5=E6=89=A3/=E8=BF=BD=E6=BA=AF?= =?UTF-8?q?=E5=A4=9A=E9=94=85=E6=AC=A1=E5=88=86=E6=94=AF=20+=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=AB=AF=E7=BB=9F=E8=AE=A1=E4=B8=8E=E9=AA=8C=E6=94=B6?= =?UTF-8?q?=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- .claude/memory/MEMORY.md | 6 +- .claude/memory/lint_report.md | 55 +- .claude/memory/project_progress.md | 25 +- .idea/claudeCodeTabState.xml | 63 +- CLAUDE.md | 2 +- .../checkout-device/07-statistics.html | 321 ++++--- other-module/canteen-device/doc/CHANGELOG.md | 12 + .../01-verify-inspection.html | 140 +-- web-admin/nutrition/ano-result.html | 25 +- web-admin/nutrition/din-dish-own.html | 181 +++- web-admin/nutrition/din-order-pickup.html | 23 +- web-admin/nutrition/din-terminal.html | 891 +++++++++++++++++- web-admin/nutrition/din-user-mgmt.html | 76 +- web-admin/nutrition/doc/CHANGELOG.md | 35 + web-admin/nutrition/farm-purchase.html | 172 +++- web-admin/nutrition/trace-timeline-dish.html | 20 +- .../nutrition/trace-timeline-ingredient.html | 20 +- 17 files changed, 1718 insertions(+), 349 deletions(-) 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - '; - } - 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 + 详情 +
@@ -318,18 +320,19 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
- + + - - - - - - - - - + + + + + + + + +
就餐记录号姓名员工编号单位部门餐次取餐方式取餐类型人员类别取餐开始时间取餐结束时间取餐总数量总金额营养评分操作
就餐记录号姓名员工编号单位部门餐次取餐方式取餐类型人员类别取餐开始时间取餐结束时间取餐总数量结算重量(克)实际重量(克)重量偏差(克)总金额营养评分操作
M-20260428-001张伟EMP001CQ能源研发部午餐点餐取餐正常取餐会员11:0211:083件¥22.0088分查看用户信息
M-20260428-002李娜EMP002CQ能源市场部午餐点餐取餐正常取餐会员11:0811:143件¥18.0092分查看用户信息
M-20260428-003王磊EMP003CQ能源运营部午餐餐线取餐正常取餐非会员11:1511:202件¥28.0068分查看用户信息
M-20260428-004赵敏EMP004CQ能源财务部午餐档口+餐线正常取餐会员11:2211:303件¥25.5094分查看用户信息
M-20260428-005陈刚EMP005CQ能源人事部早餐餐线取餐正常取餐非会员07:4807:553件¥12.0085分查看用户信息
M-20260428-006刘芳EMP006CQ能源市场部午餐外卖/外带正常取餐会员11:4511:482件¥15.8090分查看用户信息
SP-001-001张运维EMP101CQ能源运维部午餐余量取餐余量取餐员工14:0514:061件¥5.00查看用户信息
SP-001-003王保安CQ能源安保部午餐余量取餐余量取餐工作人员14:1014:111件¥5.00查看用户信息
M-20260428-007林教授外部单位外部访客午餐点餐取餐正常取餐外部人员12:1012:183件¥26.5086分查看用户信息
M-20260428-001张伟EMP001CQ能源研发部午餐点餐取餐正常取餐会员11:0211:083件326.0338.5+12.5¥22.0088分查看用户信息
M-20260428-002李娜EMP002CQ能源市场部午餐点餐取餐正常取餐会员11:0811:143件365.0363.0-2.0¥18.0092分查看用户信息
M-20260428-003王磊EMP003CQ能源运营部午餐餐线取餐正常取餐非会员11:1511:202件285.0285.5+0.5¥28.0068分查看用户信息
M-20260428-004赵敏EMP004CQ能源财务部午餐档口+餐线正常取餐会员11:2211:303件420.0447.0+27.0¥25.5094分查看用户信息
M-20260428-005陈刚EMP005CQ能源人事部早餐餐线取餐正常取餐非会员07:4807:553件390.0未复秤¥12.0085分查看用户信息
M-20260428-006刘芳EMP006CQ能源市场部午餐外卖/外带正常取餐会员11:4511:482件380.0362.0-18.0¥15.8090分查看用户信息
SP-001-001张运维EMP101CQ能源运维部午餐余量取餐余量取餐员工14:0514:061件220.0未复秤¥5.00查看用户信息
SP-001-003王保安CQ能源安保部午餐余量取餐余量取餐工作人员14:1014:111件195.0198.0+3.0¥5.00查看用户信息
M-20260428-007林教授外部单位外部访客午餐点餐取餐正常取餐外部人员12:1012:183件475.0478.0+3.0¥26.5086分查看用户信息
diff --git a/web-admin/nutrition/din-terminal.html b/web-admin/nutrition/din-terminal.html index 4c674dc..fd7769a 100644 --- a/web-admin/nutrition/din-terminal.html +++ b/web-admin/nutrition/din-terminal.html @@ -144,6 +144,167 @@ tbody tr:hover{background:#F8FBFF;} .confirm-icon svg{width:48px;height:48px;} .confirm-msg{text-align:center;font-size:var(--font-base);color:var(--text-primary);margin-bottom:8px;font-weight:500;} .confirm-sub{text-align:center;font-size:var(--font-sm);color:var(--text-secondary);} +/* ===== 设备概览大弹窗 ===== */ +.ov-modal-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1020;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;} +.ov-modal-mask.closing{animation:ovFadeOut .2s ease forwards;} +@keyframes ovFadeIn{from{opacity:0}to{opacity:1}} +@keyframes ovFadeOut{from{opacity:1}to{opacity:0}} +.ov-modal{width:1100px;max-width:94vw;height:80vh;max-height:90vh;background:#fff;border-radius:var(--radius-md);box-shadow:0 12px 40px rgba(0,0,0,.18);display:flex;flex-direction:column;overflow:hidden;animation:ovModalIn .22s ease;} +.ov-modal-mask.closing .ov-modal{animation:ovModalOut .18s ease forwards;} +@keyframes ovModalIn{from{transform:translateY(16px) scale(.98);opacity:0}to{transform:translateY(0) scale(1);opacity:1}} +@keyframes ovModalOut{from{transform:translateY(0) scale(1);opacity:1}to{transform:translateY(16px) scale(.98);opacity:0}} +.ov-modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;} +.ov-modal__title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);} +.ov-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;} +.ov-modal__close:hover{background:#f5f5f5;color:var(--text-primary);} +/* 原型演示提示条 */ +.ov-demo-tip{display:flex;align-items:center;gap:8px;background:#FFF7E6;border:1px solid #FFE58F;border-radius:var(--radius-sm);padding:8px 14px;margin:16px 24px 0;font-size:var(--font-sm);color:#8C6D1F;} +.ov-demo-tip .tip-icon{flex-shrink:0;} +/* Tab 栏 */ +.ov-tabs{display:flex;gap:4px;padding:12px 24px 0;border-bottom:1px solid var(--border);flex-shrink:0;overflow-x:auto;} +.ov-tab{padding:8px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border:1px solid transparent;border-bottom:none;border-radius:var(--radius-sm) var(--radius-sm) 0 0;white-space:nowrap;transition:all .15s;user-select:none;} +.ov-tab:hover{color:var(--primary);} +.ov-tab.active{color:var(--primary);font-weight:600;background:var(--primary-light);border-color:var(--border);position:relative;top:1px;} +.ov-modal__body{padding:20px 24px;overflow-y:auto;flex:1;} +/* 占位 Tab */ +.ov-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 0;color:var(--text-placeholder);gap:12px;} +.ov-placeholder__icon{font-size:48px;opacity:.6;} +.ov-placeholder__text{font-size:var(--font-base);} +.ov-placeholder__sub{font-size:var(--font-sm);} +/* 智能货柜可视化(参考 prod-proc-cabinet 左侧柜子样式) */ +.ov-cabinet-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:8px;} +.ov-cabinet-header select{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-sm);font-weight:600;outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;appearance:none;cursor:pointer;} +.ov-cabinet-header select:focus{border-color:var(--primary);} +.ov-cabinet-status{font-size:var(--font-sm);color:var(--text-secondary);} +.ov-cabinet-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;} +.ov-slot{border:2px solid var(--border);border-radius:var(--radius-md);padding:14px 10px;cursor:pointer;transition:all .2s;display:flex;flex-direction:column;align-items:center;gap:4px;min-height:96px;justify-content:center;position:relative;} +.ov-slot:hover{border-color:var(--primary);box-shadow:0 2px 8px rgba(24,144,255,0.15);} +.ov-slot.occupied{background:#FAFFF5;border-color:#B7EB8F;} +.ov-slot.warning{background:#FFFBE6;border-color:#FFD666;} +.ov-slot.alert{background:#FFF1F0;border-color:#FFA39E;} +.ov-slot--empty{background:#FAFAFA;border-style:dashed;} +.ov-slot--empty .ov-slot__name{color:var(--text-placeholder);font-weight:400;} +.ov-slot__badge{position:absolute;top:5px;right:5px;width:8px;height:8px;border-radius:50%;} +.ov-slot__badge.green{background:var(--success);} +.ov-slot__badge.orange{background:var(--warning);} +.ov-slot__badge.red{background:var(--danger);} +.ov-slot__id{font-size:var(--font-xs);color:var(--text-placeholder);font-weight:500;} +.ov-slot__name{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);text-align:center;line-height:1.3;} +.ov-slot__remain{font-size:var(--font-sm);font-weight:700;color:var(--primary);} +.ov-slot__expire{font-size:10px;color:var(--text-placeholder);} +.ov-cabinet-legend{display:flex;gap:16px;padding:14px 4px 0;flex-wrap:wrap;} +.ov-legend__item{display:flex;align-items:center;gap:4px;font-size:var(--font-xs);color:var(--text-secondary);} +.ov-legend__dot{width:10px;height:10px;border-radius:2px;border:1.5px solid;} +.ov-legend__dot.idle{background:#FAFAFA;border-color:var(--border);border-style:dashed;} +.ov-legend__dot.storing{background:#FAFFF5;border-color:#B7EB8F;} +.ov-legend__dot.warn{background:#FFFBE6;border-color:#FFD666;} +.ov-legend__dot.err{background:#FFF1F0;border-color:#FFA39E;} +/* 格口历史记录弹窗(叠加在设备概览之上,参考净菜进柜历史弹窗) */ +.slot-his-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1500;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;} +.slot-his-mask.closing{animation:ovFadeOut .2s ease forwards;} +.slot-his-modal{width:1000px;max-width:92vw;max-height:78vh;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;} +.slot-his-mask.closing .slot-his-modal{animation:ovModalOut .18s ease forwards;} +.slot-his-modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;} +.slot-his-modal__title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);} +.slot-his-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;} +.slot-his-modal__close:hover{background:#f5f5f5;color:var(--text-primary);} +.slot-his-modal__body{padding:20px 24px;overflow-y:auto;flex:1;} +.slot-his-modal__footer{padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;background:#FAFBFC;} +.slot-his-summary{display:flex;align-items:center;gap:16px;flex-wrap:wrap;background:#F8FBFF;border:1px solid var(--border);border-radius:var(--radius-md);padding:12px 16px;margin-bottom:16px;} +.slot-his-summary__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);color:var(--text-secondary);} +.slot-his-summary__item strong{color:var(--text-primary);font-weight:600;} +.slot-his-summary__item .label{color:var(--text-placeholder);font-size:var(--font-xs);} +.slot-his-count{display:inline-flex;align-items:center;gap:4px;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);} +.slot-his-count.in{background:#F6FFED;color:var(--success);} +.slot-his-count.out{background:#FFF7E6;color:var(--warning);} +.slot-his-table{width:100%;border-collapse:collapse;} +.slot-his-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;} +.slot-his-table tbody td{padding:10px 12px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;} +.slot-his-table tbody tr:last-child td{border-bottom:none;} +.slot-his-delta{display:inline-flex;align-items:center;gap:2px;font-weight:600;} +.slot-his-delta.pos{color:var(--success);} +.slot-his-delta.neg{color:var(--danger);} +.slot-his-pagination{display:flex;align-items:center;justify-content:flex-end;gap:6px;} +.slot-his-pagination__total{font-size:var(--font-sm);color:var(--text-secondary);margin-right:auto;} +/* ===== 餐盘柜格口扩展状态 ===== */ +.ov-slot.using{background:#F0F8FF;border-color:#91D5FF;} +.ov-slot.disabled{background:#F5F5F5;border-color:#D9D9D9;cursor:not-allowed;opacity:.75;} +.ov-slot__badge.blue{background:var(--primary);} +.ov-slot__badge.gray{background:#999;} +.ov-slot__level{font-size:10px;padding:1px 6px;border-radius:8px;background:rgba(251,191,36,.15);color:#D48806;border:1px solid rgba(251,191,36,.5);} +.ov-slot__level.vip{background:rgba(96,165,250,.15);color:#1D6FD1;border-color:rgba(96,165,250,.5);} +.ov-legend__dot.using{background:#F0F8FF;border-color:#91D5FF;} +.ov-legend__dot.disabled{background:#F5F5F5;border-color:#D9D9D9;} +/* 餐盘柜格口详情弹窗 */ +.plate-detail__grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 24px;} +.plate-detail__section{margin-bottom:20px;} +.plate-detail__section-title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);padding-bottom:8px;border-bottom:1px solid var(--border);margin-bottom:12px;display:flex;align-items:center;gap:6px;} +.plate-detail__section-title::before{content:'';display:inline-block;width:3px;height:14px;background:var(--primary);border-radius:2px;} +.plate-detail__field{display:flex;flex-direction:column;gap:2px;} +.plate-detail__label{font-size:var(--font-xs);color:var(--text-placeholder);} +.plate-detail__value{font-size:var(--font-sm);color:var(--text-primary);font-weight:500;min-height:22px;} +/* 会员选择层(格口分配) */ +.member-picker-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1600;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;} +.member-picker-mask.closing{animation:ovFadeOut .2s ease forwards;} +.member-picker{width:560px;max-width:92vw;max-height:70vh;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;} +.member-picker__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;} +.member-picker__title{font-size:var(--font-lg);font-weight:600;} +.member-picker__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;} +.member-picker__close:hover{background:#f5f5f5;color:var(--text-primary);} +.member-picker__search{padding:14px 24px 0;flex-shrink:0;} +.member-picker__search input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;} +.member-picker__search input:focus{border-color:var(--primary);} +.member-picker__body{padding:12px 24px 20px;overflow-y:auto;flex:1;} +.member-picker__item{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:8px;cursor:pointer;transition:all .15s;} +.member-picker__item:hover{border-color:var(--primary);background:#F8FBFF;} +.member-picker__item.selected{border-color:var(--primary);background:var(--primary-light);} +.member-picker__avatar{width:36px;height:36px;border-radius:50%;background:var(--primary-light);color:var(--primary);display:flex;align-items:center;justify-content:center;font-size:var(--font-sm);font-weight:600;flex-shrink:0;} +.member-picker__info{flex:1;min-width:0;} +.member-picker__name{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);} +.member-picker__meta{font-size:var(--font-xs);color:var(--text-placeholder);} +.member-picker__empty{text-align:center;padding:30px 0;color:var(--text-placeholder);font-size:var(--font-sm);} +.member-picker__footer{display:flex;justify-content:flex-end;gap:12px;padding:14px 24px;border-top:1px solid var(--border);flex-shrink:0;} +/* 确认弹窗(解绑二次确认) */ +.ov-confirm-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1600;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;} +.ov-confirm-mask.closing{animation:ovFadeOut .2s ease forwards;} +.ov-confirm{width:420px;background:#fff;border-radius:var(--radius-lg);padding:24px;animation:ovModalIn .22s ease;} +.ov-confirm__msg{text-align:center;font-size:var(--font-base);font-weight:500;margin:12px 0 4px;} +.ov-confirm__sub{text-align:center;font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;} +.ov-confirm__icon{text-align:center;font-size:40px;} +/* ===== 净菜/毛菜货柜格口离线态 ===== */ +.ov-slot.offline{background:#F5F5F5;border-color:#D9D9D9;} +.ov-slot.offline .ov-slot__name{color:var(--text-placeholder);} +.ov-legend__dot.offline{background:#F5F5F5;border-color:#D9D9D9;} +/* ===== 水电气表卡片(参考 prod-qc-daily energy-card,重布局) ===== */ +.ov-energy-card{position:relative;border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;margin-bottom:20px;background:linear-gradient(135deg,#FBFDFF,#F6FAFF);} +.ov-energy-card__entry{position:absolute;top:16px;right:16px;font-size:var(--font-xs);color:var(--primary);cursor:pointer;padding:4px 10px;border:1px solid var(--primary);border-radius:12px;transition:all .15s;background:#fff;} +.ov-energy-card__entry:hover{background:var(--primary);color:#fff;} +.ov-energy-card__header{display:flex;align-items:center;gap:10px;margin-bottom:14px;} +.ov-energy-card__icon{font-size:26px;} +.ov-energy-card__title{font-size:var(--font-lg);font-weight:700;} +.ov-energy-card__meter{font-size:var(--font-xs);color:var(--text-placeholder);font-family:monospace;} +.ov-energy-card__value{font-size:34px;font-weight:700;line-height:1.2;margin-bottom:14px;} +.ov-energy-card__value span{font-size:var(--font-sm);font-weight:400;color:var(--text-secondary);} +.ov-energy-card__rows{display:grid;grid-template-columns:repeat(4,1fr);gap:10px 24px;} +.ov-energy-card__row{display:flex;flex-direction:column;gap:2px;} +.ov-energy-card__row-label{font-size:var(--font-xs);color:var(--text-placeholder);} +.ov-energy-card__row-value{font-size:var(--font-sm);font-weight:500;color:var(--text-primary);} +.ov-energy-card__hint{margin-top:14px;padding-top:12px;border-top:1px dashed var(--border);font-size:var(--font-xs);color:var(--text-secondary);} +.ov-energy-edit{margin-top:14px;padding-top:14px;border-top:1px dashed var(--border);display:grid;grid-template-columns:repeat(3,1fr);gap:12px 24px;align-items:end;} +.ov-energy-edit .plate-detail__field input{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;} +.ov-energy-edit .plate-detail__field input:focus{border-color:var(--primary);} +.ov-energy-edit__actions{display:flex;gap:8px;justify-content:flex-end;grid-column:1/-1;} +/* 日使用量统计表格(Tab 内嵌) */ +.ov-daily-table{width:100%;border-collapse:collapse;} +.ov-daily-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;} +.ov-daily-table tbody td{padding:10px 12px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;} +.ov-daily-table tbody tr:last-child td{border-bottom:none;} +.ov-daily-table tbody tr:hover{background:#F8FBFF;} +.ov-daily-pagination{display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:14px 0 0;} +.ov-daily-pagination__total{font-size:var(--font-sm);color:var(--text-secondary);margin-right:auto;} +/* 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;} @@ -282,12 +443,12 @@ tbody tr:hover{background:#F8FBFF;} - - - - - - + + + + + +
设备编号设备名称设备类型所属餐线厂家品牌责任人网络类型IP地址/设备ID固件版本安装日期在线状态设备健康评分今日使用次数最近心跳时间运行天数累计时长(分钟)最近维护时间故障记录数操作
DEV-0011号点餐屏点餐屏海康威视张运维WiFi192.168.1.101v3.2.12024-08-15在线98分4862026-04-22 11:30:22615362,4002026-03-202故障记录 远程重启
DEV-002智能取餐柜-01智能取餐柜1号餐线海尔智家李运维有线192.168.1.102v2.8.02024-08-15在线100分3122026-04-22 11:30:18615498,7202026-04-010故障记录 远程重启
DEV-0032号点餐屏点餐屏海康威视张运维WiFi192.168.1.103v3.2.12024-10-01在线92分3982026-04-22 11:30:25568298,8002026-04-101故障记录 远程重启
DEV-004智能取餐柜-02智能取餐柜2号餐线海尔智家李运维有线192.168.1.104v2.7.32024-10-01离线0分02026-04-22 09:18:44568423,6002026-04-153故障记录 远程重启
DEV-005餐线秤台-00025餐线秤台1号餐线大华技术王运维有线DEV-00025v1.5.22022-06-10在线99分1,2802026-04-22 11:30:201,4121,016,6402026-03-010故障记录 远程重启
DEV-006体脂秤-01体脂秤华为智选张运维WiFi192.168.1.106v2.1.02023-12-01故障45分02026-04-22 10:55:0887378,5402026-02-281故障记录 远程重启
DEV-0011号点餐屏点餐屏海康威视张运维WiFi192.168.1.101v3.2.12024-08-15在线98分4862026-04-22 11:30:22615362,4002026-03-202设备概览 故障记录 远程重启
DEV-002智能取餐柜-01智能取餐柜1号餐线海尔智家李运维有线192.168.1.102v2.8.02024-08-15在线100分3122026-04-22 11:30:18615498,7202026-04-010设备概览 故障记录 远程重启
DEV-0032号点餐屏点餐屏海康威视张运维WiFi192.168.1.103v3.2.12024-10-01在线92分3982026-04-22 11:30:25568298,8002026-04-101设备概览 故障记录 远程重启
DEV-004智能取餐柜-02智能取餐柜2号餐线海尔智家李运维有线192.168.1.104v2.7.32024-10-01离线0分02026-04-22 09:18:44568423,6002026-04-153设备概览 故障记录 远程重启
DEV-005餐线秤台-00025餐线秤台1号餐线大华技术王运维有线DEV-00025v1.5.22022-06-10在线99分1,2802026-04-22 11:30:201,4121,016,6402026-03-010设备概览 故障记录 远程重启
DEV-006体脂秤-01体脂秤华为智选张运维WiFi192.168.1.106v2.1.02023-12-01故障45分02026-04-22 10:55:0887378,5402026-02-281设备概览 故障记录 远程重启
@@ -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=` +
+
+
设备概览 — ${devName}(${devCode} · ${devType})
+ +
+
+ ⚠️ + 原型演示说明:以下 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 `
${s.id}
空闲
`; + } + return `
+
+
${s.id}
+
${s.name}
+
${s.remain}
+
${s.expire}
+
`; + }).join(''); + return ` +
+
1号智能货柜
+
4.1℃ · 在线 · 已占用 7/10 格口
+
+
${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 `
${s.id}
未绑定
`; + } + const memberHtml=s.member + ?`
${s.member.name}
${s.member.level}
` + :`
停用
`; + return `
+ ${meta.badge?`
`:''} +
${s.id}
+ ${memberHtml} +
${meta.text}
+
`; + }).join(''); + return ` +
+
1号餐盘柜 · 每格口配专属消毒餐盘供固定会员使用
+
36.8℃(消毒保温)· 在线 · 已绑定 ${boundCount}/${totalCount} 格口
+
+
${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=`
+
餐盘格口详情 — ${s.id}
+
${html}
+ +
`; + 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=`
+
格口分配 — 选择会员(${PLATE_SLOTS[idx].id})
+ +
+ +
`; + 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 `
${s.id}
空闲
`; + } + 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 ` +
+
${cab.name}(${cab.code})· ${cab.type}
+
${cab.status} · 已存放 ${occupied}/${cab.slots.length} 格口
+
+
${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+=`
存取历史
+ + ${rows.map(r=>{ + const isPos=r.type==='存入'; + const delta=Math.abs(r.after-r.before).toFixed(1); + const typeHtml=isPos?'存入':'取出'; + return ``; + }).join('')} +
操作时间操作类型操作前(kg)操作后(kg)余量变化操作人关联单号备注
${r.time}${typeHtml}${r.before.toFixed(1)}${r.after.toFixed(1)}${isPos?'+':'-'}${delta} ${isPos?'↑':'↓'}${r.op}${r.order}${r.remark||'—'}
`; + } + const mask=document.createElement('div'); + mask.className='slot-his-mask'; + mask.id='stockSlotMask'; + mask.innerHTML=`
+
格口存放详情 — ${s.id}(${cab.name})
+
${html}
+
`; + 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.icon}${e.title}表号:${e.meter}
+
${e.remain} ${unit}
+
+
今日用量${e.todayUse} ${unit}
+
今日费用¥${e.todayCost.toFixed(1)}
+
状态正常
+
${e.rate}${e.rateVal}
+
+
${e.hint}
+ +
+
日使用量统计
+
`; +} +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='
'; + pageRows.forEach(r=>{ + html+=``; + }); + html+='
记录日期用量('+unit+')费用(元)峰值时段采集方式操作
${r.date}${r.use}¥${r.cost.toFixed(1)}${r.peak}${r.op}详情
'; + html+=`
共 ${total} 条`; + html+=``; + for(let i=1;i<=totalPages;i++){ + 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=`
+
格口历史记录 — ${d.slot}
+
${summary}
+ +
`; + 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='
'; + 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+=``; + }); + html+='
操作时间存放物品操作类型操作前(kg)操作后(kg)余量变化操作人关联单号备注
${r.time}${r.name}${typeHtml}${r.before.toFixed(1)}${r.after.toFixed(1)}${deltaHtml}${r.op}${orderHtml}${r.remark||'—'}
'; + wrap.innerHTML=html; + if(footer){ + let pg=`
共 ${total} 条`; + pg+=``; + for(let i=1;i<=totalPages;i++){ + pg+=``; + } + 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); +} \ No newline at end of file diff --git a/web-admin/nutrition/din-user-mgmt.html b/web-admin/nutrition/din-user-mgmt.html index 21519b3..131c5aa 100644 --- a/web-admin/nutrition/din-user-mgmt.html +++ b/web-admin/nutrition/din-user-mgmt.html @@ -544,12 +544,12 @@ tbody td.td-actions{overflow:visible;} 会员卡号姓名用户编号会员等级当前积分累计消费(元)本月消费开通日期有效期至最近活跃会员状态操作 - MB-202401-001张伟EMP001钻石8,62812,860¥1,2862024-01-152027-01-142026-04-28 11:32正常编辑 详情 人脸采集 - MB-202405-008李娜EMP002金卡3,2864,820¥6822024-05-082027-05-072026-04-28 12:08正常编辑 详情 人脸采集 - MB-202508-015赵敏EMP004银卡1,4202,180¥4862025-08-122027-08-112026-04-28 11:30正常编辑 详情 人脸采集 - MB-202510-026刘芳EMP006银卡9861,520¥3202025-10-202027-10-192026-04-28 11:48正常编辑 详情 人脸采集 - MB-202312-003孙磊EMP010金卡5,1207,860¥02023-12-082026-12-072026-04-15 12:20即将到期编辑 详情 人脸采集 - MB-202402-005周明EMP008钻石12,48616,820¥02024-02-102027-02-092026-03-28 12:15休眠编辑 详情 人脸采集 + MB-202401-001张伟EMP001钻石8,62812,860¥1,2862024-01-152027-01-142026-04-28 11:32正常编辑 详情 人脸采集 会员权益 + MB-202405-008李娜EMP002金卡3,2864,820¥6822024-05-082027-05-072026-04-28 12:08正常编辑 详情 人脸采集 会员权益 + MB-202508-015赵敏EMP004银卡1,4202,180¥4862025-08-122027-08-112026-04-28 11:30正常编辑 详情 人脸采集 会员权益 + MB-202510-026刘芳EMP006银卡9861,520¥3202025-10-202027-10-192026-04-28 11:48正常编辑 详情 人脸采集 会员权益 + MB-202312-003孙磊EMP010金卡5,1207,860¥02023-12-082026-12-072026-04-15 12:20即将到期编辑 详情 人脸采集 会员权益 + MB-202402-005周明EMP008钻石12,48616,820¥02024-02-102027-02-092026-03-28 12:15休眠编辑 详情 人脸采集 会员权益 @@ -1059,8 +1059,72 @@ document.querySelectorAll('.tab-panel')[2].querySelectorAll('tbody tr').forEach( // 人脸采集 var faceLink = row.querySelector('[data-action="face"]'); if (faceLink) faceLink.addEventListener('click', function() { showFaceModal(name); }); + // 会员权益 + var benefitsLink = row.querySelector('[data-action="benefits"]'); + if (benefitsLink) benefitsLink.addEventListener('click', function() { showVipBenefitsModal(name, tds); }); }); +/* ========== 会员权益弹窗(Tab3) ========== */ +/* 交互说明:点击操作列「会员权益」弹窗展示该会员的权益概要与专属餐盘柜绑定信息。 + 会员权益体系尚在规划中,当前已明确权益为「专属餐盘柜」,此处回显柜子/格口/绑定时间/最后使用状态/累计使用次数。 */ +// 模拟数据:会员专属餐盘柜绑定信息(未配置的会员显示空态) +var vipTrayLockerMap = { + '张伟': { locker: 'A区-01号柜', cell: '12号格口', bindTime: '2024-01-20 10:32', lastUseTime: '2026-04-28 11:32', lastUseStatus: '已归还', useCount: 1286 }, + '李娜': { locker: 'A区-01号柜', cell: '08号格口', bindTime: '2024-05-15 09:18', lastUseTime: '2026-04-28 12:08', lastUseStatus: '使用中', useCount: 862 }, + '赵敏': { locker: 'B区-03号柜', cell: '21号格口', bindTime: '2025-08-20 14:05', lastUseTime: '2026-04-28 11:30', lastUseStatus: '已归还', useCount: 316 }, + '周明': { locker: 'B区-03号柜', cell: '05号格口', bindTime: '2024-02-18 16:47', lastUseTime: '2026-03-28 12:15', lastUseStatus: '已归还', useCount: 2048 } +}; +// 等级权益文案映射(与等级配置 Tab 保持一致) +var vipLevelBenefitsMap = { + '钻石': '专属营养师、VIP定制菜单、季度体检券、专属餐盘柜、专属取餐窗口、2.0x 积分倍数、9.0折 点餐折扣', + '金卡': '生日券 ¥30、AI营养周报、专属客服、专属餐盘柜、优先取餐、1.5x 积分倍数、9.5折 点餐折扣', + '银卡': '生日券 ¥10、月度营养报告、专属餐盘柜、1.2x 积分倍数、9.8折 点餐折扣', + '普通': '基础点餐功能、专属餐盘柜' +}; + +function showVipBenefitsModal(name, tds) { + var levelText = tds[3].querySelector('.tag').textContent; + var cardNo = tds[0].textContent; + var locker = vipTrayLockerMap[name]; + + // 专属餐盘柜信息区:已绑定展示明细,未绑定展示空态 + var lockerHtml; + if (locker) { + var statusTag = locker.lastUseStatus === '使用中' ? 'tag-blue' : 'tag-green'; + lockerHtml = '' + + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
所属柜子格口绑定时间最后使用时间最后使用状态累计使用次数
' + locker.locker + '' + locker.cell + '' + locker.bindTime + '' + locker.lastUseTime + '' + locker.lastUseStatus + '' + locker.useCount.toLocaleString() + ' 次
'; + } else { + lockerHtml = '' + + '
' + + '
🗄️
' + + '
暂未绑定专属餐盘柜,可在餐盘柜管理中为该会员分配格口
' + + '
'; + } + + var html = '' + + '
' + + ' 💡 会员权益体系持续规划中,当前已上线权益:专属餐盘柜' + + '
' + + '
当前等级权益(' + levelText + '会员)
' + + '
' + + (vipLevelBenefitsMap[levelText] || vipLevelBenefitsMap['普通']) + + '
' + + '
🍽️ 专属餐盘柜信息
' + + lockerHtml; + + Modal.open('会员权益 — ' + name + '(' + cardNo + ')', html, '', ''); +} + /* ========== 编辑会员信息弹窗(Tab3) ========== */ function openVipEditModal(row) { var tds = row.querySelectorAll('td'); diff --git a/web-admin/nutrition/doc/CHANGELOG.md b/web-admin/nutrition/doc/CHANGELOG.md index d2aaeca..0c3b255 100644 --- a/web-admin/nutrition/doc/CHANGELOG.md +++ b/web-admin/nutrition/doc/CHANGELOG.md @@ -1,5 +1,40 @@ # 营养用餐管理端 CHANGELOG +## [2026-08-18] feat: 订餐终端设备概览大弹窗 + 会员权益体系 + 采购库存抵扣 + 追溯多锅次分支 + +### 变更一:订餐终端设备概览大弹窗(`din-terminal.html`) +- 新增「设备概览」大弹窗(1100px 宽·Tab 结构),按设备类型展示对应概览内容 +- 智能货柜可视化:格口网格(空闲/存放/临期预警/异常四态)+ 图例 + 格口绑定会员展示 +- 新增格口历史记录弹窗(叠加层):存取历史汇总 + 明细列表,参考净菜进柜历史弹窗样式 +- 支持格口分配/解绑操作,异常停用格口不可操作 + +### 变更二:会员权益概览弹窗(`din-user-mgmt.html`) +- 新增「会员权益」弹窗:展示会员权益概要与专属餐盘柜信息 +- 会员等级配置(如季度体检券、专属取餐窗口、专属餐盘柜、AI 营养周报等权益明细) +- 未绑定餐盘柜显示空态,格口使用状态/累计使用次数展示 + +### 变更三:采购单库存抵扣交互(`farm-purchase.html`) +- 待确认采购单新增库存列与「一键抵扣」:库存充足建议整行移除、部分库存可部分抵扣采购量 +- 抵扣提示动态化(检测到有库存的行批量应用),克重缺失/单价待补充时留空提示 +- 隐藏/移除抵扣行的按钮交互 + +### 变更四:餐品食材选择器可搜索下拉(`din-dish-own.html`) +- 食材选择器改造为可搜索下拉面板:分类过滤·关键词过滤·去重·候选列表渲染 +- 展开时自动聚焦搜索框,未找到匹配时显示空态;关闭面板状态记忆便于重新展开 + +### 变更五:取餐记录重量偏差筛选与详情(`din-order-pickup.html`) +- 筛选栏新增「重量偏差范围」(最小值/最大值,单位克)筛选,餐线秤记录不参与该筛选 +- 取餐详情展示结算重量·实际重量·人员类别(员工/会员)·取餐方式/类型等字段 + +### 变更六:异常结果告警详情(`ano-result.html`) +- 异常详情新增设备告警信息:人脸识别累计失败/连续失败(阈值触发)·档口秤/餐线秤告警 +- 异常记录条数与触发告警次数展示 + +### 变更七:追溯时间轴多锅次分支(`trace-timeline-dish.html` / `trace-timeline-ingredient.html`) +- 烹制环节支持多锅次分支展示(分支 A/B/C 多条 COOK 记录并行) +- 时间轴节点补充净菜全流程(农残检测·净菜加工/组配/入库/进柜/内供·干货入库/存储)·上餐监控·余量处置 +- 节点详情展示操作人·时间·监控摄像头(如后厨-CAM10),未显式声明时不重复展示 + ## [2026-08-14] optimize: 餐品/食材档案检索与录入体系强化 + 进货明细金额补全 ### 变更一:餐品库高级搜索与筛选汇总(`din-dish-all.html` / `din-dish-own.html`) diff --git a/web-admin/nutrition/farm-purchase.html b/web-admin/nutrition/farm-purchase.html index 55b9d7a..c389be2 100644 --- a/web-admin/nutrition/farm-purchase.html +++ b/web-admin/nutrition/farm-purchase.html @@ -131,6 +131,8 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} .items-table input,.items-table select{height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-xs);outline:none;width:100%;} .items-table input:focus,.items-table select:focus{border-color:var(--primary);} .items-table .btn-remove{color:var(--danger);cursor:pointer;font-size:16px;border:none;background:none;padding:4px;} +/* 库存抵扣列:仅在"立即确认"(待确认订单)入口显示,其余场景整列隐藏 */ +.items-table.hide-stock .stock-col{display:none;} /* 规格输入组(表格内):克重 input + 单位 select 连在一起 */ .items-table .spec-input-group{display:flex;align-items:stretch;height:30px;} .items-table .spec-input-group input{flex:1;border-radius:4px 0 0 4px;border-right:none;width:auto;} @@ -309,13 +311,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-LY01-VG-XLHU-080K-20260416-001西兰花蔬菜类80500g/袋¥4.5¥360
ST-EX-LY01-VG-QCAI-120K-20260416-002芹菜蔬菜类120500g/袋¥2.5¥300
ST-EX-LY01-VG-XLHU-080K-20260416-001西兰花蔬菜类80500g/袋40 kg¥4.5¥360
ST-EX-LY01-VG-QCAI-120K-20260416-002芹菜蔬菜类120500g/袋60 kg¥2.5¥300
- + @@ -335,14 +337,14 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - - + + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-LY01-VG-BOCA-050K-20260415-002菠菜蔬菜类50500g/袋¥2.5¥125
ST-EX-LY01-VG-BCAI-080K-20260415-003白菜蔬菜类80500g/袋¥1.8¥144
ST-EX-LY01-VG-HLUB-120K-20260415-004胡萝卜蔬菜类120500g/袋¥1.8¥216
ST-EX-LY01-VG-BOCA-050K-20260415-002菠菜蔬菜类50500g/袋25 kg¥2.5¥125
ST-EX-LY01-VG-BCAI-080K-20260415-003白菜蔬菜类80500g/袋40 kg¥1.8¥144
ST-EX-LY01-VG-HLUB-120K-20260415-004胡萝卜蔬菜类120500g/袋60 kg¥1.8¥216
- + @@ -362,13 +364,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-XR01-MT-ZLRJ-080K-20260415-005猪里脊肉禽类80500g/袋¥28¥2,240需冷链运输
ST-EX-XR01-MT-ZWUA-240K-20260415-006猪五花肉禽类240500g/袋¥26¥6,240
ST-EX-XR01-MT-ZLRJ-080K-20260415-005猪里脊肉禽类80500g/袋40 kg¥28¥2,240需冷链运输
ST-EX-XR01-MT-ZWUA-240K-20260415-006猪五花肉禽类240500g/袋120 kg¥26¥6,240
- + @@ -388,14 +390,14 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - - + + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-HS01-SF-CAYO-060K-20260414-001草鱼水产类60500g/袋¥18¥1,08060活鲜运输
ST-EX-HS01-SF-JIYU-080K-20260414-002鲫鱼水产类80500g/袋¥16¥1,28080
ST-EX-HS01-SF-XIAA-050K-20260414-003水产类50500g/袋¥30¥1,500冰鲜
ST-EX-HS01-SF-CAYO-060K-20260414-001草鱼水产类60500g/袋30 kg¥18¥1,08060活鲜运输
ST-EX-HS01-SF-JIYU-080K-20260414-002鲫鱼水产类80500g/袋40 kg¥16¥1,28080
ST-EX-HS01-SF-XIAA-050K-20260414-003水产类50500g/袋25 kg¥30¥1,500冰鲜
- + @@ -415,13 +417,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-LY02-DG-DAMI-1000-20260413-001大米粮油类100025kg/袋¥3.5¥3,500东北五常大米
ST-EX-LY02-DG-HSYO-168K-20260413-002花生油粮油类1685kg/桶¥16¥2,688
ST-EX-LY02-DG-DAMI-1000-20260413-001大米粮油类100025kg/袋25000 kg¥3.5¥3,500东北五常大米
ST-EX-LY02-DG-HSYO-168K-20260413-002花生油粮油类1685kg/桶840 kg¥16¥2,688
- + @@ -441,13 +443,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} - + - - + +
溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
ST-EX-LY01-VG-XLHU-080K-20260416-007西兰花蔬菜类80500g/袋待补充
ST-EX-LY01-VG-QCAI-120K-20260416-008芹菜蔬菜类120500g/袋待补充
ST-EX-LY01-VG-XLHU-080K-20260416-007西兰花蔬菜类80500g/袋40 kg待补充
ST-EX-LY01-VG-QCAI-120K-20260416-008芹菜蔬菜类120500g/袋60 kg待补充
- + @@ -477,7 +479,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
@@ -494,16 +496,19 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
食材明细 - + + + +
- +
- +
溯源码食材名称食材分类采购数量包装规格单价(元)小计金额备注
溯源码食材名称食材分类采购数量包装规格采购重量当前库存单价(元)小计金额备注
- 0 种食材 + 0 种食材 采购总金额:¥0
@@ -535,7 +540,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
食材明细
- +
序号溯源码食材名称食材分类采购数量包装规格单价小计金额验收数量备注
序号溯源码食材名称食材分类采购数量包装规格采购重量单价小计金额验收数量备注
@@ -697,6 +702,62 @@ function parseSpecText(specText){ } return {gram:'',unit:''}; } +/* ===== 食材当前库存模拟数据(单位 kg,用于待确认采购单的库存抵扣提醒) ===== */ +var ingredientStock = {'西兰花':1, '芹菜':0.5}; + +/* 构建库存抵扣单元格内容:库存充足→建议移除;部分库存→可一键抵扣;无库存→灰字 */ +function buildStockCellHtml(name, qty, gram){ + var stock = ingredientStock[name] || 0; + var demandKg = (qty && gram) ? qty * gram / 1000 : 0; + if (stock <= 0) { + return '0 kg'; + } + if (demandKg > 0 && stock >= demandKg) { + return '库存充足 移除'; + } + return '' + stock + ' kg' + + '
需求 ' + demandKg + ' kg
' + + '按库存抵扣'; +} +/* 单行按库存抵扣:新数量 = ⌈(需求重量 − 库存) ÷ 单位克重⌉,不足一袋则整行移除 */ +function applyDeduct(tr){ + var qtyEl = tr.querySelector('.item-qty'); + var gramEl = tr.querySelector('.item-gram'); + var qty = parseFloat(qtyEl.value) || 0; + var gram = parseFloat(gramEl.value) || 0; + var name = tr.querySelector('select').value; + var stock = ingredientStock[name] || 0; + if (!qty || !gram || stock <= 0) return; + if (!tr.dataset.origQty) tr.dataset.origQty = qty; + var demandKg = qty * gram / 1000; + var newQty = Math.ceil((demandKg - stock) * 1000 / gram); + if (newQty <= 0) { + tr.remove(); + recalcTotal(); + return; + } + qtyEl.value = newQty; + calcRow(qtyEl); + var stockEl = tr.querySelector('.item-stock'); + if (stockEl) stockEl.innerHTML = '已抵扣 ' + stock + ' kg'; + var note = document.createElement('div'); + note.style.cssText = 'font-size:10px;color:var(--warning);margin-top:2px;'; + note.textContent = '已由 ' + tr.dataset.origQty + ' 抵扣调整为 ' + newQty; + qtyEl.parentElement.appendChild(note); +} +function deductStock(el){ applyDeduct(el.closest('tr')); } +/* 一键按库存抵扣:对所有有库存的行批量应用 */ +function deductAllStock(){ + var rows = Array.prototype.slice.call(document.getElementById('itemsBody').rows); + rows.forEach(applyDeduct); + recalcTotal(); +} +/* 计算采购重量:采购数量 × 包装规格克重,统一换算为 kg 展示 */ +function calcPurchaseWeight(qty, specText){ + var p = parseSpecText(specText); + if(!qty || !p.gram) return '—'; + return (qty * p.gram / 1000).toLocaleString() + ' kg'; +} /* ===== 模拟数据 ===== */ var orders = [ @@ -797,6 +858,9 @@ function openCreateModal() { document.getElementById('contactInfoBar').style.display = 'none'; document.getElementById('confirmHintBar').style.display = 'none'; setSubmitBtnText('提交采购单'); + document.getElementById('itemsTable').classList.add('hide-stock'); + document.getElementById('btnDeductAll').style.display = 'none'; + document.getElementById('confirmStockHint').textContent = ''; document.getElementById('itemsBody').innerHTML = ''; addItemRow(); recalcTotal(); @@ -813,12 +877,19 @@ function openEditModal(idx) { showSupplierContact(); document.getElementById('confirmHintBar').style.display = 'none'; setSubmitBtnText('提交采购单'); + /* 普通新建/编辑模式:隐藏库存抵扣列与一键抵扣按钮 */ + document.getElementById('itemsTable').classList.add('hide-stock'); + document.getElementById('btnDeductAll').style.display = 'none'; + document.getElementById('confirmStockHint').textContent = ''; var tbody = document.getElementById('itemsBody'); tbody.innerHTML = ''; o.items.forEach(function(it) { var specParts = parseSpecText(it.spec); /* 待确认订单:单价为 null 时空值回填 + 橙色边框提醒 */ var priceVal = (it.price === null || it.price === undefined) ? '' : it.price; + /* 采购重量回填:数量 × 克重换算为 kg,克重缺失时留空 */ + var specP = parseSpecText(it.spec); + var weightVal = (it.qty && specP.gram) ? (it.qty * specP.gram / 1000) : ''; var subtotalText = (it.subtotal === '—' || !it.subtotal) ? '¥0' : it.subtotal; var tr = document.createElement('tr'); tr.innerHTML = '' @@ -826,6 +897,8 @@ function openEditModal(idx) { +'' +'' +'
' + +'' + +''+buildStockCellHtml(it.name, it.qty, specParts.gram)+'' +'' +''+subtotalText+'' +'' @@ -847,6 +920,8 @@ function addItemRow() { +'' +'' +'
' + +'' + +'' +'' +'¥0' +'' @@ -868,8 +943,22 @@ function calcRow(el) { var price = parseFloat(tr.querySelector('.item-price').value) || 0; var sub = qty * price; tr.querySelector('.item-subtotal').textContent = '¥' + sub.toLocaleString(); + /* 同步刷新采购重量:采购数量 × 包装规格克重 */ + var gram = parseFloat(tr.querySelector('.item-gram').value) || 0; + var weightEl = tr.querySelector('.item-weight-input'); + if (weightEl) weightEl.value = (qty && gram) ? Math.round(qty * gram / 100) / 10 : ''; recalcTotal(); } +/* 反向联动:手动输入采购重量(kg) → 换算采购数量 = 重量×1000÷克重,四舍五入取整袋 */ +function calcFromWeight(el) { + var tr = el.closest('tr'); + var weight = parseFloat(el.value) || 0; + var gram = parseFloat(tr.querySelector('.item-gram').value) || 0; + if (weight > 0 && gram > 0) { + tr.querySelector('.item-qty').value = Math.round(weight * 1000 / gram); + } + calcRow(el); +} /* 汇总总金额 */ function recalcTotal() { var rows = document.getElementById('itemsBody').rows; @@ -881,6 +970,15 @@ function recalcTotal() { } document.getElementById('itemCount').textContent = rows.length; document.getElementById('totalAmount').textContent = '¥' + total.toLocaleString(); + /* 汇总已抵扣重量:Σ(原始数量 − 当前数量) × 单位克重 */ + var deductKg = 0; + for (var j = 0; j < rows.length; j++) { + if (!rows[j].dataset.origQty) continue; + var g = parseFloat(rows[j].querySelector('.item-gram').value) || 0; + var curQty = parseFloat(rows[j].querySelector('.item-qty').value) || 0; + deductKg += (parseFloat(rows[j].dataset.origQty) - curQty) * g / 1000; + } + document.getElementById('stockDeductInfo').textContent = deductKg > 0 ? '已抵扣 ' + deductKg + ' kg' : ''; } /* ===== 详情抽屉 ===== */ @@ -924,7 +1022,7 @@ function showDetail(idx) { var priceHtml = (it.price === null || it.price === '' || it.price === undefined) ? '待补充' : '¥'+it.price; - tr.innerHTML = ''+(i+1)+''+it.trace+''+it.name+''+it.cat+''+it.qty+''+it.spec+''+priceHtml+''+(it.subtotal||'—')+''+(it.acceptedQty||'—')+''+it.remark+''; + tr.innerHTML = ''+(i+1)+''+it.trace+''+it.name+''+it.cat+''+it.qty+''+it.spec+''+calcPurchaseWeight(it.qty, it.spec)+''+priceHtml+''+(it.subtotal||'—')+''+(it.acceptedQty||'—')+''+it.remark+''; tbody.appendChild(tr); }); /* 配送信息(待收货/已完成显示) */ @@ -986,6 +1084,14 @@ function confirmOrder(idx) { openEditModal(idx); document.getElementById('confirmHintBar').style.display = 'flex'; setSubmitBtnText('确认并提交采购单'); + /* 库存抵扣:检测有库存的食材数量,动态提示并展示库存列/一键抵扣按钮 */ + var o = orders[idx]; + var stockCount = o.items.filter(function(it){ return (ingredientStock[it.name] || 0) > 0; }).length; + document.getElementById('confirmStockHint').textContent = stockCount > 0 + ? ';检测到 ' + stockCount + ' 种食材有可用库存,可抵扣部分采购量' + : ''; + document.getElementById('itemsTable').classList.remove('hide-stock'); + document.getElementById('btnDeductAll').style.display = stockCount > 0 ? 'inline-flex' : 'none'; } /* ===== 取消订单弹窗 ===== */ diff --git a/web-admin/nutrition/trace-timeline-dish.html b/web-admin/nutrition/trace-timeline-dish.html index d79c043..f0956d8 100644 --- a/web-admin/nutrition/trace-timeline-dish.html +++ b/web-admin/nutrition/trace-timeline-dish.html @@ -243,7 +243,7 @@ var INGREDIENTS = [ {stage:'净菜内供',time:'2026-04-22 09:30',title:'净菜领用出库(3条分支)',trace:'NC-OUT-20260422-001',detail:'分支A:切丁5kg → 后厨A组
分支B:切片3kg → 后厨B组
分支C:切条2kg → 后厨C组',badge:'x3',status:'ok',video:['净菜内供','2026-04-22 09:30','出库口-CAM08','00:02:00']}, {stage:'净菜进柜',time:'2026-04-22 09:45',title:'后厨冷柜暂存(2条分支)',trace:'KC-IN-20260422-001',detail:'分支A:切丁5kg → 后厨冷柜KC-01
分支B:切片3kg → 后厨冷柜KC-02',badge:'x2',status:'ok',video:['净菜进柜','2026-04-22 09:45','后厨柜-CAM08','00:01:20']}, {stage:'净菜组配',time:'2026-04-22 10:20',title:'菜品组配(2条分支)',trace:'CMB-20260422-001',detail:'分支A:CMB-001 宫保鸡丁 5.0kg · 李组配
分支B:CMB-006 辣子鸡 3.5kg · 张组配',badge:'x2',status:'ok',video:['净菜组配','2026-04-22 10:20','组配间-CAM09','00:02:40']}, - {stage:'按单烹制',time:'2026-04-22 11:00 ~ 11:35',title:'烹制任务 · 多锅次烹制(3条分支)',trace:'COOK-20260422-001',detail:'分支A:COOK-001(宫保鸡丁)张厨师 2锅
分支B:COOK-009(辣子鸡)王厨师 1锅
分支C:COOK-015(青椒鸡丁)李厨师 1锅',badge:'x3',status:'ok',video:['按单烹制','2026-04-22 11:00','后厨-CAM10','00:04:20']}, + {stage:'按单烹制',time:'2026-04-22 11:00 ~ 11:35',operator:'张厨师 等3人',title:'烹制任务 · 多锅次烹制(3条分支)',trace:'COOK-20260422-001',detail:'分支A:COOK-001(宫保鸡丁)张厨师 2锅
分支B:COOK-009(辣子鸡)王厨师 1锅
分支C:COOK-015(青椒鸡丁)李厨师 1锅',badge:'x3',status:'ok',video:['按单烹制','2026-04-22 11:00','后厨-CAM10','00:04:20']}, {stage:'上餐监控',time:'2026-04-22 11:30 ~ 12:00',title:'上餐批次 · 多餐线分发(3条分支)',trace:'SERVE-20260422-001',detail:'分支A:SERVE-001 → A区中餐线 6.8kg
分支B:SERVE-007 → B区中餐线 1.4kg
分支C:SERVE-015 → C区中餐线 2.1kg',badge:'x3',status:'ok',video:['上餐监控','2026-04-22 11:30','餐线-CAM11','00:03:05']}, {stage:'余量处置',time:'2026-04-22 13:00 ~ 13:20',title:'余量处置 · 多餐线余量登记(3条分支)',trace:'DISP-20260422-001',detail:'分支A:余量0.3kg → 冷藏留用
分支B:余量0.1kg → 废弃销毁
分支C:余量0.2kg → 废弃销毁
经办人:李处置',badge:'x3',status:'ok',video:['余量处置','2026-04-22 13:00','余量区-CAM12','00:01:30']}, {stage:'营养检验',time:'2026-04-22 13:30',title:'营养检测 · 成品抽检',trace:'NUT-20260422-001',detail:'COOK-001(宫保鸡丁)检测单号:NUT-20260422-001
热量:186kcal/100g · 蛋白质:18.2g · 脂肪:9.4g · 碳水:8.1g
检测结论:达标',badge:'',status:'ok',video:['营养检验','2026-04-22 13:30','检测室-CAM13','00:02:15']}, @@ -290,6 +290,15 @@ var INGREDIENTS = [ var INGREDIENT_EMOJIS = ['🥩','🥜','🌶️','🧄']; +/* 环节默认操作人映射(节点未显式声明 operator 时使用) */ +var STAGE_OPERATORS = { + '采供入库':'赵采购','卫生检测':'陈检测','毛菜入库':'王仓管','毛菜存储':'系统监控', + '净菜加工':'刘加工','净菜入库':'王仓管','净菜内供':'王仓管','净菜进柜':'王仓管', + '净菜组配':'李组配','按单烹制':'张厨师','上餐监控':'系统记录','余量处置':'李处置', + '营养检验':'周营养师','用户取餐':'系统记录','种植采收':'孙农技','农残检测':'陈检测', + '干货入库':'王仓管','干货存储':'系统监控','预处理':'刘加工' +}; + /* 当前状态 */ var currentIngIdx = 0; var currentNodeIdx = 0; @@ -321,13 +330,16 @@ function renderNodeDetail(node) { var iconBg = NODE_ICON_BG[stage] || '#F5F5F5'; var iconColor = NODE_COLORS[stage] || '#999'; var icon = NODE_ICONS[stage] || '●'; + var operator = node.operator || STAGE_OPERATORS[stage] || '—'; + /* 详情中已含操作人(经办人/采收人/厨师等叫法)时不重复展示 */ + var detailHtml = node.detail.indexOf(operator.split(' ')[0]) === -1 ? '操作人:' + operator + '
' + node.detail : node.detail; var html = '
'; html += '
' + icon + '
'; html += '
'; - html += '
' + icon + ' ' + stage + '' + node.time + '
'; + html += '
' + icon + ' ' + stage + '
'; html += '
' + node.title + '
'; - html += '
溯源码:' + node.trace + '
'; - html += '
' + node.detail + '
'; + html += '
时间:' + node.time + ' · 溯源码:' + node.trace + '
'; + html += '
' + detailHtml + '
'; if (node.video) { html += '
🎬 查看视频
'; } diff --git a/web-admin/nutrition/trace-timeline-ingredient.html b/web-admin/nutrition/trace-timeline-ingredient.html index d779d68..b153dbd 100644 --- a/web-admin/nutrition/trace-timeline-ingredient.html +++ b/web-admin/nutrition/trace-timeline-ingredient.html @@ -218,13 +218,22 @@ var NODES = [ {stage:'净菜内供',time:'2026-04-22 09:30',title:'净菜领用出库(3条分支)',trace:'NC-OUT-20260422-001',detail:'分支A:切丁5kg → 后厨A组
分支B:切片3kg → 后厨B组
分支C:切条2kg → 后厨C组',badge:'x3',status:'ok',video:['净菜内供','2026-04-22 09:30','出库口-CAM08','00:02:00']}, {stage:'净菜进柜',time:'2026-04-22 09:45',title:'后厨冷柜暂存(2条分支)',trace:'KC-IN-20260422-001',detail:'分支A:切丁5kg → 后厨冷柜KC-01
分支B:切片3kg → 后厨冷柜KC-02',badge:'x2',status:'ok',video:['净菜进柜','2026-04-22 09:45','后厨柜-CAM08','00:01:20']}, {stage:'净菜组配',time:'2026-04-22 10:20',title:'菜品组配(2条分支)',trace:'CMB-20260422-001',detail:'分支A:CMB-001 宫保鸡丁 5.0kg · 李组配
分支B:CMB-006 辣子鸡 3.5kg · 张组配',badge:'x2',status:'ok',video:['净菜组配','2026-04-22 10:20','组配间-CAM09','00:02:40']}, - {stage:'按单烹制',time:'2026-04-22 11:00 ~ 11:35',title:'烹制任务 · 多锅次烹制(3条分支)',trace:'COOK-20260422-001',detail:'分支A:COOK-001(宫保鸡丁)张厨师 2锅
分支B:COOK-009(辣子鸡)王厨师 1锅
分支C:COOK-015(青椒鸡丁)李厨师 1锅',badge:'x3',status:'ok',video:['按单烹制','2026-04-22 11:00','后厨-CAM10','00:04:20']}, + {stage:'按单烹制',time:'2026-04-22 11:00 ~ 11:35',operator:'张厨师 等3人',title:'烹制任务 · 多锅次烹制(3条分支)',trace:'COOK-20260422-001',detail:'分支A:COOK-001(宫保鸡丁)张厨师 2锅
分支B:COOK-009(辣子鸡)王厨师 1锅
分支C:COOK-015(青椒鸡丁)李厨师 1锅',badge:'x3',status:'ok',video:['按单烹制','2026-04-22 11:00','后厨-CAM10','00:04:20']}, {stage:'上餐监控',time:'2026-04-22 11:30 ~ 12:00',title:'上餐批次 · 多餐线分发(3条分支)',trace:'SERVE-20260422-001',detail:'分支A:SERVE-001 → A区中餐线 6.8kg
分支B:SERVE-007 → B区中餐线 1.4kg
分支C:SERVE-015 → C区中餐线 2.1kg',badge:'x3',status:'ok',video:['上餐监控','2026-04-22 11:30','餐线-CAM11','00:03:05']}, {stage:'余量处置',time:'2026-04-22 13:00 ~ 13:20',title:'余量处置 · 多餐线余量登记(3条分支)',trace:'DISP-20260422-001',detail:'分支A:余量0.3kg → 冷藏留用
分支B:余量0.1kg → 废弃销毁
分支C:余量0.2kg → 废弃销毁
经办人:李处置',badge:'x3',status:'ok',video:['余量处置','2026-04-22 13:00','余量区-CAM12','00:01:30']}, {stage:'营养检验',time:'2026-04-22 13:30',title:'营养检测 · 成品抽检',trace:'NUT-20260422-001',detail:'COOK-001(宫保鸡丁)检测单号:NUT-20260422-001
热量:186kcal/100g · 蛋白质:18.2g · 脂肪:9.4g · 碳水:8.1g
检测结论:达标',badge:'',status:'ok',video:['营养检验','2026-04-22 13:30','检测室-CAM13','00:02:15']}, {stage:'用户取餐',time:'2026-04-22 11:40 ~ 12:30',title:'关联订单食材映射',trace:'ORD-20260422-021',detail:'关联订单:SO20260422021#1、SO20260422087#2、SO20260422103#1
用于召回时可直接反查受影响订单范围',badge:'',status:'ok',video:['用户取餐','2026-04-22 11:40','取餐口-CAM14','00:01:50']} ]; +/* 环节默认操作人映射(节点未显式声明 operator 时使用) */ +var STAGE_OPERATORS = { + '采供入库':'赵采购','卫生检测':'陈检测','毛菜入库':'王仓管','毛菜存储':'系统监控', + '净菜加工':'刘加工','净菜入库':'王仓管','净菜内供':'王仓管','净菜进柜':'王仓管', + '净菜组配':'李组配','按单烹制':'张厨师','上餐监控':'系统记录','余量处置':'李处置', + '营养检验':'周营养师','用户取餐':'系统记录','种植采收':'孙农技','农残检测':'陈检测', + '干货入库':'王仓管','干货存储':'系统监控','预处理':'刘加工' +}; + /* 当前节点索引 */ var currentNodeIdx = 0; @@ -254,13 +263,16 @@ function renderNodeDetail(node) { var iconBg = NODE_ICON_BG[stage] || '#F5F5F5'; var iconColor = NODE_COLORS[stage] || '#999'; var icon = NODE_ICONS[stage] || '●'; + var operator = node.operator || STAGE_OPERATORS[stage] || '—'; + /* 详情中已含操作人(经办人/采收人/厨师等叫法)时不重复展示 */ + var detailHtml = node.detail.indexOf(operator.split(' ')[0]) === -1 ? '操作人:' + operator + '
' + node.detail : node.detail; var html = '
'; html += '
' + icon + '
'; html += '
'; - html += '
' + icon + ' ' + stage + '' + node.time + '
'; + html += '
' + icon + ' ' + stage + '
'; html += '
' + node.title + '
'; - html += '
溯源码:' + node.trace + '
'; - html += '
' + node.detail + '
'; + html += '
时间:' + node.time + ' · 溯源码:' + node.trace + '
'; + html += '
' + detailHtml + '
'; if (node.video) { html += '
🎬 查看视频
'; }