feat: 订餐终端设备概览弹窗 + 会员权益/采购库存抵扣/追溯多锅次分支 + 设备端统计与验收改造
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b4dfe31b25
commit
792567ac03
@@ -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 |
|
||||
|
||||
@@ -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 居中外壳设计模式]]` ✅ 目标存在(本次新增)
|
||||
|
||||
@@ -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#补齐明细页业务路径修正:少记来源 = 未识别取餐记录]])
|
||||
|
||||
Generated
+1
-62
@@ -7,67 +7,7 @@
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="283fd756-b396-42c1-9c09-92be9e28fc03" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="68c1996d-eec2-4ab9-b784-3ffb5361b022" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="2">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="3cb2ed86-4c98-42cc-a37c-8d213f6d3262" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="3">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="fbdaa82c-4db6-4524-af37-a9615f16dda7" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="4">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="b0afeb94-4113-4f79-989e-84bae83331a3" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="5">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="24dd6f10-4a49-4b9c-95fa-486906c3cdba" />
|
||||
<option name="sessionId" value="74d0830a-01f8-44b3-afd4-267a8ccb514e" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
@@ -77,6 +17,5 @@
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="tabCount" value="6" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Last updated: 2026-08-10 | Commit: ff48f38 -->
|
||||
<!-- Last updated: 2026-08-14 | Commit: b4dfe31 -->
|
||||
# 项目约束
|
||||
|
||||
本项目所有工作必须严格遵守 `项目背景.md` 中定义的全部规范,包括:
|
||||
|
||||
@@ -1102,6 +1102,7 @@
|
||||
.mh-dmethod-card--donate { background: rgba(74, 222, 128, 0.08); border-color: rgba(74, 222, 128, 0.2); }
|
||||
.mh-dmethod-card--waste { background: rgba(239, 68, 68, 0.08); border-color: rgba(239, 68, 68, 0.2); }
|
||||
.mh-dmethod-card--reprocess { background: rgba(168, 85, 247, 0.08); border-color: rgba(168, 85, 247, 0.2); }
|
||||
.mh-dmethod-card--cabinet { background: rgba(34, 211, 238, 0.08); border-color: rgba(34, 211, 238, 0.2); }
|
||||
.mh-dmethod-card__label { font-size: 10px; color: rgba(255, 255, 255, 0.55); }
|
||||
.mh-dmethod-card__value { font-size: 20px; font-weight: 700; color: #fff; }
|
||||
.mh-dmethod-card__count { font-size: 10px; color: rgba(255, 255, 255, 0.45); }
|
||||
@@ -1109,6 +1110,7 @@
|
||||
.mh-dmethod-card--donate .mh-dmethod-card__value { color: #4ade80; }
|
||||
.mh-dmethod-card--waste .mh-dmethod-card__value { color: #ef4444; }
|
||||
.mh-dmethod-card--reprocess .mh-dmethod-card__value { color: #c084fc; }
|
||||
.mh-dmethod-card--cabinet .mh-dmethod-card__value { color: #22d3ee; }
|
||||
|
||||
/* 筛选 tab */
|
||||
.mh-filter-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||
@@ -1142,6 +1144,7 @@
|
||||
.mh-method-badge--donate { background: rgba(74, 222, 128, 0.18); color: #4ade80; }
|
||||
.mh-method-badge--waste { background: rgba(239, 68, 68, 0.18); color: #ef4444; }
|
||||
.mh-method-badge--reprocess { background: rgba(168, 85, 247, 0.18); color: #c084fc; }
|
||||
.mh-method-badge--cabinet { background: rgba(34, 211, 238, 0.18); color: #22d3ee; }
|
||||
|
||||
/* 节点卡片内的处置方式紧凑网格 */
|
||||
.mh-dispose-compact {
|
||||
@@ -1741,7 +1744,7 @@
|
||||
var MH_NODE_TYPES = {
|
||||
start: { dotCls: 'mh-tl-node__dot--start', dotIcon: '▶', badgeCls: 'mh-detail-subhead__badge--start', label: '开餐' },
|
||||
add: { dotCls: 'mh-tl-node__dot--add', dotIcon: '➕', badgeCls: 'mh-detail-subhead__badge--add', label: '补餐' },
|
||||
close: { dotCls: 'mh-tl-node__dot--close', dotIcon: '✕', badgeCls: 'mh-detail-subhead__badge--close', label: '闭餐' },
|
||||
close: { dotCls: 'mh-tl-node__dot--close', dotIcon: '✕', badgeCls: 'mh-detail-subhead__badge--close', label: '收餐' },
|
||||
dispose: { dotCls: 'mh-tl-node__dot--dispose', dotIcon: '♻', badgeCls: 'mh-detail-subhead__badge--dispose', label: '余量处置' }
|
||||
};
|
||||
|
||||
@@ -1749,7 +1752,8 @@
|
||||
welfare: { label: '员工福利', cls: 'mh-method-badge--welfare', cardCls: 'mh-dmethod-card--welfare' },
|
||||
donate: { label: '捐赠', cls: 'mh-method-badge--donate', cardCls: 'mh-dmethod-card--donate' },
|
||||
waste: { label: '废弃', cls: 'mh-method-badge--waste', cardCls: 'mh-dmethod-card--waste' },
|
||||
reprocess: { label: '加工再利用', cls: 'mh-method-badge--reprocess', cardCls: 'mh-dmethod-card--reprocess' }
|
||||
reprocess: { label: '加工再利用', cls: 'mh-method-badge--reprocess', cardCls: 'mh-dmethod-card--reprocess' },
|
||||
cabinet: { label: '存入智能货柜', cls: 'mh-method-badge--cabinet', cardCls: 'mh-dmethod-card--cabinet' }
|
||||
};
|
||||
|
||||
var MH_MEAL_HISTORY = [
|
||||
@@ -1762,8 +1766,7 @@
|
||||
{ type: 'start', time: '11:00:23', title: '开餐', desc: '操作人 <strong>张三 · CS001</strong> 开启<strong>午餐</strong>餐次,涉及 <strong>28 道菜</strong>', tags: [{ cls: 'mh-tl-tag--green', text: '28 道菜' }], detail: { firstOrder: { time: '11:32:10', line: 'A', customer: '张*明', amount: 27.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '11:01:12', maker: '王师傅', operator: '张三', planned: 8.5, sold: 7.8 }, { name: '鱼香肉丝', line: 'A', serveTime: '11:01:18', maker: '王师傅', operator: '张三', planned: 7.0, sold: 6.2 }, { name: '番茄炒蛋', line: 'B', serveTime: '11:00:45', maker: '李师傅', operator: '张三', planned: 6.0, sold: 4.5 }, { name: '白米饭', line: 'A', serveTime: '11:00:30', maker: '赵师傅', operator: '张三', planned: 15.0, sold: 12.0 }, { name: '清炒西兰花', line: 'B', serveTime: '11:01:05', maker: '李师傅', operator: '张三', planned: 5.0, sold: 4.0 }, { name: '红烧肉', line: 'A', serveTime: '11:01:22', maker: '王师傅', operator: '张三', planned: 6.5, sold: 5.8 }, { name: '麻婆豆腐', line: 'B', serveTime: '11:01:30', maker: '李师傅', operator: '张三', planned: 4.5, sold: 3.9 }, { name: '糖醋排骨', line: 'A', serveTime: '11:02:00', maker: '王师傅', operator: '张三', planned: 5.0, sold: 4.2 }, { name: '酸辣土豆丝', line: 'B', serveTime: '11:02:10', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.3 }, { name: '回锅肉', line: 'C', serveTime: '11:00:55', maker: '陈师傅', operator: '张三', planned: 5.5, sold: 4.8 }, { name: '水煮鱼', line: 'A', serveTime: '11:02:20', maker: '王师傅', operator: '张三', planned: 4.0, sold: 3.2 }, { name: '干煸豆角', line: 'C', serveTime: '11:01:40', maker: '陈师傅', operator: '张三', planned: 3.5, sold: 2.8 }, { name: '蒜蓉生菜', line: 'B', serveTime: '11:01:50', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.1 }, { name: '紫菜蛋花汤', line: 'A', serveTime: '11:00:40', maker: '王师傅', operator: '张三', planned: 10.0, sold: 8.0 }] } },
|
||||
{ type: 'add', time: '12:05:45', title: '补餐 · 番茄炒蛋', desc: '<strong>番茄炒蛋</strong> 余量降至 0.8kg(预警阈值),后厨补做 <strong>3.0kg</strong>', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '番茄炒蛋', line: 'B', dishEmoji: '🍳', triggerLeftover: 0.8, addAmount: 3.0, reason: '自动预警', operator: '张三', status: '已补做' } },
|
||||
{ type: 'add', time: '12:48:20', title: '补餐 · 白米饭', desc: '<strong>白米饭</strong> 余量降至 3.0kg,后厨补做 <strong>5.0kg</strong>', tags: [{ cls: 'mh-tl-tag--gold', text: '手动通知' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '白米饭', line: 'A', dishEmoji: '🍚', triggerLeftover: 3.0, addAmount: 5.0, reason: '手动通知', operator: '张三', status: '已补做' } },
|
||||
{ type: 'close', time: '13:15:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '闭餐' }], closeDetail: { lines: [{ line: 'B', name: 'B 区中餐线', closeTime: '13:15:00', orders: 32, revenue: 870.00, avgPer: 27.19 }] } },
|
||||
{ type: 'close', time: '13:45:30', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区中餐线', closeTime: '13:45:30', orders: 41, revenue: 1130.50, avgPer: 27.57 }] } }
|
||||
{ type: 'close', time: '13:45:30', title: '收餐', desc: '部分餐线已收餐,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '部分收餐' }], closeDetail: { operator: '张三', closeTime: '13:45:30', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 73, revenue: 2000.50, avgPer: 27.40, surplus: '5.0' } }
|
||||
],
|
||||
footer: { totalOrders: 96, totalRevenue: 2614.50, avgPer: 27.23, surplus: '进行中' }
|
||||
},
|
||||
@@ -1775,8 +1778,8 @@
|
||||
nodes: [
|
||||
{ type: 'start', time: '06:30:00', title: '开餐', desc: '操作人 <strong>李四 · CS002</strong> 开启<strong>早餐</strong>餐次,涉及 <strong>14 道菜</strong>', tags: [{ cls: 'mh-tl-tag--green', text: '14 道菜' }], detail: { firstOrder: { time: '06:48:15', line: 'A', customer: '王*强', amount: 12.50 }, dishes: [{ name: '豆浆', line: 'A', serveTime: '06:30:10', maker: '孙师傅', operator: '李四', planned: 8.0, sold: 6.5 }, { name: '油条', line: 'A', serveTime: '06:30:20', maker: '孙师傅', operator: '李四', planned: 5.0, sold: 4.2 }, { name: '鲜肉包', line: 'B', serveTime: '06:30:05', maker: '周师傅', operator: '李四', planned: 6.0, sold: 5.0 }, { name: '鸡蛋饼', line: 'A', serveTime: '06:30:35', maker: '孙师傅', operator: '李四', planned: 4.0, sold: 3.3 }, { name: '小米粥', line: 'B', serveTime: '06:30:15', maker: '周师傅', operator: '李四', planned: 6.0, sold: 5.1 }, { name: '馒头', line: 'B', serveTime: '06:30:25', maker: '周师傅', operator: '李四', planned: 5.0, sold: 4.0 }, { name: '花卷', line: 'A', serveTime: '06:30:45', maker: '孙师傅', operator: '李四', planned: 3.5, sold: 2.8 }, { name: '茶叶蛋', line: 'A', serveTime: '06:30:08', maker: '孙师傅', operator: '李四', planned: 4.0, sold: 3.5 }] } },
|
||||
{ type: 'add', time: '07:25:30', title: '补餐 · 豆浆', desc: '<strong>豆浆</strong> 余量降至 1.2kg,后厨补做 <strong>3.0kg</strong>', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '豆浆', line: 'A', dishEmoji: '🥛', triggerLeftover: 1.2, addAmount: 3.0, reason: '自动预警', operator: '李四', status: '已补做' } },
|
||||
{ type: 'close', time: '08:50:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '全部闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区早餐线', closeTime: '08:50:00', orders: 78, revenue: 995.00, avgPer: 12.76 }, { line: 'B', name: 'B 区早餐线', closeTime: '08:50:00', orders: 64, revenue: 825.00, avgPer: 12.89 }] } },
|
||||
{ type: 'dispose', time: '09:32:18', title: '余量处置完成', desc: '本餐次共处置余量 <strong>12.4kg</strong>,操作人 <strong>李四</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 12.4, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 5.2, count: 3 }, { key: 'donate', label: '捐赠', amount: 4.8, count: 2 }, { key: 'waste', label: '废弃', amount: 1.4, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }], records: [{ time: '09:33:10', dish: '油条', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '09:34:05', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.8, receiver: '员工 · 陈小红', operator: '李四', note: '值班餐' }, { time: '09:35:20', dish: '鸡蛋饼', line: 'A', method: 'welfare', amount: 1.4, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '09:36:45', dish: '小米粥', line: 'B', method: 'donate', amount: 3.0, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '09:37:30', dish: '馒头', line: 'B', method: 'donate', amount: 1.8, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '09:38:15', dish: '花卷', line: 'A', method: 'waste', amount: 0.9, receiver: '—', operator: '李四', note: '临期食品' }, { time: '09:39:00', dish: '茶叶蛋', line: 'A', method: 'waste', amount: 0.5, receiver: '—', operator: '李四', note: '破壳损耗' }, { time: '09:40:10', dish: '豆浆', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '李四', note: '员工餐再利用' }] } }
|
||||
{ type: 'close', time: '08:50:00', title: '收餐', desc: '全部餐线收餐完成,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '李四', closeTime: '08:50:00', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 142, revenue: 1820.00, avgPer: 12.82, surplus: '12.4' } },
|
||||
{ type: 'dispose', time: '09:32:18', title: '余量处置', desc: '本餐次共处置余量 <strong>12.9kg</strong>,操作人 <strong>李四</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 12.9, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 5.2, count: 3 }, { key: 'donate', label: '捐赠', amount: 4.8, count: 2 }, { key: 'waste', label: '废弃', amount: 1.4, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }, { key: 'cabinet', label: '存入智能货柜', amount: 0.5, count: 1 }], records: [{ time: '09:33:10', dish: '油条', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '09:34:05', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.8, receiver: '员工 · 陈小红', operator: '李四', note: '值班餐' }, { time: '09:35:20', dish: '鸡蛋饼', line: 'A', method: 'welfare', amount: 1.4, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '09:36:45', dish: '小米粥', line: 'B', method: 'donate', amount: 3.0, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '09:37:30', dish: '馒头', line: 'B', method: 'donate', amount: 1.8, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '09:38:15', dish: '花卷', line: 'A', method: 'waste', amount: 0.9, receiver: '—', operator: '李四', reason: '临期食品', note: '' }, { time: '09:39:00', dish: '茶叶蛋', line: 'A', method: 'waste', amount: 0.5, receiver: '—', operator: '李四', reason: '破壳损耗', note: '' }, { time: '09:40:10', dish: '豆浆', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '李四', processing: '员工餐再利用', note: '' }, { time: '09:42:00', dish: '鸡蛋饼', line: 'A', method: 'cabinet', amount: 0.5, cabinetId: 'SCG-01-03', operator: '李四', note: '保鲜盒存储' }] } }
|
||||
],
|
||||
footer: { totalOrders: 142, totalRevenue: 1820.00, avgPer: 12.82, surplus: '12.4 kg' }
|
||||
},
|
||||
@@ -1788,8 +1791,8 @@
|
||||
nodes: [
|
||||
{ type: 'start', time: '17:00:00', title: '开餐', desc: '操作人 <strong>张三 · CS001</strong> 开启<strong>晚餐</strong>餐次,涉及 <strong>24 道菜</strong>', tags: [{ cls: 'mh-tl-tag--green', text: '24 道菜' }], detail: { firstOrder: { time: '17:21:50', line: 'C', customer: '陈*芳', amount: 8.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '17:00:30', maker: '王师傅', operator: '张三', planned: 7.0, sold: 6.3 }, { name: '鱼香肉丝', line: 'A', serveTime: '17:00:45', maker: '王师傅', operator: '张三', planned: 6.5, sold: 5.7 }, { name: '番茄炒蛋', line: 'B', serveTime: '17:00:20', maker: '李师傅', operator: '张三', planned: 6.0, sold: 5.0 }, { name: '白米饭', line: 'A', serveTime: '17:00:10', maker: '赵师傅', operator: '张三', planned: 12.0, sold: 10.5 }, { name: '红烧肉', line: 'A', serveTime: '17:01:00', maker: '王师傅', operator: '张三', planned: 5.5, sold: 4.8 }, { name: '牛肉面', line: 'C', serveTime: '17:00:50', maker: '陈师傅', operator: '张三', planned: 8.0, sold: 6.5 }, { name: '酸辣粉', line: 'C', serveTime: '17:01:10', maker: '陈师傅', operator: '张三', planned: 5.0, sold: 4.2 }, { name: '蛋炒饭', line: 'C', serveTime: '17:00:35', maker: '陈师傅', operator: '张三', planned: 4.5, sold: 3.8 }, { name: '清炒西兰花', line: 'B', serveTime: '17:01:20', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.2 }, { name: '麻婆豆腐', line: 'B', serveTime: '17:01:30', maker: '李师傅', operator: '张三', planned: 4.0, sold: 3.5 }] } },
|
||||
{ type: 'add', time: '18:15:22', title: '补餐 · 牛肉面', desc: '<strong>牛肉面</strong> 余量降至 0.5kg(预警阈值),后厨补做 <strong>2.5kg</strong>', tags: [{ cls: 'mh-tl-tag--gold', text: '自动预警' }, { cls: 'mh-tl-tag--green', text: '已通知后厨' }], supply: { dish: '牛肉面', line: 'C', dishEmoji: '🍜', triggerLeftover: 0.5, addAmount: 2.5, reason: '自动预警', operator: '张三', status: '已补做' } },
|
||||
{ type: 'close', time: '19:30:00', title: '闭餐', desc: '停止取餐', tags: [{ cls: 'mh-tl-tag--gray', text: '全部闭餐' }], closeDetail: { lines: [{ line: 'A', name: 'A 区中餐线', closeTime: '19:30:00', orders: 58, revenue: 1340.00, avgPer: 23.10 }, { line: 'B', name: 'B 区中餐线', closeTime: '19:30:00', orders: 49, revenue: 1150.00, avgPer: 23.47 }, { line: 'C', name: 'C 区快餐线', closeTime: '19:30:00', orders: 61, revenue: 1360.00, avgPer: 22.30 }] } },
|
||||
{ type: 'dispose', time: '20:25:10', title: '余量处置完成', desc: '本餐次共处置余量 <strong>22.6kg</strong>,操作人 <strong>张三</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--red', text: '损耗超标 5.1kg' }], dispose: { total: 22.6, operator: '张三', methods: [{ key: 'welfare', label: '员工福利', amount: 8.0, count: 4 }, { key: 'donate', label: '捐赠', amount: 6.5, count: 3 }, { key: 'waste', label: '废弃', amount: 5.1, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.0, count: 2 }], records: [{ time: '20:26:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '张三', note: '加班餐' }, { time: '20:27:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 李小红', operator: '张三', note: '' }, { time: '20:27:50', dish: '白米饭', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '张三', note: '' }, { time: '20:28:40', dish: '清炒西兰花', line: 'B', method: 'donate', amount: 2.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '20:29:15', dish: '番茄炒蛋', line: 'B', method: 'donate', amount: 2.2, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '20:29:50', dish: '麻婆豆腐', line: 'B', method: 'donate', amount: 1.8, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '20:30:05', dish: '酸辣粉', line: 'C', method: 'waste', amount: 2.1, receiver: '—', operator: '张三', note: '临期食品' }, { time: '20:30:40', dish: '蛋炒饭', line: 'C', method: 'waste', amount: 1.5, receiver: '—', operator: '张三', note: '' }, { time: '20:31:10', dish: '红烧肉', line: 'A', method: 'waste', amount: 0.8, receiver: '—', operator: '张三', note: '过夜损耗' }, { time: '20:31:45', dish: '牛肉面', line: 'C', method: 'waste', amount: 0.7, receiver: '—', operator: '张三', note: '' }, { time: '20:32:30', dish: '番茄炒蛋', line: 'B', method: 'reprocess', amount: 1.5, receiver: '后厨 · 李师傅', operator: '李四', note: '次日员工餐' }, { time: '20:33:10', dish: '白米饭', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 赵师傅', operator: '张三', note: '员工餐再利用' }, { time: '20:34:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '张三', note: '' }] } }
|
||||
{ type: 'close', time: '19:30:00', title: '收餐', desc: '全部餐线收餐完成,进入余量处置流程', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '张三', closeTime: '19:30:00', method: 'surplus_dispose', methodLabel: '余量处置', orders: 168, revenue: 3850.00, avgPer: 22.92, surplus: '22.6' } },
|
||||
{ type: 'dispose', time: '20:25:10', title: '余量处置', desc: '本餐次共处置余量 <strong>24.1kg</strong>,操作人 <strong>张三</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 24.1, operator: '张三', methods: [{ key: 'welfare', label: '员工福利', amount: 8.0, count: 4 }, { key: 'donate', label: '捐赠', amount: 6.5, count: 3 }, { key: 'waste', label: '废弃', amount: 5.1, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.0, count: 2 }, { key: 'cabinet', label: '存入智能货柜', amount: 1.5, count: 2 }], records: [{ time: '20:26:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '张三', note: '加班餐' }, { time: '20:27:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 李小红', operator: '张三', note: '' }, { time: '20:27:50', dish: '白米饭', line: 'A', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '张三', note: '' }, { time: '20:28:40', dish: '清炒西兰花', line: 'B', method: 'donate', amount: 2.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '20:29:15', dish: '番茄炒蛋', line: 'B', method: 'donate', amount: 2.2, receiver: '浐灞社区服务站', operator: '李四', note: '' }, { time: '20:29:50', dish: '麻婆豆腐', line: 'B', method: 'donate', amount: 1.8, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '20:30:05', dish: '酸辣粉', line: 'C', method: 'waste', amount: 2.1, receiver: '—', operator: '张三', reason: '临期食品', note: '' }, { time: '20:30:40', dish: '蛋炒饭', line: 'C', method: 'waste', amount: 1.5, receiver: '—', operator: '张三', reason: '剩余变质', note: '' }, { time: '20:31:10', dish: '红烧肉', line: 'A', method: 'waste', amount: 0.8, receiver: '—', operator: '张三', reason: '过夜损耗', note: '' }, { time: '20:31:45', dish: '牛肉面', line: 'C', method: 'waste', amount: 0.7, receiver: '—', operator: '张三', reason: '剩余变质', note: '' }, { time: '20:32:30', dish: '番茄炒蛋', line: 'B', method: 'reprocess', amount: 1.5, receiver: '后厨 · 李师傅', operator: '李四', processing: '次日员工餐', note: '' }, { time: '20:33:10', dish: '白米饭', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 赵师傅', operator: '张三', processing: '员工餐再利用', note: '' }, { time: '20:34:00', dish: '鱼香肉丝', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '张三', note: '' }, { time: '20:35:00', dish: '宫保鸡丁', line: 'A', method: 'cabinet', amount: 1.0, cabinetId: 'SCG-02-05', operator: '张三', note: '次日复热' }, { time: '20:35:40', dish: '鱼香肉丝', line: 'A', method: 'cabinet', amount: 0.5, cabinetId: 'SCG-02-06', operator: '张三', note: '' }] } }
|
||||
],
|
||||
footer: { totalOrders: 168, totalRevenue: 3850.00, avgPer: 22.92, surplus: '22.6 kg' }
|
||||
},
|
||||
@@ -1800,7 +1803,8 @@
|
||||
timeRange: '11:00:00 - 14:05:20', endTime: '14:05:20',
|
||||
nodes: [
|
||||
{ type: 'start', time: '11:00:00', title: '开餐', desc: '操作人 <strong>李四 · CS002</strong> 开启<strong>午餐</strong>餐次,涉及 <strong>28 道菜</strong>', tags: [{ cls: 'mh-tl-tag--green', text: '28 道菜' }], detail: { firstOrder: { time: '11:25:10', line: 'A', customer: '刘*华', amount: 32.50 }, dishes: [{ name: '宫保鸡丁', line: 'A', serveTime: '11:00:30', maker: '王师傅', operator: '李四', planned: 8.0, sold: 7.5 }, { name: '糖醋排骨', line: 'A', serveTime: '11:00:50', maker: '王师傅', operator: '李四', planned: 6.0, sold: 5.2 }, { name: '红烧狮子头', line: 'B', serveTime: '11:01:10', maker: '李师傅', operator: '李四', planned: 5.0, sold: 4.3 }, { name: '番茄炒蛋', line: 'B', serveTime: '11:00:40', maker: '李师傅', operator: '李四', planned: 7.0, sold: 6.0 }, { name: '白米饭', line: 'A', serveTime: '11:00:15', maker: '赵师傅', operator: '李四', planned: 14.0, sold: 12.0 }, { name: '葱爆羊肉', line: 'C', serveTime: '11:01:25', maker: '陈师傅', operator: '李四', planned: 4.5, sold: 3.8 }, { name: '蚝油生菜', line: 'C', serveTime: '11:01:40', maker: '陈师傅', operator: '李四', planned: 3.5, sold: 2.9 }, { name: '辣子鸡', line: 'C', serveTime: '11:02:00', maker: '陈师傅', operator: '李四', planned: 5.0, sold: 4.1 }, { name: '麻婆豆腐', line: 'B', serveTime: '11:01:55', maker: '李师傅', operator: '李四', planned: 4.0, sold: 3.4 }, { name: '紫菜蛋花汤', line: 'A', serveTime: '11:00:20', maker: '王师傅', operator: '李四', planned: 8.0, sold: 7.0 }] } },
|
||||
{ type: 'dispose', time: '14:28:42', title: '余量处置完成', desc: '本餐次共处置余量 <strong>28.6kg</strong>,操作人 <strong>李四</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 28.6, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 10.0, count: 5 }, { key: 'donate', label: '捐赠', amount: 8.5, count: 3 }, { key: 'waste', label: '废弃', amount: 6.6, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.5, count: 2 }], records: [{ time: '14:29:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 3.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '14:29:50', dish: '糖醋排骨', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 陈小红', operator: '李四', note: '' }, { time: '14:30:30', dish: '番茄炒蛋', line: 'B', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '14:31:10', dish: '白米饭', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '李四', note: '' }, { time: '14:32:00', dish: '麻婆豆腐', line: 'B', method: 'welfare', amount: 1.0, receiver: '员工 · 刘洋', operator: '李四', note: '' }, { time: '14:33:20', dish: '红烧狮子头', line: 'B', method: 'donate', amount: 3.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '14:34:00', dish: '辣子鸡', line: 'C', method: 'donate', amount: 3.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:34:40', dish: '蚝油生菜', line: 'C', method: 'donate', amount: 2.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:35:30', dish: '葱爆羊肉', line: 'C', method: 'waste', amount: 2.5, receiver: '—', operator: '李四', note: '临期食品' }, { time: '14:36:10', dish: '紫菜蛋花汤', line: 'A', method: 'waste', amount: 2.1, receiver: '—', operator: '李四', note: '' }, { time: '14:36:50', dish: '白米饭', line: 'A', method: 'waste', amount: 1.2, receiver: '—', operator: '李四', note: '' }, { time: '14:37:30', dish: '番茄炒蛋', line: 'B', method: 'waste', amount: 0.8, receiver: '—', operator: '李四', note: '' }, { time: '14:38:20', dish: '白米饭', line: 'A', method: 'reprocess', amount: 2.0, receiver: '后厨 · 赵师傅', operator: '李四', note: '员工餐' }, { time: '14:39:00', dish: '宫保鸡丁', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 王师傅', operator: '李四', note: '' }] } }
|
||||
{ type: 'close', time: '14:05:20', title: '收餐', desc: '全部餐线收餐完成,进入余量处置流程', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '李四', closeTime: '14:05:20', method: 'surplus_dispose', methodLabel: '余量处置', orders: 132, revenue: 3050.00, avgPer: 23.10, surplus: '28.6' } },
|
||||
{ type: 'dispose', time: '14:28:42', title: '余量处置', desc: '本餐次共处置余量 <strong>30.6kg</strong>,操作人 <strong>李四</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }, { cls: 'mh-tl-tag--green', text: '交叉核对正常' }], dispose: { total: 30.6, operator: '李四', methods: [{ key: 'welfare', label: '员工福利', amount: 10.0, count: 5 }, { key: 'donate', label: '捐赠', amount: 8.5, count: 3 }, { key: 'waste', label: '废弃', amount: 6.6, count: 4 }, { key: 'reprocess', label: '加工再利用', amount: 3.5, count: 2 }, { key: 'cabinet', label: '存入智能货柜', amount: 2.0, count: 2 }], records: [{ time: '14:29:10', dish: '宫保鸡丁', line: 'A', method: 'welfare', amount: 3.0, receiver: '员工 · 王建国', operator: '李四', note: '加班餐' }, { time: '14:29:50', dish: '糖醋排骨', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 陈小红', operator: '李四', note: '' }, { time: '14:30:30', dish: '番茄炒蛋', line: 'B', method: 'welfare', amount: 2.0, receiver: '员工 · 赵大鹏', operator: '李四', note: '' }, { time: '14:31:10', dish: '白米饭', line: 'A', method: 'welfare', amount: 1.5, receiver: '员工 · 张伟', operator: '李四', note: '' }, { time: '14:32:00', dish: '麻婆豆腐', line: 'B', method: 'welfare', amount: 1.0, receiver: '员工 · 刘洋', operator: '李四', note: '' }, { time: '14:33:20', dish: '红烧狮子头', line: 'B', method: 'donate', amount: 3.5, receiver: '浐灞社区服务站', operator: '李四', note: '捐赠凭证已上传' }, { time: '14:34:00', dish: '辣子鸡', line: 'C', method: 'donate', amount: 3.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:34:40', dish: '蚝油生菜', line: 'C', method: 'donate', amount: 2.0, receiver: '爱心食堂', operator: '李四', note: '' }, { time: '14:35:30', dish: '葱爆羊肉', line: 'C', method: 'waste', amount: 2.5, receiver: '—', operator: '李四', reason: '临期食品', note: '' }, { time: '14:36:10', dish: '紫菜蛋花汤', line: 'A', method: 'waste', amount: 2.1, receiver: '—', operator: '李四', reason: '剩余变质', note: '' }, { time: '14:36:50', dish: '白米饭', line: 'A', method: 'waste', amount: 1.2, receiver: '—', operator: '李四', reason: '剩余变质', note: '' }, { time: '14:37:30', dish: '番茄炒蛋', line: 'B', method: 'waste', amount: 0.8, receiver: '—', operator: '李四', reason: '外观不佳', note: '' }, { time: '14:38:20', dish: '白米饭', line: 'A', method: 'reprocess', amount: 2.0, receiver: '后厨 · 赵师傅', operator: '李四', processing: '员工餐', note: '' }, { time: '14:39:00', dish: '宫保鸡丁', line: 'A', method: 'reprocess', amount: 1.5, receiver: '后厨 · 王师傅', operator: '李四', processing: '员工餐再利用', note: '' }, { time: '14:40:00', dish: '紫菜蛋花汤', line: 'A', method: 'cabinet', amount: 1.2, cabinetId: 'SCG-01-08', operator: '李四', note: '冷藏保存' }, { time: '14:40:30', dish: '麻婆豆腐', line: 'B', method: 'cabinet', amount: 0.8, cabinetId: 'SCG-01-09', operator: '李四', note: '' }] } }
|
||||
],
|
||||
footer: { totalOrders: 132, totalRevenue: 3050.00, avgPer: 23.10, surplus: '28.6 kg' }
|
||||
},
|
||||
@@ -1811,7 +1815,8 @@
|
||||
timeRange: '06:30:00 - 09:18:00', endTime: '09:18:00',
|
||||
nodes: [
|
||||
{ type: 'start', time: '06:30:00', title: '开餐', desc: '操作人 <strong>王五 · CS003</strong> 开启<strong>早餐</strong>餐次,涉及 <strong>12 道菜</strong>', tags: [{ cls: 'mh-tl-tag--green', text: '12 道菜' }], detail: { dishes: [{ name: '豆浆', line: 'A', serveTime: '06:30:10', maker: '孙师傅', operator: '王五', planned: 6.0, sold: 5.2 }, { name: '油条', line: 'A', serveTime: '06:30:25', maker: '孙师傅', operator: '王五', planned: 4.0, sold: 3.5 }, { name: '鲜肉包', line: 'B', serveTime: '06:30:05', maker: '周师傅', operator: '王五', planned: 5.0, sold: 4.2 }, { name: '鸡蛋饼', line: 'A', serveTime: '06:30:40', maker: '孙师傅', operator: '王五', planned: 3.5, sold: 2.8 }, { name: '小米粥', line: 'B', serveTime: '06:30:15', maker: '周师傅', operator: '王五', planned: 5.0, sold: 4.5 }, { name: '馒头', line: 'B', serveTime: '06:30:30', maker: '周师傅', operator: '王五', planned: 4.0, sold: 3.3 }, { name: '花卷', line: 'A', serveTime: '06:30:50', maker: '孙师傅', operator: '王五', planned: 3.0, sold: 2.4 }, { name: '茶叶蛋', line: 'A', serveTime: '06:30:08', maker: '孙师傅', operator: '王五', planned: 3.5, sold: 2.9 }] } },
|
||||
{ type: 'dispose', time: '09:35:24', title: '余量处置完成', desc: '本餐次共处置余量 <strong>8.4kg</strong>,操作人 <strong>王五</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 8.4, operator: '王五', methods: [{ key: 'welfare', label: '员工福利', amount: 4.0, count: 2 }, { key: 'donate', label: '捐赠', amount: 2.4, count: 2 }, { key: 'waste', label: '废弃', amount: 1.0, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }], records: [{ time: '09:36:10', dish: '豆浆', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '王五', note: '加班餐' }, { time: '09:37:00', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.5, receiver: '员工 · 陈小红', operator: '王五', note: '' }, { time: '09:38:20', dish: '小米粥', line: 'B', method: 'donate', amount: 1.5, receiver: '浐灞社区服务站', operator: '王五', note: '捐赠凭证已上传' }, { time: '09:39:00', dish: '馒头', line: 'B', method: 'donate', amount: 0.9, receiver: '浐灞社区服务站', operator: '王五', note: '' }, { time: '09:40:10', dish: '油条', line: 'A', method: 'waste', amount: 0.6, receiver: '—', operator: '王五', note: '临期' }, { time: '09:40:50', dish: '花卷', line: 'A', method: 'waste', amount: 0.4, receiver: '—', operator: '王五', note: '' }, { time: '09:41:30', dish: '鸡蛋饼', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '王五', note: '员工餐' }] } }
|
||||
{ type: 'close', time: '09:18:00', title: '收餐', desc: '全部餐线收餐完成,进入员工餐时段', tags: [{ cls: 'mh-tl-tag--gray', text: '全部收餐' }], closeDetail: { operator: '王五', closeTime: '09:18:00', method: 'staff_meal', methodLabel: '进入员工餐时段', orders: 98, revenue: 1250.00, avgPer: 12.76, surplus: '8.4' } },
|
||||
{ type: 'dispose', time: '09:35:24', title: '余量处置', desc: '本餐次共处置余量 <strong>9.0kg</strong>,操作人 <strong>王五</strong>', tags: [{ cls: 'mh-tl-tag--purple', text: '处置完成' }], dispose: { total: 9.0, operator: '王五', methods: [{ key: 'welfare', label: '员工福利', amount: 4.0, count: 2 }, { key: 'donate', label: '捐赠', amount: 2.4, count: 2 }, { key: 'waste', label: '废弃', amount: 1.0, count: 2 }, { key: 'reprocess', label: '加工再利用', amount: 1.0, count: 1 }, { key: 'cabinet', label: '存入智能货柜', amount: 0.6, count: 1 }], records: [{ time: '09:36:10', dish: '豆浆', line: 'A', method: 'welfare', amount: 2.5, receiver: '员工 · 王建国', operator: '王五', note: '加班餐' }, { time: '09:37:00', dish: '鲜肉包', line: 'B', method: 'welfare', amount: 1.5, receiver: '员工 · 陈小红', operator: '王五', note: '' }, { time: '09:38:20', dish: '小米粥', line: 'B', method: 'donate', amount: 1.5, receiver: '浐灞社区服务站', operator: '王五', note: '捐赠凭证已上传' }, { time: '09:39:00', dish: '馒头', line: 'B', method: 'donate', amount: 0.9, receiver: '浐灞社区服务站', operator: '王五', note: '' }, { time: '09:40:10', dish: '油条', line: 'A', method: 'waste', amount: 0.6, receiver: '—', operator: '王五', reason: '临期', note: '' }, { time: '09:40:50', dish: '花卷', line: 'A', method: 'waste', amount: 0.4, receiver: '—', operator: '王五', reason: '剩余变质', note: '' }, { time: '09:41:30', dish: '鸡蛋饼', line: 'A', method: 'reprocess', amount: 1.0, receiver: '后厨 · 孙师傅', operator: '王五', processing: '员工餐', note: '' }, { time: '09:42:00', dish: '豆浆', line: 'A', method: 'cabinet', amount: 0.6, cabinetId: 'SCG-01-02', operator: '王五', note: '保鲜存储' }] } }
|
||||
],
|
||||
footer: { totalOrders: 98, totalRevenue: 1250.00, avgPer: 12.76, surplus: '8.4 kg' }
|
||||
}
|
||||
@@ -1820,7 +1825,7 @@
|
||||
var mhCurrentMealId = null;
|
||||
var mhCurrentView = 'timeline';
|
||||
var mhViewNodeType = null;
|
||||
var mhDisposeFilter = 'all';
|
||||
var mhDisposeFilter = 'welfare';
|
||||
|
||||
function mhRenderMealList(filterDate) {
|
||||
var groups = {};
|
||||
@@ -1881,7 +1886,7 @@
|
||||
mhCurrentMealId = mealId;
|
||||
mhCurrentView = 'timeline';
|
||||
mhViewNodeType = null;
|
||||
mhDisposeFilter = 'all';
|
||||
mhDisposeFilter = 'welfare';
|
||||
document.querySelectorAll('.mh-meal-item').forEach(function (el) {
|
||||
el.classList.toggle('is-active', el.getAttribute('data-id') === mealId);
|
||||
});
|
||||
@@ -1904,7 +1909,6 @@
|
||||
+ '<span class="mh-detail-header__pill ' + pillCls + '">' + meal.statusLabel + '</span>'
|
||||
+ '<div class="mh-detail-header__spacer"></div>'
|
||||
+ '<div class="mh-detail-header__info">'
|
||||
+ ' <div class="mh-detail-header__info-item">操作人 <strong>' + meal.operator + '</strong></div>'
|
||||
+ ' <div class="mh-detail-header__info-item">餐厅 <strong>' + meal.restaurant + '</strong></div>'
|
||||
+ ' <div class="mh-detail-header__info-item">' + meal.timeRange + '</div>'
|
||||
+ '</div>';
|
||||
@@ -1922,7 +1926,7 @@
|
||||
|
||||
var html = '<div class="mh-timeline">';
|
||||
|
||||
// 固定四阶段:开餐 → 补餐 → 闭餐 → 余量处置
|
||||
// 固定四阶段:开餐 → 补餐 → 收餐 → 余量处置
|
||||
|
||||
// 1. 开餐
|
||||
if (startNode) {
|
||||
@@ -1941,27 +1945,19 @@
|
||||
html += mhBuildTlCard('add', lastAddTime, '补餐 · ' + addNodes.length + ' 次', addDesc, addTags, true, 'mhOpenNodeDetail(\'add\')');
|
||||
}
|
||||
|
||||
// 3. 闭餐(汇总视图)
|
||||
// 3. 收餐
|
||||
if (closeNodes.length > 0) {
|
||||
var lastCloseTime = closeNodes[closeNodes.length - 1].time;
|
||||
var closeDesc = '本餐次共 <strong>' + closeNodes.length + ' 次闭餐</strong>';
|
||||
var closeDesc = '本餐次共 <strong>' + closeNodes.length + ' 次收餐</strong>';
|
||||
var closeTags = [
|
||||
{ cls: 'mh-tl-tag--gray', text: closeNodes.length + ' 次闭餐' }
|
||||
{ cls: 'mh-tl-tag--gray', text: closeNodes.length + ' 次收餐' }
|
||||
];
|
||||
html += mhBuildTlCard('close', lastCloseTime, '闭餐 · ' + closeNodes.length + ' 次', closeDesc, closeTags, true, 'mhOpenNodeDetail(\'close\')');
|
||||
html += mhBuildTlCard('close', lastCloseTime, '收餐 · ' + closeNodes.length + ' 次', closeDesc, closeTags, true, 'mhOpenNodeDetail(\'close\')');
|
||||
}
|
||||
|
||||
// 4. 余量处置
|
||||
if (disposeNode) {
|
||||
var disposeCompactHtml = '';
|
||||
if (disposeNode.dispose && disposeNode.dispose.methods) {
|
||||
var items = disposeNode.dispose.methods.map(function (d) {
|
||||
return '<div class="mh-dispose-compact__item"><span class="mh-dispose-compact__label">' + d.label + '</span>'
|
||||
+ '<span class="mh-dispose-compact__value">' + d.amount + ' kg</span></div>';
|
||||
}).join('');
|
||||
disposeCompactHtml = '<div class="mh-dispose-compact">' + items + '</div>';
|
||||
}
|
||||
html += mhBuildTlCard('dispose', disposeNode.time, disposeNode.title, disposeNode.desc, disposeNode.tags, true, 'mhOpenNodeDetail(\'dispose\')', disposeCompactHtml);
|
||||
html += mhBuildTlCard('dispose', disposeNode.time, disposeNode.title, disposeNode.desc, disposeNode.tags, true, 'mhOpenNodeDetail(\'dispose\')');
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
@@ -2014,7 +2010,7 @@
|
||||
if (!meal) return;
|
||||
mhViewNodeType = nodeType;
|
||||
mhCurrentView = 'detail';
|
||||
mhDisposeFilter = 'all';
|
||||
mhDisposeFilter = 'welfare';
|
||||
var typeInfo = MH_NODE_TYPES[nodeType] || MH_NODE_TYPES.start;
|
||||
var subhead = document.getElementById('mhDetailSubhead');
|
||||
subhead.classList.add('mh-detail-subhead--show');
|
||||
@@ -2032,7 +2028,7 @@
|
||||
document.getElementById('mhSubheadTime').textContent = addNodes.length > 0 ? addNodes[addNodes.length - 1].time : '';
|
||||
} else if (nodeType === 'close') {
|
||||
var clNodes = meal.nodes.filter(function(n) { return n.type === 'close'; });
|
||||
document.getElementById('mhSubheadTitle').textContent = '闭餐 · ' + clNodes.length + ' 次';
|
||||
document.getElementById('mhSubheadTitle').textContent = '收餐';
|
||||
document.getElementById('mhSubheadTime').textContent = clNodes.length > 0 ? clNodes[clNodes.length - 1].time : '';
|
||||
} else if (nodeType === 'dispose') {
|
||||
var dn = meal.nodes.find(function(n) { return n.type === 'dispose'; });
|
||||
@@ -2057,7 +2053,7 @@
|
||||
var meal = mhGetMeal();
|
||||
if (!meal) return;
|
||||
mhCurrentView = 'timeline';
|
||||
mhDisposeFilter = 'all';
|
||||
mhDisposeFilter = 'welfare';
|
||||
document.getElementById('mhDetailSubhead').classList.remove('mh-detail-subhead--show');
|
||||
mhRenderTimeline(meal);
|
||||
document.querySelector('.mh-timeline-wrap').scrollTop = 0;
|
||||
@@ -2068,30 +2064,39 @@
|
||||
if (!node) return '<div style="padding:20px;color:rgba(255,255,255,0.5)">暂无开餐数据</div>';
|
||||
var d = node.detail || {};
|
||||
var dishes = d.dishes || [];
|
||||
var fo = d.firstOrder;
|
||||
// 收集本餐次所有补餐量,按菜品名分组(用于反推初始上餐量)
|
||||
var supplyMap = {};
|
||||
meal.nodes.forEach(function (n) {
|
||||
if (n.type === 'add' && n.supply) {
|
||||
var dn = n.supply.dish;
|
||||
supplyMap[dn] = (supplyMap[dn] || 0) + n.supply.addAmount;
|
||||
}
|
||||
});
|
||||
// 计算总制作量(累计上餐量之和)
|
||||
var totalMade = 0;
|
||||
dishes.forEach(function (it) { totalMade += (it.planned || 0); });
|
||||
var html = '<div class="mh-detail-summary">';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">开餐时间</span><span class="mh-ds-card__value">' + node.time + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">菜品数</span><span class="mh-ds-card__value mh-ds-card__value--green">' + dishes.length + ' 道</span></div>';
|
||||
if (fo) {
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">首单 · ' + fo.time + '</span>'
|
||||
+ '<span class="mh-ds-card__value mh-ds-card__value--gold">¥' + fo.amount.toFixed(2)
|
||||
+ ' <span style="font-size:10px;font-weight:400;color:rgba(255,255,255,0.5)">' + fo.customer + '</span></span></div>';
|
||||
} else {
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">首单</span><span class="mh-ds-card__value" style="color:rgba(255,255,255,0.35)">—</span></div>';
|
||||
}
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">总制作量</span><span class="mh-ds-card__value mh-ds-card__value--gold">' + totalMade.toFixed(1) + ' kg</span></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:18%"><col style="width:15%"><col style="width:12%"><col style="width:13%"><col style="width:21%"><col style="width:21%"></colgroup>';
|
||||
html += '<thead><tr><th>菜品</th><th>上餐时间</th><th>制作人</th><th>操作人</th><th class="mh-col-num">计划量 (kg)</th><th class="mh-col-num">已售 (kg)</th></tr></thead>';
|
||||
html += '<colgroup><col style="width:16%"><col style="width:14%"><col style="width:11%"><col style="width:11%"><col style="width:16%"><col style="width:16%"><col style="width:16%"></colgroup>';
|
||||
html += '<thead><tr><th>菜品</th><th>上餐时间</th><th>制作人</th><th>操作人</th><th class="mh-col-num">初始上餐量 (kg)</th><th class="mh-col-num">累计上餐量 (kg)</th><th class="mh-col-num">已售 (kg)</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
dishes.forEach(function (d) {
|
||||
// 累计上餐量=planned;初始上餐量=planned − 该菜品补餐总量
|
||||
var accumulated = d.planned || 0;
|
||||
var addAmount = supplyMap[d.name] || 0;
|
||||
var initial = +(accumulated - addAmount).toFixed(1);
|
||||
html += '<tr>'
|
||||
+ '<td>' + d.name + '</td>'
|
||||
+ '<td>' + d.serveTime + '</td>'
|
||||
+ '<td>' + d.maker + '</td>'
|
||||
+ '<td>' + d.operator + '</td>'
|
||||
+ '<td class="mh-col-num">' + d.planned.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + d.sold.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + initial.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + accumulated.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + (d.sold || 0).toFixed(1) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
@@ -2103,31 +2108,31 @@
|
||||
var supplies = [];
|
||||
addNodes.forEach(function (n) { if (n.supply) supplies.push(n.supply); });
|
||||
if (supplies.length === 0) return '<div style="padding:20px;color:rgba(255,255,255,0.5)">暂无补餐记录</div>';
|
||||
var totalAmount = 0, autoCount = 0, manualCount = 0;
|
||||
var totalAmount = 0;
|
||||
var dishSet = {};
|
||||
supplies.forEach(function (s) {
|
||||
totalAmount += s.addAmount;
|
||||
if (s.reason === '自动预警') autoCount++;
|
||||
else manualCount++;
|
||||
dishSet[s.dish] = (dishSet[s.dish] || 0) + 1;
|
||||
});
|
||||
var dishCount = Object.keys(dishSet).length;
|
||||
var html = '<div class="mh-detail-summary">';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">补餐次数</span><span class="mh-ds-card__value mh-ds-card__value--blue">' + supplies.length + ' 次</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">累计补做量</span><span class="mh-ds-card__value mh-ds-card__value--gold">' + totalAmount.toFixed(1) + ' kg</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">自动预警</span><span class="mh-ds-card__value mh-ds-card__value--green">' + autoCount + ' 次</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">手动通知</span><span class="mh-ds-card__value">' + manualCount + ' 次</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">补餐菜品数</span><span class="mh-ds-card__value mh-ds-card__value--blue">' + dishCount + ' 种</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">补餐次数</span><span class="mh-ds-card__value mh-ds-card__value--green">' + supplies.length + ' 次</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">补餐总量</span><span class="mh-ds-card__value mh-ds-card__value--gold">' + totalAmount.toFixed(1) + ' kg</span></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:14%"><col style="width:18%"><col style="width:16%" class="mh-col-num"><col style="width:14%" class="mh-col-num"><col style="width:13%"><col style="width:12%"><col style="width:13%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">触发余量 (kg)</th><th class="mh-col-num">补做量 (kg)</th><th>原因</th><th>操作人</th><th>状态</th></tr></thead>';
|
||||
html += '<colgroup><col style="width:14%"><col style="width:18%"><col style="width:13%" class="mh-col-num"><col style="width:14%" class="mh-col-num"><col style="width:14%" class="mh-col-num"><col style="width:12%"><col style="width:15%"></colgroup>';
|
||||
html += '<thead><tr><th>补餐时间</th><th>餐品</th><th class="mh-col-num">补餐量 (kg)</th><th class="mh-col-num">补餐前余量 (kg)</th><th class="mh-col-num">补餐后余量 (kg)</th><th>操作人</th><th>状态</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
supplies.forEach(function (s, i) {
|
||||
var reasonCls = s.reason === '自动预警' ? 'mh-tl-tag--gold' : 'mh-tl-tag--gray';
|
||||
var statusCls = s.status === '已补做' ? 'mh-tl-tag--green' : 'mh-tl-tag--gold';
|
||||
var afterLeftover = +(s.triggerLeftover + s.addAmount).toFixed(1);
|
||||
html += '<tr>'
|
||||
+ '<td>' + (addNodes[i] ? addNodes[i].time : '—') + '</td>'
|
||||
+ '<td>' + (s.dishEmoji || '') + ' ' + s.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + s.triggerLeftover.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + s.addAmount.toFixed(1) + '</td>'
|
||||
+ '<td><span class="mh-tl-tag ' + reasonCls + '">' + s.reason + '</span></td>'
|
||||
+ '<td class="mh-col-num">' + s.triggerLeftover.toFixed(1) + '</td>'
|
||||
+ '<td class="mh-col-num">' + afterLeftover.toFixed(1) + '</td>'
|
||||
+ '<td>' + s.operator + '</td>'
|
||||
+ '<td><span class="mh-tl-tag ' + statusCls + '">' + s.status + '</span></td>'
|
||||
+ '</tr>';
|
||||
@@ -2138,50 +2143,25 @@
|
||||
|
||||
function mhRenderCloseDetail(meal) {
|
||||
var closeNodes = meal.nodes.filter(function (n) { return n.type === 'close'; });
|
||||
if (closeNodes.length === 0) return '<div style="padding:20px;color:rgba(255,255,255,0.5)">暂无闭餐记录</div>';
|
||||
|
||||
// 汇总各闭餐事件的订单与营收
|
||||
var closeEvents = closeNodes.map(function (cn) {
|
||||
var orders = 0, revenue = 0;
|
||||
if (cn.closeDetail && cn.closeDetail.lines) {
|
||||
cn.closeDetail.lines.forEach(function (l) { orders += l.orders; revenue += l.revenue; });
|
||||
}
|
||||
return { time: cn.time, orders: orders, revenue: revenue, avgPer: orders > 0 ? (revenue / orders) : 0 };
|
||||
});
|
||||
|
||||
var totalOrders = 0, totalRevenue = 0;
|
||||
closeEvents.forEach(function (e) { totalOrders += e.orders; totalRevenue += e.revenue; });
|
||||
var overallAvg = totalOrders > 0 ? (totalRevenue / totalOrders) : 0;
|
||||
|
||||
var endInfo = meal.endTime
|
||||
? ('<span class="mh-ds-card__value" style="font-size:13px">' + meal.endTime + ' <span style="font-size:10px;font-weight:400;color:rgba(255,255,255,0.5)">· ' + meal.operator + '</span></span>')
|
||||
: '<span class="mh-ds-card__value mh-ds-card__value--gold">进行中</span>';
|
||||
|
||||
if (closeNodes.length === 0) return '<div style="padding:20px;color:rgba(255,255,255,0.5)">暂无收餐记录</div>';
|
||||
// 收餐是一次性动作,取最新一条作为收餐事件
|
||||
var last = closeNodes[closeNodes.length - 1];
|
||||
var cd = last.closeDetail || {};
|
||||
var methodLabel = cd.methodLabel || '—';
|
||||
var methodCls = cd.method === 'staff_meal' ? 'mh-tl-tag--blue' : 'mh-tl-tag--purple';
|
||||
// 第一组:收餐主信息(3 卡片)
|
||||
var html = '<div class="mh-detail-summary">';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">结束用餐</span>' + endInfo + '</div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">闭餐次数</span><span class="mh-ds-card__value mh-ds-card__value--blue">' + closeEvents.length + ' 次</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">总订单</span><span class="mh-ds-card__value">' + totalOrders + ' 单</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">总营收</span><span class="mh-ds-card__value mh-ds-card__value--green">¥' + totalRevenue.toFixed(2) + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">收餐时间</span><span class="mh-ds-card__value">' + (cd.closeTime || last.time) + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">收餐人</span><span class="mh-ds-card__value mh-ds-card__value--blue">' + (cd.operator || '—') + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">收餐方式</span><span class="mh-ds-card__value"><span class="mh-tl-tag ' + methodCls + '">' + methodLabel + '</span></span></div>';
|
||||
html += '</div>';
|
||||
// 第二组:截止收餐时主要统计数据(4 卡片)
|
||||
html += '<div class="mh-detail-summary" style="margin-top:10px">';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">截止收餐订单数</span><span class="mh-ds-card__value">' + (cd.orders || 0) + ' 单</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">截止收餐营收</span><span class="mh-ds-card__value mh-ds-card__value--green">¥' + (cd.revenue || 0).toFixed(2) + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">客单价</span><span class="mh-ds-card__value mh-ds-card__value--gold">¥' + (cd.avgPer || 0).toFixed(2) + '</span></div>';
|
||||
html += '<div class="mh-ds-card"><span class="mh-ds-card__label">截止收餐余量</span><span class="mh-ds-card__value">' + (cd.surplus || '—') + ' kg</span></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:30%"><col style="width:23%"><col style="width:23%"><col style="width:24%"></colgroup>';
|
||||
html += '<thead><tr><th>闭餐时间</th><th class="mh-col-num">订单数</th><th class="mh-col-num">营收</th><th class="mh-col-num">客单价</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
closeEvents.forEach(function (e) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + e.time + '</td>'
|
||||
+ '<td class="mh-col-num">' + e.orders + '</td>'
|
||||
+ '<td class="mh-col-num">¥' + e.revenue.toFixed(2) + '</td>'
|
||||
+ '<td class="mh-col-num">¥' + e.avgPer.toFixed(2) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
html += '<tr class="mh-detail-table__total">'
|
||||
+ '<td>合计 · ' + closeEvents.length + ' 次闭餐</td>'
|
||||
+ '<td class="mh-col-num">' + totalOrders + '</td>'
|
||||
+ '<td class="mh-col-num">¥' + totalRevenue.toFixed(2) + '</td>'
|
||||
+ '<td class="mh-col-num">¥' + overallAvg.toFixed(2) + '</td>'
|
||||
+ '</tr>';
|
||||
html += '</tbody></table></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -2191,6 +2171,7 @@
|
||||
var d = node.dispose;
|
||||
if (!d) return '<div style="padding:20px;color:rgba(255,255,255,0.5)">暂无处置记录</div>';
|
||||
var html = '';
|
||||
// 处置方式统计卡片(5种方式)
|
||||
html += '<div class="mh-dmethod-grid">';
|
||||
(d.methods || []).forEach(function (m) {
|
||||
var mInfo = MH_METHOD_MAP[m.key] || {};
|
||||
@@ -2206,41 +2187,131 @@
|
||||
+ '<span class="mh-dmethod-card__count">操作人 · ' + d.operator + '</span>'
|
||||
+ '</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="mh-filter-tabs">';
|
||||
// 5 种方式过滤 Tab(无"全部",每种方式独立查看)
|
||||
var allRecords = d.records || [];
|
||||
var filters = [
|
||||
{ key: 'all', label: '全部 (' + (d.records || []).length + ')' },
|
||||
{ key: 'welfare', label: '员工福利' },
|
||||
{ key: 'donate', label: '捐赠' },
|
||||
{ key: 'waste', label: '废弃' },
|
||||
{ key: 'reprocess', label: '加工再利用' }
|
||||
{ key: 'welfare', label: '员工福利' },
|
||||
{ key: 'donate', label: '捐赠' },
|
||||
{ key: 'waste', label: '废弃' },
|
||||
{ key: 'reprocess', label: '加工再利用' },
|
||||
{ key: 'cabinet', label: '存入智能货柜' }
|
||||
];
|
||||
html += '<div class="mh-filter-tabs">';
|
||||
filters.forEach(function (f) {
|
||||
html += '<span class="mh-filter-tab' + (mhDisposeFilter === f.key ? ' is-active' : '') + '" onclick="mhSetDisposeFilter(\'' + f.key + '\')">' + f.label + '</span>';
|
||||
var count = allRecords.filter(function (r) { return r.method === f.key; }).length;
|
||||
html += '<span class="mh-filter-tab' + (mhDisposeFilter === f.key ? ' is-active' : '') + '" onclick="mhSetDisposeFilter(\'' + f.key + '\')">' + f.label + ' (' + count + ')</span>';
|
||||
});
|
||||
html += '</div>';
|
||||
var records = d.records || [];
|
||||
if (mhDisposeFilter !== 'all') records = records.filter(function (r) { return r.method === mhDisposeFilter; });
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:15%"><col style="width:13%"><col style="width:12%" class="mh-col-num"><col style="width:18%"><col style="width:11%"><col style="width:18%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th>处置方式</th><th class="mh-col-num">处置量 (kg)</th><th>接收方 · 领用人</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="7" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无该类型的处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
var mInfo = MH_METHOD_MAP[r.method] || {};
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td><span class="mh-method-badge ' + (mInfo.cls || '') + '">' + (mInfo.label || r.method) + '</span></td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + r.receiver + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
// 当前方式的记录
|
||||
var records = allRecords.filter(function (r) { return r.method === mhDisposeFilter; });
|
||||
// 按方式渲染独立列结构
|
||||
if (mhDisposeFilter === 'welfare') {
|
||||
// 员工福利: 时间/餐品/处置量/领用人/操作人/备注
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:18%"><col style="width:12%" class="mh-col-num"><col style="width:20%"><col style="width:12%"><col style="width:25%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">处置量 (kg)</th><th>领用人</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="6" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无员工福利处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + (r.receiver || '—') + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
} else if (mhDisposeFilter === 'donate') {
|
||||
// 捐赠: 时间/餐品/处置量/接收机构/操作人/备注
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:18%"><col style="width:12%" class="mh-col-num"><col style="width:20%"><col style="width:12%"><col style="width:25%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">处置量 (kg)</th><th>接收机构</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="6" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无捐赠处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + (r.receiver || '—') + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
} else if (mhDisposeFilter === 'waste') {
|
||||
// 废弃: 时间/餐品/处置量/废弃原因/操作人/备注
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:18%"><col style="width:12%" class="mh-col-num"><col style="width:20%"><col style="width:12%"><col style="width:25%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">处置量 (kg)</th><th>废弃原因</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="6" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无废弃处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + (r.reason || '—') + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
} else if (mhDisposeFilter === 'reprocess') {
|
||||
// 加工再利用: 时间/餐品/处置量/加工用途/接收人/操作人/备注
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:15%"><col style="width:10%" class="mh-col-num"><col style="width:18%"><col style="width:17%"><col style="width:12%"><col style="width:15%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">处置量 (kg)</th><th>加工用途</th><th>接收人</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="7" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无加工再利用处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + (r.processing || '—') + '</td>'
|
||||
+ '<td>' + (r.receiver || '—') + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
} else if (mhDisposeFilter === 'cabinet') {
|
||||
// 存入智能货柜: 时间/餐品/处置量/货柜编号/操作人/备注
|
||||
html += '<div class="mh-detail-table-wrap"><table class="mh-detail-table">';
|
||||
html += '<colgroup><col style="width:13%"><col style="width:18%"><col style="width:12%" class="mh-col-num"><col style="width:20%"><col style="width:12%"><col style="width:25%"></colgroup>';
|
||||
html += '<thead><tr><th>时间</th><th>餐品</th><th class="mh-col-num">处置量 (kg)</th><th>货柜编号</th><th>操作人</th><th>备注</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
if (records.length === 0) {
|
||||
html += '<tr><td colspan="6" style="text-align:center;color:rgba(255,255,255,0.4);padding:20px">暂无存入智能货柜处置记录</td></tr>';
|
||||
} else {
|
||||
records.forEach(function (r) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + r.time + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td class="mh-col-num">' + r.amount.toFixed(1) + '</td>'
|
||||
+ '<td>' + (r.cabinetId || '—') + '</td>'
|
||||
+ '<td>' + r.operator + '</td>'
|
||||
+ '<td>' + (r.note || '—') + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# 餐厅终端设备模块变更日志
|
||||
|
||||
## 2026-08-18 · 结算设备统计页收餐记录 + 毛菜验收秤实收规格可编辑
|
||||
|
||||
### 变更一:结算设备统计页收餐与余量处置记录(`checkout-device/07-statistics.html`)
|
||||
- 新增收餐记录:全部收餐/全部餐线收餐完成事件,取最新一条作为收餐时间基准
|
||||
- 新增余量处置记录:临期食品·冷藏保存·加工再利用·值班餐/加班餐等处置方式
|
||||
- 各记录弹窗明细含原因/用途/接收人/操作人/备注等字段,初始上餐量·制作人·上餐时间展示
|
||||
|
||||
### 变更二:毛菜验收秤实收规格增强(`raw-veg-inspection-scale/01-verify-inspection.html`)
|
||||
- 实收规格列拆分为「克重 + 单位」组合编辑:单位组合字符串展示,支持分类过滤(叶菜类等)
|
||||
- 采购单价为数据字典值只读展示(灰字),实收量/实收单价/实收金额可编辑
|
||||
- 编辑时仅持久化到本次订单,输入不重渲染避免光标丢失
|
||||
|
||||
## 2026-08-14 · 毛菜验收秤 PAD 外壳改造 + 后厨补餐屏首版设计
|
||||
|
||||
### 变更一:毛菜验收秤全页面 PAD 居中外壳改造(`raw-veg-inspection-scale/` 全部 9 页)
|
||||
|
||||
@@ -308,6 +308,33 @@
|
||||
}
|
||||
.food-row__spec-select:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
|
||||
.food-row__spec-select option { background: #1a2b4a; color: #fff; }
|
||||
/* 采购规格列(只读):克重+单位组合字符串展示,不可编辑 */
|
||||
.food-row__spec-readonly {
|
||||
display: inline-block; padding: 2px 6px; border-radius: 4px;
|
||||
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.55);
|
||||
font-size: 11px; font-weight: 500; white-space: nowrap;
|
||||
}
|
||||
/* 实收规格列:克重 input + 单位 select 连体容器(参考 sup-in-raw.html 的 .spec-input-group) */
|
||||
.food-row__spec-input-group { display: flex; align-items: stretch; width: 100%; height: 24px; }
|
||||
.food-row__spec-input-group input {
|
||||
flex: 1; min-width: 0; padding: 0 4px;
|
||||
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 4px 0 0 4px; border-right: none;
|
||||
color: rgba(255,255,255,0.85); font-size: 11px; text-align: center;
|
||||
outline: none; -moz-appearance: textfield;
|
||||
}
|
||||
.food-row__spec-input-group input::-webkit-outer-spin-button,
|
||||
.food-row__spec-input-group input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.food-row__spec-input-group input:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
|
||||
.food-row__spec-input-group input::placeholder { color: rgba(255,255,255,0.25); }
|
||||
.food-row__spec-input-group select {
|
||||
width: 44px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 0 4px 4px 0; padding: 2px 2px;
|
||||
color: rgba(255,255,255,0.7); font-size: 11px; text-align: center;
|
||||
outline: none; appearance: auto; cursor: pointer;
|
||||
}
|
||||
.food-row__spec-input-group select:focus { border-color: #4a90d9; background: rgba(74,144,217,0.08); color: #fff; }
|
||||
.food-row__spec-input-group select option { background: #1a2b4a; color: #fff; }
|
||||
.food-row__amount { font-size: 12px; color: #faad14; font-weight: 500; }
|
||||
.food-row__actual { color: #4a90d9; font-weight: 600; }
|
||||
.food-row__status { font-size: 11px; }
|
||||
@@ -1035,39 +1062,39 @@
|
||||
/* ===== 食材目录(称重验收,存储单位克,展示层用 formatGramForCanteen 还原"斤") ===== */
|
||||
/* verifyType:'weight' 表示称重验收,可用 ['斤','公斤','克','千克'] */
|
||||
var FOOD_CATALOG = {
|
||||
'大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001', verifyType: 'weight' },
|
||||
'圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002', verifyType: 'weight' },
|
||||
'菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003', verifyType: 'weight' },
|
||||
'油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004', verifyType: 'weight' },
|
||||
'生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005', verifyType: 'weight' },
|
||||
'芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006', verifyType: 'weight' },
|
||||
'西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001', verifyType: 'weight' },
|
||||
'花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002', verifyType: 'weight' },
|
||||
'黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001', verifyType: 'weight' },
|
||||
'西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002', verifyType: 'weight' },
|
||||
'茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003', verifyType: 'weight' },
|
||||
'青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004', verifyType: 'weight' },
|
||||
'豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005', verifyType: 'weight' },
|
||||
'土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001', verifyType: 'weight' },
|
||||
'胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002', verifyType: 'weight' },
|
||||
'白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007', verifyType: 'weight' },
|
||||
'猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001', verifyType: 'weight' },
|
||||
'五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002', verifyType: 'weight' },
|
||||
'草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001', verifyType: 'weight' },
|
||||
'虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002', verifyType: 'weight' }
|
||||
'大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001', verifyType: 'weight', specGram: 1000, specUnit: '袋' },
|
||||
'胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001', verifyType: 'weight', specGram: 500, specUnit: '袋' },
|
||||
'虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002', verifyType: 'weight', specGram: 250, specUnit: '袋' }
|
||||
};
|
||||
|
||||
/* ===== 调料目录(点收验收,按规格/数量验收) ===== */
|
||||
/* verifyType:'count' 表示点收验收,可用 ['袋','瓶','箱','桶','罐','份','个','只','条','把'] */
|
||||
var SPICE_CATALOG = {
|
||||
'酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001', verifyType: 'count' },
|
||||
'醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002', verifyType: 'count' },
|
||||
'料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003', verifyType: 'count' },
|
||||
'盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004', verifyType: 'count' },
|
||||
'白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005', verifyType: 'count' },
|
||||
'鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006', verifyType: 'count' },
|
||||
'食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007', verifyType: 'count' },
|
||||
'花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008', verifyType: 'count' }
|
||||
'酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001', verifyType: 'count', specGram: 500, specUnit: '瓶' },
|
||||
'醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002', verifyType: 'count', specGram: 500, specUnit: '瓶' },
|
||||
'料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003', verifyType: 'count', specGram: 500, specUnit: '瓶' },
|
||||
'盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004', verifyType: 'count', specGram: 500, specUnit: '袋' },
|
||||
'白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005', verifyType: 'count', specGram: 1000, specUnit: '袋' },
|
||||
'鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006', verifyType: 'count', specGram: 200, specUnit: '罐' },
|
||||
'食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007', verifyType: 'count', specGram: 5000, specUnit: '桶' },
|
||||
'花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008', verifyType: 'count', specGram: 100, specUnit: '袋' }
|
||||
};
|
||||
|
||||
/* 判断食材是否为调料(按规格验收) */
|
||||
@@ -1442,12 +1469,12 @@
|
||||
|
||||
var isDirect = !!selectedOrder._direct;
|
||||
/* 列宽(单位 px,首列 1fr 自适应)
|
||||
* 有订单(10列): 食材/采购规格/采购量/采购单价/采购单金额/实收规格/实收量/实收单价/实收总价/操作
|
||||
* 无订单(6列): 食材/实收规格/实收量/实收单价/实收总价/操作
|
||||
* 有订单(10列): 食材/采购规格(只读)/采购量/采购单价/采购单金额/实收规格(input+select)/实收量/实收单价/实收总价/操作
|
||||
* 无订单(6列): 食材/实收规格(input+select)/实收量/实收单价/实收总价/操作
|
||||
*/
|
||||
var gridCols = isDirect
|
||||
? '1fr 50px 50px 56px 60px 52px'
|
||||
: '1fr 56px 50px 56px 60px 50px 50px 56px 60px 52px';
|
||||
? '1fr 96px 50px 56px 60px 52px'
|
||||
: '1fr 60px 50px 56px 60px 96px 50px 56px 60px 52px';
|
||||
headEl.style.gridTemplateColumns = gridCols;
|
||||
/* 表头文字 */
|
||||
headEl.innerHTML = isDirect
|
||||
@@ -1493,22 +1520,31 @@
|
||||
? '<span class="food-row__weight" style="color:rgba(255,255,255,0.25);">--</span>'
|
||||
: '<span class="food-row__weight">' + (spice ? orderQty : orderQty.toFixed(1)) + '</span>';
|
||||
|
||||
/* 规格下拉选项(按 verifyType 分类过滤) */
|
||||
/* 规格单位下拉选项(按 verifyType 分类过滤,用于实收规格单位 select) */
|
||||
var UNIT_OPTIONS_WEIGHT = ['斤','公斤','克','千克'];
|
||||
var UNIT_OPTIONS_COUNT = ['袋','瓶','箱','桶','罐','份','个','只','条','把'];
|
||||
var verifyType = info.verifyType || 'weight';
|
||||
var UNIT_OPTIONS = (verifyType === 'count') ? UNIT_OPTIONS_COUNT : UNIT_OPTIONS_WEIGHT;
|
||||
var currentUnit = (f._customUnit !== undefined) ? f._customUnit : info.unit;
|
||||
var actualUnit = (f._actualUnit !== undefined) ? f._actualUnit : currentUnit;
|
||||
function buildUnitSelect(selected, onChangeFn) {
|
||||
var opts = '';
|
||||
for (var u = 0; u < UNIT_OPTIONS.length; u++) {
|
||||
var unit = UNIT_OPTIONS[u];
|
||||
opts += '<option value="' + unit + '"' + (unit === selected ? ' selected' : '') + '>' + unit + '</option>';
|
||||
}
|
||||
return '<select class="food-row__spec-select" onclick="event.stopPropagation()" onchange="' + onChangeFn + '">' + opts + '</select>';
|
||||
/* 采购规格克重/单位(数据字典值,只读展示;格式 "500g/袋") */
|
||||
var specGram = info.specGram || 0;
|
||||
var specUnit = info.specUnit || info.unit || '';
|
||||
/* 采购规格列(只读):克重+单位组合字符串 */
|
||||
var specHtml = '<span class="food-row__spec-readonly">' + specGram + 'g/' + specUnit + '</span>';
|
||||
/* 实收规格:默认代入采购规格数据(克重/单位),用户可编辑 */
|
||||
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 += '<option value="' + unit + '"' + (unit === actualSpecUnit ? ' selected' : '') + '>' + unit + '</option>';
|
||||
}
|
||||
var specHtml = buildUnitSelect(currentUnit, 'updateFoodUnit(' + i + ', this.value)');
|
||||
var actualSpecHtml = '<div class="food-row__spec-input-group">'
|
||||
+ '<input type="number" min="0" step="0.01" value="' + actualSpecGram + '" placeholder="0" '
|
||||
+ 'onclick="event.stopPropagation()" '
|
||||
+ 'oninput="updateActualSpecGram(' + i + ', this.value)" '
|
||||
+ 'title="实收规格克重">'
|
||||
+ '<select onclick="event.stopPropagation()" onchange="updateActualUnit(' + i + ', this.value)" title="实收规格单位">' + unitOpts + '</select>'
|
||||
+ '</div>';
|
||||
|
||||
/* 采购单价列(只读) + 采购单金额列(只读,= 采购单价 × 采购量) */
|
||||
var priceReadOnlyHtml = '<span class="food-row__price-readonly">¥' + currentPrice.toFixed(1) + '</span>';
|
||||
@@ -1571,8 +1607,6 @@
|
||||
+ '</div>';
|
||||
|
||||
var isCurrent = (i === currentFoodIdx) ? ' current' : '';
|
||||
/* 实收规格列(下拉可选,默认跟随采购规格) */
|
||||
var actualSpecHtml = buildUnitSelect(actualUnit, 'updateActualUnit(' + i + ', this.value)');
|
||||
|
||||
/* 组装行(按订单类型选不同列顺序) */
|
||||
var rowHtml = '<div class="food-row' + isCurrent + '" data-idx="' + i + '" style="grid-template-columns:' + gridCols + ';">'
|
||||
@@ -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');
|
||||
|
||||
@@ -146,6 +146,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<option>营养值超范围</option>
|
||||
<option>算法超时</option>
|
||||
<option>模型输出异常</option>
|
||||
<option>设备告警</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
@@ -162,7 +163,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
|
||||
<button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
|
||||
<div class="action-bar__right">共 <strong>89</strong> 条异常记录</div>
|
||||
<div class="action-bar__right">共 <strong>91</strong> 条异常记录</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
@@ -282,13 +283,33 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<td>2026-05-11 08:40</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-011</td>
|
||||
<td><span class="tag tag-orange">设备告警</span></td>
|
||||
<td>人脸识别</td>
|
||||
<td>档口秤 FCD-B007(2 号档口)于 2026-05-10 11:30~11:55 期间人脸识别连续失败 12 次,触发设备告警</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v1.5.0</td>
|
||||
<td>2026-05-10 11:55</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-012</td>
|
||||
<td><span class="tag tag-orange">设备告警</span></td>
|
||||
<td>人脸识别</td>
|
||||
<td>餐线秤 FCD-A012(3 号窗口)于 2026-05-09 17:20~18:10 期间人脸识别累计失败 28 次(阈值 20 次),触发设备告警</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v1.5.0</td>
|
||||
<td>2026-05-09 18:10</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination">
|
||||
<div class="pagination__left">
|
||||
<span>共 89 条</span>
|
||||
<span>共 91 条</span>
|
||||
<span>每页</span>
|
||||
<select class="filter-select" style="min-width:70px;">
|
||||
<option>10</option>
|
||||
|
||||
@@ -171,6 +171,32 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.adv-range{display:flex;align-items:center;gap:8px;}
|
||||
.adv-range .form-input{flex:1;min-width:0;}
|
||||
.adv-range__sep{color:var(--text-placeholder);font-size:var(--font-sm);flex-shrink:0;}
|
||||
/* 食材选择器(可搜索下拉) */
|
||||
.ingredient-picker{position:relative;}
|
||||
.ingredient-picker__trigger{display:flex;align-items:center;gap:6px;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;background:#fff;cursor:pointer;font-size:var(--font-sm);color:var(--text-primary);position:relative;transition:border-color .15s;}
|
||||
.ingredient-picker__trigger:hover{border-color:var(--primary);}
|
||||
.ingredient-picker__trigger.open{border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.12);}
|
||||
.ingredient-picker__trigger-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.ingredient-picker__trigger-text.is-empty{color:var(--text-placeholder);}
|
||||
.ingredient-picker__trigger-code{font-size:var(--font-xs);color:var(--primary);background:var(--primary-light);padding:1px 6px;border-radius:8px;flex-shrink:0;}
|
||||
.ingredient-picker__trigger-sep{color:var(--text-placeholder);flex-shrink:0;}
|
||||
.ingredient-picker__trigger-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.ingredient-picker__trigger-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:50%;color:var(--text-placeholder);font-size:12px;cursor:pointer;background:#fff;border:1px solid var(--border);}
|
||||
.ingredient-picker__trigger-clear:hover{color:var(--danger);border-color:var(--danger);}
|
||||
.ingredient-picker__trigger-arrow{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:10px;height:6px;pointer-events:none;background: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 center;}
|
||||
.ingredient-picker__trigger.open .ingredient-picker__trigger-arrow{transform:translateY(-50%) rotate(180deg);}
|
||||
.ingredient-picker__panel{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);z-index:50;overflow:hidden;}
|
||||
.ingredient-picker.open .ingredient-picker__panel{display:block;}
|
||||
.ingredient-picker__search{padding:8px;border-bottom:1px solid var(--border);}
|
||||
.ingredient-picker__search input{width:100%;height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;}
|
||||
.ingredient-picker__search input:focus{border-color:var(--primary);}
|
||||
.ingredient-picker__list{max-height:220px;overflow-y:auto;padding:4px 0;}
|
||||
.ingredient-picker__item{display:flex;align-items:center;gap:8px;padding:7px 12px;cursor:pointer;font-size:var(--font-sm);color:var(--text-primary);transition:background .12s;}
|
||||
.ingredient-picker__item:hover{background:var(--primary-light);}
|
||||
.ingredient-picker__item.selected{background:var(--primary-light);color:var(--primary);font-weight:500;}
|
||||
.ingredient-picker__item-code{font-size:var(--font-xs);color:var(--primary);background:var(--primary-light);padding:1px 6px;border-radius:8px;flex-shrink:0;font-family:Consolas,Monaco,monospace;}
|
||||
.ingredient-picker__item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.ingredient-picker__empty{padding:16px 12px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);}
|
||||
/* 操作列固定右侧 */
|
||||
thead th:last-child,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:2;white-space:nowrap;}
|
||||
thead th:last-child{background:#FAFAFA;}
|
||||
@@ -470,6 +496,20 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<option>低脂</option><option>高蛋白</option><option>素食</option><option>低卡</option><option>高纤维</option><option>低GI</option><option>高糖</option><option>无麸质</option><option>主食</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group ingredient-picker" id="advIngredientPicker">
|
||||
<label>食材</label>
|
||||
<div class="ingredient-picker__trigger" onclick="toggleIngredientPicker(event)">
|
||||
<span class="ingredient-picker__trigger-text is-empty" id="advIngredientText">请选择食材</span>
|
||||
<span class="ingredient-picker__trigger-clear" id="advIngredientClear" style="display:none;" onclick="clearIngredient(event)" title="清除">✕</span>
|
||||
<span class="ingredient-picker__trigger-arrow"></span>
|
||||
</div>
|
||||
<div class="ingredient-picker__panel">
|
||||
<div class="ingredient-picker__search">
|
||||
<input type="text" id="advIngredientSearch" placeholder="输入编码或名称搜索" oninput="filterIngredients()">
|
||||
</div>
|
||||
<div class="ingredient-picker__list" id="advIngredientList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>上架状态</label>
|
||||
<select class="form-select" id="advStatus">
|
||||
<option value="">全部</option>
|
||||
@@ -593,6 +633,23 @@ let currentTab = 0;
|
||||
let filteredDishes = [];
|
||||
/* 高级搜索已应用条件(用于汇总条显示) */
|
||||
let appliedAdvSearch = null;
|
||||
/* 食材候选列表:从所有餐品的 composition 中提取 {code,name} 并按 code 去重 */
|
||||
var INGREDIENT_OPTIONS = (function(){
|
||||
var map = {};
|
||||
DISHES.forEach(function(d){
|
||||
(d.composition||[]).forEach(function(c){
|
||||
if(c && c.code && c.name && !map[c.code]){
|
||||
map[c.code] = {code:c.code, name:c.name};
|
||||
}
|
||||
});
|
||||
});
|
||||
return Object.keys(map).map(function(k){return map[k];}).sort(function(a,b){
|
||||
var ca = a.code, cb = b.code;
|
||||
return ca < cb ? -1 : (ca > cb ? 1 : 0);
|
||||
});
|
||||
})();
|
||||
/* 高级搜索弹窗中食材选择器的当前选中食材编码(null 表示未选) */
|
||||
var advIngredientSelected = null;
|
||||
|
||||
function renderDishList(){
|
||||
document.getElementById('dishList').innerHTML = filteredDishes.map((entry)=>{const d=entry.dish;const i=entry.idx;return `
|
||||
@@ -1012,6 +1069,7 @@ function matchAdvanced(d, q){
|
||||
if(q.category && d.category!==q.category) return false;
|
||||
if(q.meal && (d.meal||'')!==q.meal) return false;
|
||||
if(q.tag && !(d.tags||[]).some(function(t){return t===q.tag;})) return false;
|
||||
if(q.ingredient && !(d.composition||[]).some(function(c){return c.code===q.ingredient;})) return false;
|
||||
if(q.status && d.status!==q.status) return false;
|
||||
/* 食材成本区间 */
|
||||
var cost = (d.pricing && d.pricing.ingredientCost!==undefined) ? d.pricing.ingredientCost : null;
|
||||
@@ -1040,10 +1098,17 @@ function updateFilterSummary(){
|
||||
Object.keys(appliedAdvSearch).forEach(function(k){
|
||||
var v = appliedAdvSearch[k];
|
||||
if(v===null || v===undefined || v==='') return;
|
||||
var labelMap = {name:'名称',code:'编号',category:'分类',meal:'餐次',tag:'标签',status:'状态',
|
||||
var labelMap = {name:'名称',code:'编号',category:'分类',meal:'餐次',tag:'标签',status:'状态',ingredient:'食材',
|
||||
costMin:'成本≥',costMax:'成本≤',priceMin:'售价≥',priceMax:'售价≤',
|
||||
updatedStart:'更新≥',updatedEnd:'更新≤',createdStart:'创建≥',createdEnd:'创建≤'};
|
||||
var label = labelMap[k] || k;
|
||||
/* 食材字段:v 为食材编码,需展示为 "[编码] 名称" 格式 */
|
||||
if(k==='ingredient'){
|
||||
var found = INGREDIENT_OPTIONS.filter(function(it){return it.code===v;})[0];
|
||||
var dispName = found ? found.name : v;
|
||||
bits.push(label + ' ['+v+'] ' + dispName);
|
||||
return;
|
||||
}
|
||||
var suffix = (k.indexOf('Min')>=0 || k.indexOf('Max')>=0) ? (k.indexOf('cost')>=0?'元':(k.indexOf('price')>=0?'元':'')) : '';
|
||||
bits.push(label + v + suffix);
|
||||
});
|
||||
@@ -1080,6 +1145,15 @@ function openAdvancedSearchDialog(){
|
||||
setVal('advUpdatedEnd', q.updatedEnd||'');
|
||||
setVal('advCreatedStart', q.createdStart||'');
|
||||
setVal('advCreatedEnd', q.createdEnd||'');
|
||||
/* 回填食材选择器 */
|
||||
advIngredientSelected = q.ingredient || null;
|
||||
updateIngredientTrigger();
|
||||
/* 清空食材搜索框 */
|
||||
var ingSearch = document.getElementById('advIngredientSearch');
|
||||
if(ingSearch) ingSearch.value = '';
|
||||
/* 关闭食材面板(避免上次状态残留) */
|
||||
var picker = document.getElementById('advIngredientPicker');
|
||||
if(picker) picker.classList.remove('open');
|
||||
document.getElementById('advancedSearchDialog').classList.add('open');
|
||||
}
|
||||
function closeAdvancedSearchDialog(){
|
||||
@@ -1087,6 +1161,11 @@ function closeAdvancedSearchDialog(){
|
||||
}
|
||||
function resetAdvancedSearch(){
|
||||
['advName','advCode','advCategory','advMeal','advTag','advStatus','advCostMin','advCostMax','advPriceMin','advPriceMax','advUpdatedStart','advUpdatedEnd','advCreatedStart','advCreatedEnd'].forEach(function(id){ setVal(id,''); });
|
||||
/* 重置食材选择器 */
|
||||
advIngredientSelected = null;
|
||||
updateIngredientTrigger();
|
||||
var ingSearch = document.getElementById('advIngredientSearch');
|
||||
if(ingSearch) ingSearch.value = '';
|
||||
}
|
||||
function applyAdvancedSearch(){
|
||||
appliedAdvSearch = {
|
||||
@@ -1096,6 +1175,7 @@ function applyAdvancedSearch(){
|
||||
meal: getVal('advMeal'),
|
||||
tag: getVal('advTag'),
|
||||
status: getVal('advStatus'),
|
||||
ingredient: advIngredientSelected || '',
|
||||
costMin: getNum('advCostMin'),
|
||||
costMax: getNum('advCostMax'),
|
||||
priceMin: getNum('advPriceMin'),
|
||||
@@ -1123,6 +1203,105 @@ function setVal(id, v){ var el = document.getElementById(id); if(el) el.value =
|
||||
function getVal(id){ var el = document.getElementById(id); return el ? el.value.trim() : ''; }
|
||||
function getNum(id){ var v = getVal(id); if(v==='') return null; var n = parseFloat(v); return isNaN(n) ? null : n; }
|
||||
|
||||
/* ========== 食材选择器(可搜索下拉) ========== */
|
||||
/* 切换食材下拉面板的展开/收起状态 */
|
||||
function toggleIngredientPicker(event){
|
||||
if(event){ event.stopPropagation(); }
|
||||
var picker = document.getElementById('advIngredientPicker');
|
||||
if(!picker) return;
|
||||
var isOpen = picker.classList.contains('open');
|
||||
if(isOpen){
|
||||
picker.classList.remove('open');
|
||||
return;
|
||||
}
|
||||
picker.classList.add('open');
|
||||
/* 展开时聚焦搜索框并重新渲染候选列表 */
|
||||
var searchInput = document.getElementById('advIngredientSearch');
|
||||
if(searchInput){
|
||||
setTimeout(function(){ searchInput.focus(); }, 0);
|
||||
}
|
||||
renderIngredientList('');
|
||||
}
|
||||
|
||||
/* 根据搜索关键词过滤并渲染候选列表 */
|
||||
function filterIngredients(){
|
||||
var kw = (document.getElementById('advIngredientSearch').value || '').trim().toLowerCase();
|
||||
renderIngredientList(kw);
|
||||
}
|
||||
|
||||
/* 渲染食材候选列表(传入已过滤关键词) */
|
||||
function renderIngredientList(kw){
|
||||
var listEl = document.getElementById('advIngredientList');
|
||||
if(!listEl) return;
|
||||
var filtered = INGREDIENT_OPTIONS.filter(function(it){
|
||||
if(!kw) return true;
|
||||
return (it.code||'').toLowerCase().indexOf(kw)>=0 || (it.name||'').toLowerCase().indexOf(kw)>=0;
|
||||
});
|
||||
if(filtered.length===0){
|
||||
listEl.innerHTML = '<div class="ingredient-picker__empty">未找到匹配的食材</div>';
|
||||
return;
|
||||
}
|
||||
listEl.innerHTML = filtered.map(function(it){
|
||||
var sel = advIngredientSelected && advIngredientSelected===it.code ? ' selected' : '';
|
||||
return '<div class="ingredient-picker__item'+sel+'" onclick="selectIngredient(\''+it.code+'\')">'
|
||||
+ '<span class="ingredient-picker__item-code">'+it.code+'</span>'
|
||||
+ '<span class="ingredient-picker__item-name">'+it.name+'</span>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/* 选中某条食材 */
|
||||
function selectIngredient(code){
|
||||
advIngredientSelected = code;
|
||||
updateIngredientTrigger();
|
||||
/* 关闭面板 */
|
||||
var picker = document.getElementById('advIngredientPicker');
|
||||
if(picker) picker.classList.remove('open');
|
||||
/* 清空搜索框,便于下次重新展开 */
|
||||
var searchInput = document.getElementById('advIngredientSearch');
|
||||
if(searchInput) searchInput.value = '';
|
||||
}
|
||||
|
||||
/* 更新触发器显示(空态/已选) */
|
||||
function updateIngredientTrigger(){
|
||||
var textEl = document.getElementById('advIngredientText');
|
||||
var clearEl = document.getElementById('advIngredientClear');
|
||||
var trigger = textEl ? textEl.parentElement : null;
|
||||
if(!textEl || !clearEl) return;
|
||||
if(!advIngredientSelected){
|
||||
textEl.textContent = '请选择食材';
|
||||
textEl.className = 'ingredient-picker__trigger-text is-empty';
|
||||
clearEl.style.display = 'none';
|
||||
if(trigger) trigger.classList.remove('has-value');
|
||||
return;
|
||||
}
|
||||
var found = INGREDIENT_OPTIONS.filter(function(it){return it.code===advIngredientSelected;})[0];
|
||||
var name = found ? found.name : advIngredientSelected;
|
||||
textEl.innerHTML = '<span class="ingredient-picker__trigger-code">'+advIngredientSelected+'</span>'
|
||||
+ '<span class="ingredient-picker__trigger-sep">/</span>'
|
||||
+ '<span class="ingredient-picker__trigger-name">'+name+'</span>';
|
||||
textEl.className = 'ingredient-picker__trigger-text';
|
||||
clearEl.style.display = 'flex';
|
||||
if(trigger) trigger.classList.add('has-value');
|
||||
}
|
||||
|
||||
/* 清除已选食材 */
|
||||
function clearIngredient(event){
|
||||
if(event){ event.stopPropagation(); }
|
||||
advIngredientSelected = null;
|
||||
updateIngredientTrigger();
|
||||
}
|
||||
|
||||
/* 点击食材下拉面板外部时关闭面板(挂在 document 上,只在弹窗打开期间生效) */
|
||||
document.addEventListener('click', function(e){
|
||||
var picker = document.getElementById('advIngredientPicker');
|
||||
if(!picker) return;
|
||||
if(!picker.classList.contains('open')) return;
|
||||
/* 点击在面板内则保留 */
|
||||
if(picker.contains(e.target)) return;
|
||||
picker.classList.remove('open');
|
||||
});
|
||||
|
||||
/* ========== 餐品分类管理 ========== */
|
||||
var nextDishCatId = 100;
|
||||
var dishCategories = [
|
||||
|
||||
@@ -300,6 +300,8 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<div class="filter-item"><label>取餐类型</label><select class="filter-select"><option>全部</option><option>正常取餐</option><option>余量取餐</option></select></div>
|
||||
<div class="filter-item"><label>人员类别</label><select class="filter-select"><option>全部</option><option>会员</option><option>非会员</option><option>员工</option><option>工作人员</option><option>外部人员</option></select></div>
|
||||
<div class="filter-item"><label>日期范围</label><input class="filter-input" type="date" style="min-width:130px;"> <span style="margin:0 4px;color:#999;">至</span> <input class="filter-input" type="date" style="min-width:130px;"></div>
|
||||
<!-- 交互注释:重量偏差范围筛选,单位克,支持负值(如 -20 ~ 20),按"实际重量-结算重量"的偏差值过滤记录;未复秤记录不参与该筛选 -->
|
||||
<div class="filter-item"><label>重量偏差</label><input class="filter-input" type="number" placeholder="最小值(克)" style="min-width:100px;"> <span style="margin:0 4px;color:#999;">至</span> <input class="filter-input" type="number" placeholder="最大值(克)" style="min-width:100px;"></div>
|
||||
<button class="btn btn-primary">查询</button>
|
||||
<button class="btn btn-default">重置</button>
|
||||
</div>
|
||||
@@ -318,18 +320,19 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<div class="table-card__content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>就餐记录号</th><th>姓名</th><th>员工编号</th><th>单位</th><th>部门</th><th>餐次</th><th>取餐方式</th><th>取餐类型</th><th>人员类别</th><th>取餐开始时间</th><th>取餐结束时间</th><th>取餐总数量</th><th>总金额</th><th>营养评分</th><th>操作</th></tr>
|
||||
<!-- 交互注释:结算重量=终端结算时电子秤计重;实际重量=结算后复秤电子秤上传的实际重量;重量偏差=实际重量-结算重量,偏差超过±10克(含)显示橙色预警、超过±15克显示红色异常,未复秤显示"—" -->
|
||||
<tr><th>就餐记录号</th><th>姓名</th><th>员工编号</th><th>单位</th><th>部门</th><th>餐次</th><th>取餐方式</th><th>取餐类型</th><th>人员类别</th><th>取餐开始时间</th><th>取餐结束时间</th><th>取餐总数量</th><th>结算重量(克)</th><th>实际重量(克)</th><th>重量偏差(克)</th><th>总金额</th><th>营养评分</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>M-20260428-001</td><td>张伟</td><td>EMP001</td><td>CQ能源</td><td>研发部</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:02</td><td>11:08</td><td><a class="link">3件</a></td><td>¥22.00</td><td><span class="tag tag-green">88分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-002</td><td>李娜</td><td>EMP002</td><td>CQ能源</td><td>市场部</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:08</td><td>11:14</td><td><a class="link">3件</a></td><td>¥18.00</td><td><span class="tag tag-green">92分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-003</td><td>王磊</td><td>EMP003</td><td>CQ能源</td><td>运营部</td><td>午餐</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-gray">非会员</span></td><td>11:15</td><td>11:20</td><td><a class="link">2件</a></td><td>¥28.00</td><td><span class="tag tag-orange">68分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-004</td><td>赵敏</td><td>EMP004</td><td>CQ能源</td><td>财务部</td><td>午餐</td><td><span class="tag tag-green">档口+餐线</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:22</td><td>11:30</td><td><a class="link">3件</a></td><td>¥25.50</td><td><span class="tag tag-green">94分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-005</td><td>陈刚</td><td>EMP005</td><td>CQ能源</td><td>人事部</td><td>早餐</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-gray">非会员</span></td><td>07:48</td><td>07:55</td><td><a class="link">3件</a></td><td>¥12.00</td><td><span class="tag tag-green">85分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-006</td><td>刘芳</td><td>EMP006</td><td>CQ能源</td><td>市场部</td><td>午餐</td><td><span class="tag tag-blue">外卖/外带</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:45</td><td>11:48</td><td><a class="link">2件</a></td><td>¥15.80</td><td><span class="tag tag-green">90分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>SP-001-001</td><td>张运维</td><td>EMP101</td><td>CQ能源</td><td>运维部</td><td>午餐</td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-blue">员工</span></td><td>14:05</td><td>14:06</td><td><a class="link">1件</a></td><td>¥5.00</td><td>—</td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>SP-001-003</td><td>王保安</td><td>—</td><td>CQ能源</td><td>安保部</td><td>午餐</td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-blue">工作人员</span></td><td>14:10</td><td>14:11</td><td><a class="link">1件</a></td><td>¥5.00</td><td>—</td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-007</td><td>林教授</td><td>—</td><td>外部单位</td><td>外部访客</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-purple">外部人员</span></td><td>12:10</td><td>12:18</td><td><a class="link">3件</a></td><td>¥26.50</td><td><span class="tag tag-green">86分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-001</td><td>张伟</td><td>EMP001</td><td>CQ能源</td><td>研发部</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:02</td><td>11:08</td><td><a class="link">3件</a></td><td>326.0</td><td>338.5</td><td><span class="tag tag-orange">+12.5</span></td><td>¥22.00</td><td><span class="tag tag-green">88分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-002</td><td>李娜</td><td>EMP002</td><td>CQ能源</td><td>市场部</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:08</td><td>11:14</td><td><a class="link">3件</a></td><td>365.0</td><td>363.0</td><td><span class="tag tag-green">-2.0</span></td><td>¥18.00</td><td><span class="tag tag-green">92分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-003</td><td>王磊</td><td>EMP003</td><td>CQ能源</td><td>运营部</td><td>午餐</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-gray">非会员</span></td><td>11:15</td><td>11:20</td><td><a class="link">2件</a></td><td>285.0</td><td>285.5</td><td><span class="tag tag-green">+0.5</span></td><td>¥28.00</td><td><span class="tag tag-orange">68分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-004</td><td>赵敏</td><td>EMP004</td><td>CQ能源</td><td>财务部</td><td>午餐</td><td><span class="tag tag-green">档口+餐线</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:22</td><td>11:30</td><td><a class="link">3件</a></td><td>420.0</td><td>447.0</td><td><span class="tag tag-red">+27.0</span></td><td>¥25.50</td><td><span class="tag tag-green">94分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-005</td><td>陈刚</td><td>EMP005</td><td>CQ能源</td><td>人事部</td><td>早餐</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-gray">非会员</span></td><td>07:48</td><td>07:55</td><td><a class="link">3件</a></td><td>390.0</td><td>—</td><td><span class="tag tag-gray">未复秤</span></td><td>¥12.00</td><td><span class="tag tag-green">85分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-006</td><td>刘芳</td><td>EMP006</td><td>CQ能源</td><td>市场部</td><td>午餐</td><td><span class="tag tag-blue">外卖/外带</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-orange">会员</span></td><td>11:45</td><td>11:48</td><td><a class="link">2件</a></td><td>380.0</td><td>362.0</td><td><span class="tag tag-red">-18.0</span></td><td>¥15.80</td><td><span class="tag tag-green">90分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>SP-001-001</td><td>张运维</td><td>EMP101</td><td>CQ能源</td><td>运维部</td><td>午餐</td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-blue">员工</span></td><td>14:05</td><td>14:06</td><td><a class="link">1件</a></td><td>220.0</td><td>—</td><td><span class="tag tag-gray">未复秤</span></td><td>¥5.00</td><td>—</td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>SP-001-003</td><td>王保安</td><td>—</td><td>CQ能源</td><td>安保部</td><td>午餐</td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td><span class="tag tag-blue">工作人员</span></td><td>14:10</td><td>14:11</td><td><a class="link">1件</a></td><td>195.0</td><td>198.0</td><td><span class="tag tag-green">+3.0</span></td><td>¥5.00</td><td>—</td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
<tr><td>M-20260428-007</td><td>林教授</td><td>—</td><td>外部单位</td><td>外部访客</td><td>午餐</td><td><span class="tag tag-blue">点餐取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td><span class="tag tag-purple">外部人员</span></td><td>12:10</td><td>12:18</td><td><a class="link">3件</a></td><td>475.0</td><td>478.0</td><td><span class="tag tag-green">+3.0</span></td><td>¥26.50</td><td><span class="tag tag-green">86分</span></td><td class="td-actions"><a class="link">查看用户信息</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -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;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -282,12 +443,12 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
<table>
|
||||
<thead><tr><th>设备编号</th><th>设备名称</th><th>设备类型</th><th>所属餐线</th><th>厂家品牌</th><th>责任人</th><th>网络类型</th><th>IP地址/设备ID</th><th>固件版本</th><th>安装日期</th><th>在线状态</th><th>设备健康评分</th><th>今日使用次数</th><th>最近心跳时间</th><th>运行天数</th><th>累计时长(分钟)</th><th>最近维护时间</th><th>故障记录数</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>DEV-001</td><td>1号点餐屏</td><td>点餐屏</td><td>—</td><td>海康威视</td><td>张运维</td><td>WiFi</td><td>192.168.1.101</td><td>v3.2.1</td><td>2024-08-15</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">98分</span></td><td>486</td><td>2026-04-22 11:30:22</td><td>615</td><td>362,400</td><td>2026-03-20</td><td>2</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-002</td><td>智能取餐柜-01</td><td>智能取餐柜</td><td>1号餐线</td><td>海尔智家</td><td>李运维</td><td>有线</td><td>192.168.1.102</td><td>v2.8.0</td><td>2024-08-15</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">100分</span></td><td>312</td><td>2026-04-22 11:30:18</td><td>615</td><td>498,720</td><td>2026-04-01</td><td>0</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-003</td><td>2号点餐屏</td><td>点餐屏</td><td>—</td><td>海康威视</td><td>张运维</td><td>WiFi</td><td>192.168.1.103</td><td>v3.2.1</td><td>2024-10-01</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">92分</span></td><td>398</td><td>2026-04-22 11:30:25</td><td>568</td><td>298,800</td><td>2026-04-10</td><td>1</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-004</td><td>智能取餐柜-02</td><td>智能取餐柜</td><td>2号餐线</td><td>海尔智家</td><td>李运维</td><td>有线</td><td>192.168.1.104</td><td>v2.7.3</td><td>2024-10-01</td><td><span class="tag tag-red">离线</span></td><td><span class="tag tag-red">0分</span></td><td>0</td><td>2026-04-22 09:18:44</td><td>568</td><td>423,600</td><td>2026-04-15</td><td>3</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-005</td><td>餐线秤台-00025</td><td>餐线秤台</td><td>1号餐线</td><td>大华技术</td><td>王运维</td><td>有线</td><td>DEV-00025</td><td>v1.5.2</td><td>2022-06-10</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">99分</span></td><td>1,280</td><td>2026-04-22 11:30:20</td><td>1,412</td><td>1,016,640</td><td>2026-03-01</td><td>0</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-006</td><td>体脂秤-01</td><td>体脂秤</td><td>—</td><td>华为智选</td><td>张运维</td><td>WiFi</td><td>192.168.1.106</td><td>v2.1.0</td><td>2023-12-01</td><td><span class="tag tag-orange">故障</span></td><td><span class="tag tag-red">45分</span></td><td>0</td><td>2026-04-22 10:55:08</td><td>873</td><td>78,540</td><td>2026-02-28</td><td>1</td><td class="td-actions"><a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-001</td><td>1号点餐屏</td><td>点餐屏</td><td>—</td><td>海康威视</td><td>张运维</td><td>WiFi</td><td>192.168.1.101</td><td>v3.2.1</td><td>2024-08-15</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">98分</span></td><td>486</td><td>2026-04-22 11:30:22</td><td>615</td><td>362,400</td><td>2026-03-20</td><td>2</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-002</td><td>智能取餐柜-01</td><td>智能取餐柜</td><td>1号餐线</td><td>海尔智家</td><td>李运维</td><td>有线</td><td>192.168.1.102</td><td>v2.8.0</td><td>2024-08-15</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">100分</span></td><td>312</td><td>2026-04-22 11:30:18</td><td>615</td><td>498,720</td><td>2026-04-01</td><td>0</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-003</td><td>2号点餐屏</td><td>点餐屏</td><td>—</td><td>海康威视</td><td>张运维</td><td>WiFi</td><td>192.168.1.103</td><td>v3.2.1</td><td>2024-10-01</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">92分</span></td><td>398</td><td>2026-04-22 11:30:25</td><td>568</td><td>298,800</td><td>2026-04-10</td><td>1</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-004</td><td>智能取餐柜-02</td><td>智能取餐柜</td><td>2号餐线</td><td>海尔智家</td><td>李运维</td><td>有线</td><td>192.168.1.104</td><td>v2.7.3</td><td>2024-10-01</td><td><span class="tag tag-red">离线</span></td><td><span class="tag tag-red">0分</span></td><td>0</td><td>2026-04-22 09:18:44</td><td>568</td><td>423,600</td><td>2026-04-15</td><td>3</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-005</td><td>餐线秤台-00025</td><td>餐线秤台</td><td>1号餐线</td><td>大华技术</td><td>王运维</td><td>有线</td><td>DEV-00025</td><td>v1.5.2</td><td>2022-06-10</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">99分</span></td><td>1,280</td><td>2026-04-22 11:30:20</td><td>1,412</td><td>1,016,640</td><td>2026-03-01</td><td>0</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
<tr><td>DEV-006</td><td>体脂秤-01</td><td>体脂秤</td><td>—</td><td>华为智选</td><td>张运维</td><td>WiFi</td><td>192.168.1.106</td><td>v2.1.0</td><td>2023-12-01</td><td><span class="tag tag-orange">故障</span></td><td><span class="tag tag-red">45分</span></td><td>0</td><td>2026-04-22 10:55:08</td><td>873</td><td>78,540</td><td>2026-02-28</td><td>1</td><td class="td-actions"><a class="link">设备概览</a> <a class="link">故障记录</a> <a class="link">远程重启</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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=>`<div class="ov-tab${t.key===OV_CURRENT_TAB?' active':''}" data-key="${t.key}" onclick="switchOvTab('${t.key}')">${t.icon} ${t.name}</div>`).join('');
|
||||
|
||||
const overlay=document.createElement('div');
|
||||
overlay.className='ov-modal-mask';
|
||||
overlay.id='ovModalMask';
|
||||
overlay.innerHTML=`
|
||||
<div class="ov-modal">
|
||||
<div class="ov-modal__header">
|
||||
<div class="ov-modal__title">设备概览 — ${devName}(${devCode} · ${devType})</div>
|
||||
<button class="ov-modal__close" onclick="closeDeviceOverview()">×</button>
|
||||
</div>
|
||||
<div class="ov-demo-tip">
|
||||
<span class="tip-icon">⚠️</span>
|
||||
原型演示说明:以下 Tab 仅用于集中演示各设备类型的概览效果,实际交付时根据设备类型自动显示对应的概览内容。
|
||||
</div>
|
||||
<div class="ov-tabs">${tabsHtml}</div>
|
||||
<div class="ov-modal__body" id="ovTabBody">${renderOvTabBody(OV_CURRENT_TAB)}</div>
|
||||
</div>`;
|
||||
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 `<div class="ov-placeholder">
|
||||
<div class="ov-placeholder__icon">${tab?tab.icon:'📺'}</div>
|
||||
<div class="ov-placeholder__text">${tab?tab.name:''}概览待设计</div>
|
||||
<div class="ov-placeholder__sub">原型演示阶段暂未展开,实际交付时根据设备类型展示对应概览效果</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ===== 智能货柜:柜子轮廓布局 + 储物状态(参考 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 `<div class="${cls}" onclick="showSlotHistory(${i})"><div class="ov-slot__id">${s.id}</div><div class="ov-slot__name">空闲</div></div>`;
|
||||
}
|
||||
return `<div class="${cls}" onclick="showSlotHistory(${i})">
|
||||
<div class="ov-slot__badge ${s.badge}"></div>
|
||||
<div class="ov-slot__id">${s.id}</div>
|
||||
<div class="ov-slot__name">${s.name}</div>
|
||||
<div class="ov-slot__remain">${s.remain}</div>
|
||||
<div class="ov-slot__expire"${s.expireColor?` style="color:${s.expireColor}"`:''}>${s.expire}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `
|
||||
<div class="ov-cabinet-header">
|
||||
<div style="font-size:var(--font-base);font-weight:600;color:var(--text-primary)">1号智能货柜</div>
|
||||
<div class="ov-cabinet-status">4.1℃ · <span style="color:var(--success)">在线</span> · 已占用 7/10 格口</div>
|
||||
</div>
|
||||
<div class="ov-cabinet-grid">${slotsHtml}</div>
|
||||
<div class="ov-cabinet-legend">
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot idle"></div>空闲</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot storing"></div>储物中</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot warn"></div>临期预警</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot err"></div>过期预警</div>
|
||||
<div class="ov-legend__item" style="margin-left:auto;color:var(--text-placeholder)">点击格口可查看历史记录</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ===== 餐盘柜:格口 + 会员绑定状态 ===== */
|
||||
/* 格口 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 `<div class="ov-slot ov-slot--empty" onclick="showPlateSlot(${i})"><div class="ov-slot__id">${s.id}</div><div class="ov-slot__name">未绑定</div></div>`;
|
||||
}
|
||||
const memberHtml=s.member
|
||||
?`<div class="ov-slot__name">${s.member.name}</div><div class="ov-slot__level ${s.member.levelCls}">${s.member.level}</div>`
|
||||
:`<div class="ov-slot__name" style="color:var(--text-placeholder)">停用</div>`;
|
||||
return `<div class="ov-slot ${meta.cls}" onclick="showPlateSlot(${i})">
|
||||
${meta.badge?`<div class="ov-slot__badge ${meta.badge}"></div>`:''}
|
||||
<div class="ov-slot__id">${s.id}</div>
|
||||
${memberHtml}
|
||||
<div class="ov-slot__expire">${meta.text}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `
|
||||
<div class="ov-cabinet-header">
|
||||
<div style="font-size:var(--font-base);font-weight:600;color:var(--text-primary)">1号餐盘柜 · 每格口配专属消毒餐盘供固定会员使用</div>
|
||||
<div class="ov-cabinet-status">36.8℃(消毒保温)· <span style="color:var(--success)">在线</span> · 已绑定 ${boundCount}/${totalCount} 格口</div>
|
||||
</div>
|
||||
<div class="ov-cabinet-grid">${slotsHtml}</div>
|
||||
<div class="ov-cabinet-legend">
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot storing"></div>已绑定</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot using"></div>使用中(盘子取出)</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot idle"></div>未绑定</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot disabled"></div>异常停用</div>
|
||||
<div class="ov-legend__item" style="margin-left:auto;color:var(--text-placeholder)">点击格口可查看绑定详情并分配/解绑</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ===== 餐盘柜格口详情弹窗(叠加在设备概览之上) ===== */
|
||||
function showPlateSlot(idx){
|
||||
const s=PLATE_SLOTS[idx];
|
||||
const meta=PLATE_STATUS_META[s.status];
|
||||
const m=s.member;
|
||||
const statusTag=s.status==='bound'?'<span class="tag tag-green">已绑定</span>':s.status==='using'?'<span class="tag tag-blue">使用中</span>':s.status==='free'?'<span class="tag tag-gray">未绑定</span>':'<span class="tag tag-red">异常停用</span>';
|
||||
let html=`<div class="plate-detail__section"><div class="plate-detail__section-title">格口信息</div><div class="plate-detail__grid">
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">使用状态</span><span class="plate-detail__value">${statusTag}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">所属柜</span><span class="plate-detail__value">1号餐盘柜</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">格口</span><span class="plate-detail__value">${s.id}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">绑定时间</span><span class="plate-detail__value">${s.bindTime}</span></div>
|
||||
</div></div>`;
|
||||
if(m){
|
||||
html+=`<div class="plate-detail__section"><div class="plate-detail__section-title">绑定会员信息</div><div class="plate-detail__grid">
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">会员姓名</span><span class="plate-detail__value">${m.name}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">会员等级</span><span class="plate-detail__value"><span class="ov-slot__level ${m.levelCls}">${m.level}</span></span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">手机号</span><span class="plate-detail__value">${m.phone}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">用户编号</span><span class="plate-detail__value" style="font-family:monospace">${m.code}</span></div>
|
||||
</div></div>`;
|
||||
html+=`<div class="plate-detail__section"><div class="plate-detail__section-title">使用情况</div><div class="plate-detail__grid">
|
||||
<div class="plate-detail__field" style="grid-column:1/-1"><span class="plate-detail__label">最后使用状态</span><span class="plate-detail__value">${s.lastUse}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">累计使用次数</span><span class="plate-detail__value">${s.useCount} 次</span></div>
|
||||
</div></div>`;
|
||||
}else{
|
||||
html+=`<div class="plate-detail__section"><div class="plate-detail__section-title">使用情况</div><div class="plate-detail__grid">
|
||||
<div class="plate-detail__field" style="grid-column:1/-1"><span class="plate-detail__label">最后使用状态</span><span class="plate-detail__value">${s.lastUse}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">累计使用次数</span><span class="plate-detail__value">${s.useCount} 次</span></div>
|
||||
</div></div>`;
|
||||
}
|
||||
/* 操作按钮:未绑定可分配;已绑定/使用中可解绑;异常停用不可操作 */
|
||||
let footer='';
|
||||
if(s.status==='free'){
|
||||
footer=`<button class="btn btn-primary" onclick="openPlateAssign(${idx})">格口分配</button>`;
|
||||
}else if(m){
|
||||
footer=`<button class="btn btn-danger" onclick="openPlateUnbind(${idx})">用户解绑</button>`;
|
||||
}else{
|
||||
footer=`<span style="font-size:var(--font-sm);color:var(--text-placeholder);align-self:center;">格口异常停用中,维修恢复后方可分配</span>`;
|
||||
}
|
||||
const mask=document.createElement('div');
|
||||
mask.className='slot-his-mask';
|
||||
mask.id='plateSlotMask';
|
||||
mask.innerHTML=`<div class="slot-his-modal" style="width:640px;height:auto;max-height:78vh">
|
||||
<div class="slot-his-modal__header"><div class="slot-his-modal__title">餐盘格口详情 — ${s.id}</div><button class="slot-his-modal__close" onclick="closePlateSlot()">×</button></div>
|
||||
<div class="slot-his-modal__body">${html}</div>
|
||||
<div class="slot-his-modal__footer" style="display:flex;justify-content:flex-end;gap:12px;">${footer}</div>
|
||||
</div>`;
|
||||
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=`<div class="member-picker">
|
||||
<div class="member-picker__header"><div class="member-picker__title">格口分配 — 选择会员(${PLATE_SLOTS[idx].id})</div><button class="member-picker__close" onclick="closePlateAssign()">×</button></div>
|
||||
<div class="member-picker__search"><input type="text" placeholder="搜索会员姓名 / 手机号" oninput="filterPlateMembers(this.value)"></div>
|
||||
<div class="member-picker__body" id="plateMemberList"></div>
|
||||
<div class="member-picker__footer">
|
||||
<button class="btn btn-default" onclick="closePlateAssign()">取消</button>
|
||||
<button class="btn btn-primary" onclick="confirmPlateAssign()">确认分配</button>
|
||||
</div>
|
||||
</div>`;
|
||||
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='<div class="member-picker__empty">未找到匹配的会员</div>';
|
||||
return;
|
||||
}
|
||||
wrap.innerHTML=list.map(m=>{
|
||||
const globalIdx=PLATE_MEMBERS.indexOf(m);
|
||||
const selected=PLATE_ASSIGN_SELECTED===m;
|
||||
return `<div class="member-picker__item${selected?' selected':''}" onclick="selectPlateMember(${globalIdx})">
|
||||
<div class="member-picker__avatar">${m.name.charAt(0)}</div>
|
||||
<div class="member-picker__info">
|
||||
<div class="member-picker__name">${m.name} <span class="ov-slot__level ${m.levelCls}" style="margin-left:4px">${m.level}</span></div>
|
||||
<div class="member-picker__meta">${m.phone} · ${m.dept}</div>
|
||||
</div>
|
||||
${selected?'<span style="color:var(--primary);font-size:var(--font-lg)">✓</span>':''}
|
||||
</div>`;
|
||||
}).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=`<div class="ov-confirm">
|
||||
<div class="ov-confirm__icon">⚠️</div>
|
||||
<div class="ov-confirm__msg">确认解绑该格口的会员?</div>
|
||||
<div class="ov-confirm__sub">格口 ${s.id} · ${s.member.name}(${s.member.level}),解绑后该会员餐盘回收,格口恢复为未绑定状态</div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:12px;">
|
||||
<button class="btn btn-default" onclick="closePlateUnbind()">取消</button>
|
||||
<button class="btn btn-danger" onclick="confirmPlateUnbind(${idx})">确认解绑</button>
|
||||
</div>
|
||||
</div>`;
|
||||
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 `<div class="ov-slot ov-slot--empty" onclick="showStockSlot('${kind}',${i})"><div class="ov-slot__id">${s.id}</div><div class="ov-slot__name">空闲</div></div>`;
|
||||
}
|
||||
const cls=s.status==='offline'?'ov-slot offline':'ov-slot occupied';
|
||||
const badge=s.status==='offline'?'<div class="ov-slot__badge gray"></div>':'<div class="ov-slot__badge green"></div>';
|
||||
const qtyHtml=s.status==='offline'?'<div class="ov-slot__expire" style="color:var(--text-placeholder)">离线</div>':`<div class="ov-slot__remain">${s.qty}kg</div><div class="ov-slot__expire">入库 ${s.inTime.slice(5,10)}</div>`;
|
||||
return `<div class="${cls}" onclick="showStockSlot('${kind}',${i})">
|
||||
${badge}
|
||||
<div class="ov-slot__id">${s.id}</div>
|
||||
<div class="ov-slot__name">${s.itemName}</div>
|
||||
${qtyHtml}
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `
|
||||
<div class="ov-cabinet-header">
|
||||
<div style="font-size:var(--font-base);font-weight:600;color:var(--text-primary)">${cab.name}(${cab.code})· <span class="tag ${cab.typeTag}">${cab.type}</span></div>
|
||||
<div class="ov-cabinet-status">${cab.status} · 已存放 ${occupied}/${cab.slots.length} 格口</div>
|
||||
</div>
|
||||
<div class="ov-cabinet-grid">${slotsHtml}</div>
|
||||
<div class="ov-cabinet-legend">
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot storing"></div>储物中</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot idle"></div>空闲</div>
|
||||
<div class="ov-legend__item"><div class="ov-legend__dot offline"></div>离线</div>
|
||||
<div class="ov-legend__item" style="margin-left:auto;color:var(--text-placeholder)">点击格口可查看存放详情与存取历史</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* 净菜/毛菜格口详情弹窗:存放信息 + 存取历史 */
|
||||
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'?'<span class="tag tag-green">储物中</span>':'<span class="tag tag-gray">离线</span>';
|
||||
let html=`<div class="plate-detail__section"><div class="plate-detail__section-title">存放信息</div><div class="plate-detail__grid">
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">所属柜</span><span class="plate-detail__value">${cab.name}(${cab.code})</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">格口</span><span class="plate-detail__value">${s.id}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">格口状态</span><span class="plate-detail__value">${statusTag}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">存放物品</span><span class="plate-detail__value">${s.itemName}<span class="tag ${cab.typeTag}" style="margin-left:6px">${cab.type==='净菜柜'?'净菜':'毛菜'}</span></span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">当前数量(kg)</span><span class="plate-detail__value">${s.qty.toFixed(1)}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">操作人</span><span class="plate-detail__value">${s.op}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">入库时间</span><span class="plate-detail__value">${s.inTime}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">关联入库单</span><span class="plate-detail__value" style="color:var(--primary)">${s.inOrder}</span></div>
|
||||
</div></div>`;
|
||||
const rows=(s.history||[]).slice().sort((a,b)=>b.time.localeCompare(a.time));
|
||||
if(rows.length){
|
||||
html+=`<div class="plate-detail__section"><div class="plate-detail__section-title">存取历史</div>
|
||||
<table class="slot-his-table"><thead><tr><th>操作时间</th><th>操作类型</th><th>操作前(kg)</th><th>操作后(kg)</th><th>余量变化</th><th>操作人</th><th>关联单号</th><th>备注</th></tr></thead><tbody>
|
||||
${rows.map(r=>{
|
||||
const isPos=r.type==='存入';
|
||||
const delta=Math.abs(r.after-r.before).toFixed(1);
|
||||
const typeHtml=isPos?'<span class="tag tag-blue">存入</span>':'<span class="tag tag-orange">取出</span>';
|
||||
return `<tr><td>${r.time}</td><td>${typeHtml}</td><td>${r.before.toFixed(1)}</td><td>${r.after.toFixed(1)}</td><td><span class="slot-his-delta ${isPos?'pos':'neg'}">${isPos?'+':'-'}${delta} ${isPos?'↑':'↓'}</span></td><td>${r.op}</td><td><a class="link">${r.order}</a></td><td>${r.remark||'—'}</td></tr>`;
|
||||
}).join('')}
|
||||
</tbody></table></div>`;
|
||||
}
|
||||
const mask=document.createElement('div');
|
||||
mask.className='slot-his-mask';
|
||||
mask.id='stockSlotMask';
|
||||
mask.innerHTML=`<div class="slot-his-modal" style="width:960px;height:auto;max-height:78vh">
|
||||
<div class="slot-his-modal__header"><div class="slot-his-modal__title">格口存放详情 — ${s.id}(${cab.name})</div><button class="slot-his-modal__close" onclick="closeStockSlot()">×</button></div>
|
||||
<div class="slot-his-modal__body">${html}</div>
|
||||
</div>`;
|
||||
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 `
|
||||
<div class="ov-energy-card">
|
||||
<span class="ov-energy-card__entry" onclick="toggleEnergyEditOv('${type}')">✏️ 录入</span>
|
||||
<div class="ov-energy-card__header"><span class="ov-energy-card__icon">${e.icon}</span><span class="ov-energy-card__title">${e.title}</span><span class="ov-energy-card__meter">表号:${e.meter}</span></div>
|
||||
<div class="ov-energy-card__value" style="color:${e.color}" id="energyValue_${type}">${e.remain} <span>${unit}</span></div>
|
||||
<div class="ov-energy-card__rows">
|
||||
<div class="ov-energy-card__row"><span class="ov-energy-card__row-label">今日用量</span><span class="ov-energy-card__row-value" id="energyUse_${type}">${e.todayUse} ${unit}</span></div>
|
||||
<div class="ov-energy-card__row"><span class="ov-energy-card__row-label">今日费用</span><span class="ov-energy-card__row-value" id="energyCost_${type}">¥${e.todayCost.toFixed(1)}</span></div>
|
||||
<div class="ov-energy-card__row"><span class="ov-energy-card__row-label">状态</span><span class="ov-energy-card__row-value"><span class="tag tag-green">正常</span></span></div>
|
||||
<div class="ov-energy-card__row"><span class="ov-energy-card__row-label">${e.rate}</span><span class="ov-energy-card__row-value">${e.rateVal}</span></div>
|
||||
</div>
|
||||
<div class="ov-energy-card__hint">${e.hint}</div>
|
||||
<div class="ov-energy-edit" id="energyEdit_${type}" style="display:none;">
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">当前余量(${unit})</span><input type="number" step="0.1" id="energyInputRemain_${type}" value="${e.remain}"></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">今日用量(${unit})</span><input type="number" step="0.1" id="energyInputUse_${type}" value="${e.todayUse}"></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">今日费用(元)</span><input type="number" step="0.1" id="energyInputCost_${type}" value="${e.todayCost}"></div>
|
||||
<div class="ov-energy-edit__actions">
|
||||
<button class="btn btn-default" onclick="cancelEnergyEditOv('${type}')">取消</button>
|
||||
<button class="btn btn-primary" onclick="saveEnergyEditOv('${type}')">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-section-title">日使用量统计</div>
|
||||
<div id="energyDaily_${type}"></div>`;
|
||||
}
|
||||
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='<div style="overflow-x:auto"><table class="ov-daily-table"><thead><tr><th>记录日期</th><th>用量('+unit+')</th><th>费用(元)</th><th>峰值时段</th><th>采集方式</th><th>操作</th></tr></thead><tbody>';
|
||||
pageRows.forEach(r=>{
|
||||
html+=`<tr><td>${r.date}</td><td>${r.use}</td><td>¥${r.cost.toFixed(1)}</td><td>${r.peak}</td><td>${r.op}</td><td class="td-actions"><a class="link" onclick="showOvToast('单日明细待开发(占位)')">详情</a></td></tr>`;
|
||||
});
|
||||
html+='</tbody></table></div>';
|
||||
html+=`<div class="ov-daily-pagination"><span class="ov-daily-pagination__total">共 ${total} 条</span>`;
|
||||
html+=`<button class="page-btn" onclick="goEnergyPage('${type}',${page-1})" ${page<=1?'disabled style="opacity:.4;cursor:not-allowed"':''}>‹</button>`;
|
||||
for(let i=1;i<=totalPages;i++){
|
||||
html+=`<button class="page-btn${i===page?' active':''}" onclick="goEnergyPage('${type}',${i})">${i}</button>`;
|
||||
}
|
||||
html+=`<button class="page-btn" onclick="goEnergyPage('${type}',${page+1})" ${page>=totalPages?'disabled style="opacity:.4;cursor:not-allowed"':''}>›</button></div>`;
|
||||
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} <span>${e.unit}</span>`;
|
||||
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=`<div class="slot-his-summary">
|
||||
<div class="slot-his-summary__item"><span class="label">所属柜:</span><strong>${d.cabinet}</strong></div>
|
||||
<div class="slot-his-summary__item"><span class="label">格口:</span><strong>${d.slot}</strong></div>
|
||||
<div class="slot-his-summary__item"><span class="label">累计存入:</span><span class="slot-his-count in">${cinCount} 次</span></div>
|
||||
<div class="slot-his-summary__item"><span class="label">累计取出/取用:</span><span class="slot-his-count out">${coutCount} 次</span></div>
|
||||
</div>`;
|
||||
const mask=document.createElement('div');
|
||||
mask.className='slot-his-mask';
|
||||
mask.id='slotHisMask';
|
||||
mask.innerHTML=`<div class="slot-his-modal">
|
||||
<div class="slot-his-modal__header"><div class="slot-his-modal__title">格口历史记录 — ${d.slot}</div><button class="slot-his-modal__close" onclick="closeSlotHistory()">×</button></div>
|
||||
<div class="slot-his-modal__body">${summary}<div id="ovHisTableWrap"></div></div>
|
||||
<div class="slot-his-modal__footer"><div id="ovHisFooterWrap"></div></div>
|
||||
</div>`;
|
||||
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='<div style="overflow-x:auto"><table class="slot-his-table"><thead><tr><th>操作时间</th><th>存放物品</th><th>操作类型</th><th>操作前(kg)</th><th>操作后(kg)</th><th>余量变化</th><th>操作人</th><th>关联单号</th><th>备注</th></tr></thead><tbody>';
|
||||
pageRows.forEach(r=>{
|
||||
const isPos=r.type==='存入';
|
||||
const delta=Math.abs(r.after-r.before).toFixed(1);
|
||||
const deltaHtml=`<span class="slot-his-delta ${isPos?'pos':'neg'}">${isPos?'+':'-'}${delta} ${isPos?'↑':'↓'}</span>`;
|
||||
const typeHtml=isPos?'<span class="tag tag-blue">存入</span>':(r.type==='取出'?'<span class="tag tag-orange">取出</span>':'<span class="tag tag-orange">取用</span>');
|
||||
const orderHtml=r.order==='—'?'—':`<a class="link">${r.order}</a>`;
|
||||
html+=`<tr><td>${r.time}</td><td>${r.name}</td><td>${typeHtml}</td><td>${r.before.toFixed(1)}</td><td>${r.after.toFixed(1)}</td><td>${deltaHtml}</td><td>${r.op}</td><td>${orderHtml}</td><td>${r.remark||'—'}</td></tr>`;
|
||||
});
|
||||
html+='</tbody></table></div>';
|
||||
wrap.innerHTML=html;
|
||||
if(footer){
|
||||
let pg=`<div class="slot-his-pagination"><span class="slot-his-pagination__total">共 ${total} 条</span>`;
|
||||
pg+=`<button class="page-btn" onclick="goOvHistoryPage(${OV_HIS_CURRENT.page-1})" ${OV_HIS_CURRENT.page<=1?'disabled style="opacity:.4;cursor:not-allowed"':''}>‹</button>`;
|
||||
for(let i=1;i<=totalPages;i++){
|
||||
pg+=`<button class="page-btn${i===OV_HIS_CURRENT.page?' active':''}" onclick="goOvHistoryPage(${i})">${i}</button>`;
|
||||
}
|
||||
pg+=`<button class="page-btn" onclick="goOvHistoryPage(${OV_HIS_CURRENT.page+1})" ${OV_HIS_CURRENT.page>=totalPages?'disabled style="opacity:.4;cursor:not-allowed"':''}>›</button></div>`;
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -544,12 +544,12 @@ tbody td.td-actions{overflow:visible;}
|
||||
<tr><th>会员卡号</th><th>姓名</th><th>用户编号</th><th>会员等级</th><th>当前积分</th><th>累计消费(元)</th><th>本月消费</th><th>开通日期</th><th>有效期至</th><th>最近活跃</th><th>会员状态</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>MB-202401-001</td><td>张伟</td><td>EMP001</td><td><span class="tag tag-red">钻石</span></td><td>8,628</td><td>12,860</td><td>¥1,286</td><td>2024-01-15</td><td>2027-01-14</td><td>2026-04-28 11:32</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202405-008</td><td>李娜</td><td>EMP002</td><td><span class="tag tag-orange">金卡</span></td><td>3,286</td><td>4,820</td><td>¥682</td><td>2024-05-08</td><td>2027-05-07</td><td>2026-04-28 12:08</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202508-015</td><td>赵敏</td><td>EMP004</td><td><span class="tag tag-blue">银卡</span></td><td>1,420</td><td>2,180</td><td>¥486</td><td>2025-08-12</td><td>2027-08-11</td><td>2026-04-28 11:30</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202510-026</td><td>刘芳</td><td>EMP006</td><td><span class="tag tag-blue">银卡</span></td><td>986</td><td>1,520</td><td>¥320</td><td>2025-10-20</td><td>2027-10-19</td><td>2026-04-28 11:48</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202312-003</td><td>孙磊</td><td>EMP010</td><td><span class="tag tag-orange">金卡</span></td><td>5,120</td><td>7,860</td><td>¥0</td><td>2023-12-08</td><td>2026-12-07</td><td>2026-04-15 12:20</td><td><span class="tag tag-orange">即将到期</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202402-005</td><td>周明</td><td>EMP008</td><td><span class="tag tag-red">钻石</span></td><td>12,486</td><td>16,820</td><td>¥0</td><td>2024-02-10</td><td>2027-02-09</td><td>2026-03-28 12:15</td><td><span class="tag tag-gray">休眠</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a></td></tr>
|
||||
<tr><td>MB-202401-001</td><td>张伟</td><td>EMP001</td><td><span class="tag tag-red">钻石</span></td><td>8,628</td><td>12,860</td><td>¥1,286</td><td>2024-01-15</td><td>2027-01-14</td><td>2026-04-28 11:32</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
<tr><td>MB-202405-008</td><td>李娜</td><td>EMP002</td><td><span class="tag tag-orange">金卡</span></td><td>3,286</td><td>4,820</td><td>¥682</td><td>2024-05-08</td><td>2027-05-07</td><td>2026-04-28 12:08</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
<tr><td>MB-202508-015</td><td>赵敏</td><td>EMP004</td><td><span class="tag tag-blue">银卡</span></td><td>1,420</td><td>2,180</td><td>¥486</td><td>2025-08-12</td><td>2027-08-11</td><td>2026-04-28 11:30</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
<tr><td>MB-202510-026</td><td>刘芳</td><td>EMP006</td><td><span class="tag tag-blue">银卡</span></td><td>986</td><td>1,520</td><td>¥320</td><td>2025-10-20</td><td>2027-10-19</td><td>2026-04-28 11:48</td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
<tr><td>MB-202312-003</td><td>孙磊</td><td>EMP010</td><td><span class="tag tag-orange">金卡</span></td><td>5,120</td><td>7,860</td><td>¥0</td><td>2023-12-08</td><td>2026-12-07</td><td>2026-04-15 12:20</td><td><span class="tag tag-orange">即将到期</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
<tr><td>MB-202402-005</td><td>周明</td><td>EMP008</td><td><span class="tag tag-red">钻石</span></td><td>12,486</td><td>16,820</td><td>¥0</td><td>2024-02-10</td><td>2027-02-09</td><td>2026-03-28 12:15</td><td><span class="tag tag-gray">休眠</span></td><td class="td-actions"><a class="link" data-action="edit">编辑</a> <a class="link" data-action="detail">详情</a> <a class="link" data-action="face">人脸采集</a> <a class="link" data-action="benefits">会员权益</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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 = ''
|
||||
+ '<table class="meal-history-table">'
|
||||
+ ' <thead><tr><th>所属柜子</th><th>格口</th><th>绑定时间</th><th>最后使用时间</th><th>最后使用状态</th><th>累计使用次数</th></tr></thead>'
|
||||
+ ' <tbody><tr>'
|
||||
+ ' <td>' + locker.locker + '</td>'
|
||||
+ ' <td>' + locker.cell + '</td>'
|
||||
+ ' <td>' + locker.bindTime + '</td>'
|
||||
+ ' <td>' + locker.lastUseTime + '</td>'
|
||||
+ ' <td><span class="tag ' + statusTag + '">' + locker.lastUseStatus + '</span></td>'
|
||||
+ ' <td>' + locker.useCount.toLocaleString() + ' 次</td>'
|
||||
+ ' </tr></tbody>'
|
||||
+ '</table>';
|
||||
} else {
|
||||
lockerHtml = ''
|
||||
+ '<div style="padding:32px 0;text-align:center;">'
|
||||
+ ' <div style="font-size:32px;margin-bottom:8px;">🗄️</div>'
|
||||
+ ' <div style="font-size:var(--font-sm);color:var(--text-secondary);">暂未绑定专属餐盘柜,可在餐盘柜管理中为该会员分配格口</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
var html = ''
|
||||
+ '<div style="background:#FFFBF0;border:1px solid #FFE7BA;border-radius:var(--radius-md);padding:10px 14px;margin-bottom:16px;font-size:var(--font-sm);color:#AD6800;">'
|
||||
+ ' 💡 会员权益体系持续规划中,当前已上线权益:专属餐盘柜'
|
||||
+ '</div>'
|
||||
+ '<div style="font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:8px;">当前等级权益(' + levelText + '会员)</div>'
|
||||
+ '<div style="font-size:var(--font-sm);color:var(--text-secondary);line-height:1.8;margin-bottom:20px;padding:12px 14px;background:#FAFAFA;border-radius:var(--radius-md);">'
|
||||
+ (vipLevelBenefitsMap[levelText] || vipLevelBenefitsMap['普通'])
|
||||
+ '</div>'
|
||||
+ '<div style="font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:8px;">🍽️ 专属餐盘柜信息</div>'
|
||||
+ lockerHtml;
|
||||
|
||||
Modal.open('会员权益 — ' + name + '(' + cardNo + ')', html, '', '<button class="btn btn-default" onclick="Modal.close()">关闭</button>');
|
||||
}
|
||||
|
||||
/* ========== 编辑会员信息弹窗(Tab3) ========== */
|
||||
function openVipEditModal(row) {
|
||||
var tds = row.querySelectorAll('td');
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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;}
|
||||
<tr class="detail-row" id="detail-1">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-XLHU-080K-20260416-001</td><td>西兰花</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td>¥4.5</td><td>¥360</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-QCAI-120K-20260416-002</td><td>芹菜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td>¥2.5</td><td>¥300</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-XLHU-080K-20260416-001</td><td>西兰花</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td>40 kg</td><td>¥4.5</td><td>¥360</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-QCAI-120K-20260416-002</td><td>芹菜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td>60 kg</td><td>¥2.5</td><td>¥300</td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer">合计:2 种食材,200 kg,¥660</div>
|
||||
<div class="sub-table__footer">合计:2 种食材,100 kg,¥660</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 采购单2:待发货 -->
|
||||
@@ -335,14 +337,14 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<tr class="detail-row" id="detail-2">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-BOCA-050K-20260415-002</td><td>菠菜</td><td>蔬菜类</td><td>50</td><td>500g/袋</td><td>¥2.5</td><td>¥125</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-BCAI-080K-20260415-003</td><td>白菜</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td>¥1.8</td><td>¥144</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-HLUB-120K-20260415-004</td><td>胡萝卜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td>¥1.8</td><td>¥216</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-BOCA-050K-20260415-002</td><td>菠菜</td><td>蔬菜类</td><td>50</td><td>500g/袋</td><td>25 kg</td><td>¥2.5</td><td>¥125</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-BCAI-080K-20260415-003</td><td>白菜</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td>40 kg</td><td>¥1.8</td><td>¥144</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-HLUB-120K-20260415-004</td><td>胡萝卜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td>60 kg</td><td>¥1.8</td><td>¥216</td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer">合计:3 种食材,250 kg,¥485</div>
|
||||
<div class="sub-table__footer">合计:3 种食材,125 kg,¥485</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 采购单3:待收货 -->
|
||||
@@ -362,13 +364,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<tr class="detail-row" id="detail-3">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-XR01-MT-ZLRJ-080K-20260415-005</td><td>猪里脊</td><td>肉禽类</td><td>80</td><td>500g/袋</td><td>¥28</td><td>¥2,240</td><td>—</td><td>需冷链运输</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-XR01-MT-ZWUA-240K-20260415-006</td><td>猪五花</td><td>肉禽类</td><td>240</td><td>500g/袋</td><td>¥26</td><td>¥6,240</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-XR01-MT-ZLRJ-080K-20260415-005</td><td>猪里脊</td><td>肉禽类</td><td>80</td><td>500g/袋</td><td>40 kg</td><td>¥28</td><td>¥2,240</td><td>—</td><td>需冷链运输</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-XR01-MT-ZWUA-240K-20260415-006</td><td>猪五花</td><td>肉禽类</td><td>240</td><td>500g/袋</td><td>120 kg</td><td>¥26</td><td>¥6,240</td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer">合计:2 种食材,320 kg,¥8,480</div>
|
||||
<div class="sub-table__footer">合计:2 种食材,160 kg,¥8,480</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 采购单4:已完成 -->
|
||||
@@ -388,14 +390,14 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<tr class="detail-row" id="detail-4">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-CAYO-060K-20260414-001</td><td>草鱼</td><td>水产类</td><td>60</td><td>500g/袋</td><td>¥18</td><td>¥1,080</td><td>60</td><td>活鲜运输</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-JIYU-080K-20260414-002</td><td>鲫鱼</td><td>水产类</td><td>80</td><td>500g/袋</td><td>¥16</td><td>¥1,280</td><td>80</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-XIAA-050K-20260414-003</td><td>虾</td><td>水产类</td><td>50</td><td>500g/袋</td><td>¥30</td><td>¥1,500</td><td>—</td><td>冰鲜</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-CAYO-060K-20260414-001</td><td>草鱼</td><td>水产类</td><td>60</td><td>500g/袋</td><td>30 kg</td><td>¥18</td><td>¥1,080</td><td>60</td><td>活鲜运输</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-JIYU-080K-20260414-002</td><td>鲫鱼</td><td>水产类</td><td>80</td><td>500g/袋</td><td>40 kg</td><td>¥16</td><td>¥1,280</td><td>80</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-HS01-SF-XIAA-050K-20260414-003</td><td>虾</td><td>水产类</td><td>50</td><td>500g/袋</td><td>25 kg</td><td>¥30</td><td>¥1,500</td><td>—</td><td>冰鲜</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer">合计:3 种食材,190 kg,¥3,860</div>
|
||||
<div class="sub-table__footer">合计:3 种食材,95 kg,¥3,860</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 采购单5:已取消 -->
|
||||
@@ -415,13 +417,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<tr class="detail-row" id="detail-5">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY02-DG-DAMI-1000-20260413-001</td><td>大米</td><td>粮油类</td><td>1000</td><td>25kg/袋</td><td>¥3.5</td><td>¥3,500</td><td>—</td><td>东北五常大米</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY02-DG-HSYO-168K-20260413-002</td><td>花生油</td><td>粮油类</td><td>168</td><td>5kg/桶</td><td>¥16</td><td>¥2,688</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY02-DG-DAMI-1000-20260413-001</td><td>大米</td><td>粮油类</td><td>1000</td><td>25kg/袋</td><td>25000 kg</td><td>¥3.5</td><td>¥3,500</td><td>—</td><td>东北五常大米</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY02-DG-HSYO-168K-20260413-002</td><td>花生油</td><td>粮油类</td><td>168</td><td>5kg/桶</td><td>840 kg</td><td>¥16</td><td>¥2,688</td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer">合计:2 种食材,1168 kg,¥6,188</div>
|
||||
<div class="sub-table__footer">合计:2 种食材,25840 kg,¥6,188</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 采购单6:待确认(系统自动生成,缺供应商与单价) -->
|
||||
@@ -441,13 +443,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<tr class="detail-row" id="detail-6">
|
||||
<td colspan="11">
|
||||
<table class="sub-table">
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-XLHU-080K-20260416-007</td><td>西兰花</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td><span style="color:var(--danger);">待补充</span></td><td>—</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-QCAI-120K-20260416-008</td><td>芹菜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td><span style="color:var(--danger);">待补充</span></td><td>—</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-XLHU-080K-20260416-007</td><td>西兰花</td><td>蔬菜类</td><td>80</td><td>500g/袋</td><td>40 kg</td><td><span style="color:var(--danger);">待补充</span></td><td>—</td><td>—</td><td>—</td></tr>
|
||||
<tr><td style="font-size:10px;color:#999;">ST-EX-LY01-VG-QCAI-120K-20260416-008</td><td>芹菜</td><td>蔬菜类</td><td>120</td><td>500g/袋</td><td>60 kg</td><td><span style="color:var(--danger);">待补充</span></td><td>—</td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sub-table__footer" style="color:var(--warning);">合计:2 种食材,200 kg,单价待补充</div>
|
||||
<div class="sub-table__footer" style="color:var(--warning);">合计:2 种食材,100 kg,单价待补充</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -477,7 +479,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 系统生成订单的补全提示条(仅"立即确认"入口显示) -->
|
||||
<div id="confirmHintBar" style="display:none;margin-bottom:16px;padding:12px 16px;background:#FFF7E6;border:1px solid #FFE58F;border-left:4px solid var(--warning);border-radius:var(--radius-sm);align-items:center;gap:8px;font-size:var(--font-sm);color:var(--text-primary);">
|
||||
<span style="font-size:16px;">⚠️</span>
|
||||
<span>该采购单由系统自动生成,请补全食材单价和供应商信息后提交</span>
|
||||
<span>该采购单由系统自动生成,请补全食材单价和供应商信息后提交<span id="confirmStockHint" style="color:var(--warning);"></span></span>
|
||||
</div>
|
||||
<!-- 基本信息 -->
|
||||
<div class="form-grid">
|
||||
@@ -494,16 +496,19 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 食材明细 -->
|
||||
<div style="margin-top:20px;display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:var(--font-sm);font-weight:600;color:var(--text-primary);">食材明细</span>
|
||||
<button class="btn btn-primary btn-sm" onclick="addItemRow()">+ 添加食材</button>
|
||||
<span style="display:flex;gap:8px;">
|
||||
<button class="btn btn-default btn-sm" id="btnDeductAll" style="display:none;color:var(--warning);border-color:var(--warning);" onclick="deductAllStock()">⚡ 一键按库存抵扣</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="addItemRow()">+ 添加食材</button>
|
||||
</span>
|
||||
</div>
|
||||
<table class="items-table" id="itemsTable">
|
||||
<table class="items-table hide-stock" id="itemsTable">
|
||||
<thead>
|
||||
<tr><th style="width:140px;">溯源码</th><th style="width:110px;">食材名称</th><th style="width:80px;">食材分类</th><th style="width:80px;">采购数量</th><th style="width:150px;">包装规格</th><th style="width:80px;">单价(元)</th><th style="width:70px;">小计金额</th><th>备注</th><th style="width:36px;"></th></tr>
|
||||
<tr><th style="width:140px;">溯源码</th><th style="width:110px;">食材名称</th><th style="width:80px;">食材分类</th><th style="width:80px;">采购数量</th><th style="width:150px;">包装规格</th><th style="width:80px;">采购重量</th><th class="stock-col" style="width:120px;">当前库存</th><th style="width:80px;">单价(元)</th><th style="width:70px;">小计金额</th><th>备注</th><th style="width:36px;"></th></tr>
|
||||
</thead>
|
||||
<tbody id="itemsBody"></tbody>
|
||||
</table>
|
||||
<div class="items-summary">
|
||||
<span style="font-size:var(--font-sm);color:var(--text-secondary);">共 <strong id="itemCount">0</strong> 种食材</span>
|
||||
<span style="font-size:var(--font-sm);color:var(--text-secondary);">共 <strong id="itemCount">0</strong> 种食材<span id="stockDeductInfo" style="color:var(--warning);margin-left:8px;"></span></span>
|
||||
<span class="items-summary__total">采购总金额:<span style="color:var(--primary);font-size:var(--font-lg);" id="totalAmount">¥0</span></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -535,7 +540,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<div class="drawer-section-title">食材明细</div>
|
||||
<div style="padding:12px 24px 20px;">
|
||||
<table class="sub-table" id="drawerItemsTable">
|
||||
<thead><tr><th>序号</th><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<thead><tr><th>序号</th><th>溯源码</th><th>食材名称</th><th>食材分类</th><th>采购数量</th><th>包装规格</th><th>采购重量</th><th>单价</th><th>小计金额</th><th>验收数量</th><th>备注</th></tr></thead>
|
||||
<tbody id="drawerItemsBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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 '<span style="color:var(--text-placeholder);">0 kg</span>';
|
||||
}
|
||||
if (demandKg > 0 && stock >= demandKg) {
|
||||
return '<span style="color:var(--success);font-weight:500;">库存充足</span> <a class="link" style="color:var(--danger);" onclick="removeItemRow(this)">移除</a>';
|
||||
}
|
||||
return '<span style="font-weight:500;">' + stock + ' kg</span>'
|
||||
+ '<div style="font-size:10px;color:var(--text-placeholder);">需求 ' + demandKg + ' kg</div>'
|
||||
+ '<a class="link" style="color:var(--warning);" onclick="deductStock(this)">按库存抵扣</a>';
|
||||
}
|
||||
/* 单行按库存抵扣:新数量 = ⌈(需求重量 − 库存) ÷ 单位克重⌉,不足一袋则整行移除 */
|
||||
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 = '<span style="color:var(--warning);">已抵扣 ' + stock + ' kg</span>';
|
||||
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 = '<td><input type="text" value="'+it.trace+'" placeholder="请输入溯源码(选填)"></td>'
|
||||
@@ -826,6 +897,8 @@ function openEditModal(idx) {
|
||||
+'<td><select><option'+(it.cat==='蔬菜类'?' selected':'')+'>蔬菜类</option><option'+(it.cat==='肉禽类'?' selected':'')+'>肉禽类</option><option'+(it.cat==='水产类'?' selected':'')+'>水产类</option><option'+(it.cat==='粮油类'?' selected':'')+'>粮油类</option><option>调味品</option><option>豆制品</option></select></td>'
|
||||
+'<td><input type="number" class="item-qty" value="'+it.qty+'" onchange="calcRow(this)"></td>'
|
||||
+'<td><div class="spec-input-group"><input type="number" class="item-gram" value="'+specParts.gram+'" placeholder="克重" onchange="calcRow(this)"><select class="item-unit" onchange="calcRow(this)">'+SPEC_UNIT_OPTIONS.map(function(u){return '<option'+(u===specParts.unit?' selected':'')+'>'+u+'</option>';}).join('')+'</select></div></td>'
|
||||
+'<td><input type="number" class="item-weight-input" value="'+weightVal+'" placeholder="0" step="0.1" onchange="calcFromWeight(this)"></td>'
|
||||
+'<td class="stock-col item-stock" style="font-size:var(--font-xs);">'+buildStockCellHtml(it.name, it.qty, specParts.gram)+'</td>'
|
||||
+'<td><input type="number" class="item-price" value="'+priceVal+'" placeholder="0" onchange="calcRow(this)" style="border-color:var(--warning);"></td>'
|
||||
+'<td class="item-subtotal" style="font-size:var(--font-xs);color:var(--text-primary);font-weight:500;">'+subtotalText+'</td>'
|
||||
+'<td><input type="text" value="'+(it.remark==='—'?'':it.remark)+'" placeholder="选填"></td>'
|
||||
@@ -847,6 +920,8 @@ function addItemRow() {
|
||||
+'<td><select><option>蔬菜类</option><option>肉禽类</option><option>水产类</option><option>粮油类</option><option>调味品</option><option>豆制品</option></select></td>'
|
||||
+'<td><input type="number" class="item-qty" placeholder="0" onchange="calcRow(this)"></td>'
|
||||
+'<td><div class="spec-input-group"><input type="number" class="item-gram" placeholder="克重" onchange="calcRow(this)"><select class="item-unit" onchange="calcRow(this)">'+SPEC_UNIT_OPTIONS.map(function(u){return '<option>'+u+'</option>';}).join('')+'</select></div></td>'
|
||||
+'<td><input type="number" class="item-weight-input" placeholder="0" step="0.1" onchange="calcFromWeight(this)"></td>'
|
||||
+'<td class="stock-col item-stock" style="font-size:var(--font-xs);"><span style="color:var(--text-placeholder);">—</span></td>'
|
||||
+'<td><input type="number" class="item-price" placeholder="0" onchange="calcRow(this)"></td>'
|
||||
+'<td class="item-subtotal" style="font-size:var(--font-xs);color:var(--text-primary);font-weight:500;">¥0</td>'
|
||||
+'<td><input type="text" placeholder="选填"></td>'
|
||||
@@ -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)
|
||||
? '<span style="color:var(--danger);">待补充</span>'
|
||||
: '¥'+it.price;
|
||||
tr.innerHTML = '<td>'+(i+1)+'</td><td style="font-size:10px;color:#999;">'+it.trace+'</td><td>'+it.name+'</td><td>'+it.cat+'</td><td>'+it.qty+'</td><td>'+it.spec+'</td><td>'+priceHtml+'</td><td>'+(it.subtotal||'—')+'</td><td>'+(it.acceptedQty||'—')+'</td><td>'+it.remark+'</td>';
|
||||
tr.innerHTML = '<td>'+(i+1)+'</td><td style="font-size:10px;color:#999;">'+it.trace+'</td><td>'+it.name+'</td><td>'+it.cat+'</td><td>'+it.qty+'</td><td>'+it.spec+'</td><td>'+calcPurchaseWeight(it.qty, it.spec)+'</td><td>'+priceHtml+'</td><td>'+(it.subtotal||'—')+'</td><td>'+(it.acceptedQty||'—')+'</td><td>'+it.remark+'</td>';
|
||||
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';
|
||||
}
|
||||
|
||||
/* ===== 取消订单弹窗 ===== */
|
||||
|
||||
@@ -243,7 +243,7 @@ var INGREDIENTS = [
|
||||
{stage:'净菜内供',time:'2026-04-22 09:30',title:'净菜领用出库(3条分支)',trace:'NC-OUT-20260422-001',detail:'分支A:切丁5kg → 后厨A组<br>分支B:切片3kg → 后厨B组<br>分支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<br>分支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 · 李组配<br>分支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锅<br>分支B:COOK-009(辣子鸡)王厨师 1锅<br>分支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锅<br>分支B:COOK-009(辣子鸡)王厨师 1锅<br>分支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<br>分支B:SERVE-007 → B区中餐线 1.4kg<br>分支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 → 冷藏留用<br>分支B:余量0.1kg → 废弃销毁<br>分支C:余量0.2kg → 废弃销毁<br>经办人:李处置',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<br>热量:186kcal/100g · 蛋白质:18.2g · 脂肪:9.4g · 碳水:8.1g<br>检测结论:<strong style="color:#52c41a">达标</strong>',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 + '<br>' + node.detail : node.detail;
|
||||
var html = '<div class="node-card">';
|
||||
html += '<div class="node-card__icon" style="background:' + iconBg + ';color:' + iconColor + ';">' + icon + '</div>';
|
||||
html += '<div class="node-card__body">';
|
||||
html += '<div class="node-card__header"><span class="node-card__stage">' + icon + ' ' + stage + '</span><span class="node-card__time">' + node.time + '</span></div>';
|
||||
html += '<div class="node-card__header"><span class="node-card__stage">' + icon + ' ' + stage + '</span></div>';
|
||||
html += '<div class="node-card__title">' + node.title + '</div>';
|
||||
html += '<div class="node-card__trace">溯源码:' + node.trace + '</div>';
|
||||
html += '<div class="node-card__detail">' + node.detail + '</div>';
|
||||
html += '<div class="node-card__trace">时间:' + node.time + ' · 溯源码:' + node.trace + '</div>';
|
||||
html += '<div class="node-card__detail">' + detailHtml + '</div>';
|
||||
if (node.video) {
|
||||
html += '<div class="node-card__video" onclick="openVideoModal(\'' + node.video[0] + '\',\'' + node.video[1] + '\',\'' + node.video[2] + '\',\'' + node.video[3] + '\')">🎬 查看视频</div>';
|
||||
}
|
||||
|
||||
@@ -218,13 +218,22 @@ var NODES = [
|
||||
{stage:'净菜内供',time:'2026-04-22 09:30',title:'净菜领用出库(3条分支)',trace:'NC-OUT-20260422-001',detail:'分支A:切丁5kg → 后厨A组<br>分支B:切片3kg → 后厨B组<br>分支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<br>分支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 · 李组配<br>分支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锅<br>分支B:COOK-009(辣子鸡)王厨师 1锅<br>分支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锅<br>分支B:COOK-009(辣子鸡)王厨师 1锅<br>分支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<br>分支B:SERVE-007 → B区中餐线 1.4kg<br>分支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 → 冷藏留用<br>分支B:余量0.1kg → 废弃销毁<br>分支C:余量0.2kg → 废弃销毁<br>经办人:李处置',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<br>热量:186kcal/100g · 蛋白质:18.2g · 脂肪:9.4g · 碳水:8.1g<br>检测结论:<strong style="color:#52c41a">达标</strong>',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<br>用于召回时可直接反查受影响订单范围',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 + '<br>' + node.detail : node.detail;
|
||||
var html = '<div class="node-card">';
|
||||
html += '<div class="node-card__icon" style="background:' + iconBg + ';color:' + iconColor + ';">' + icon + '</div>';
|
||||
html += '<div class="node-card__body">';
|
||||
html += '<div class="node-card__header"><span class="node-card__stage">' + icon + ' ' + stage + '</span><span class="node-card__time">' + node.time + '</span></div>';
|
||||
html += '<div class="node-card__header"><span class="node-card__stage">' + icon + ' ' + stage + '</span></div>';
|
||||
html += '<div class="node-card__title">' + node.title + '</div>';
|
||||
html += '<div class="node-card__trace">溯源码:' + node.trace + '</div>';
|
||||
html += '<div class="node-card__detail">' + node.detail + '</div>';
|
||||
html += '<div class="node-card__trace">时间:' + node.time + ' · 溯源码:' + node.trace + '</div>';
|
||||
html += '<div class="node-card__detail">' + detailHtml + '</div>';
|
||||
if (node.video) {
|
||||
html += '<div class="node-card__video" onclick="openVideoModal(\'' + node.video[0] + '\',\'' + node.video[1] + '\',\'' + node.video[2] + '\',\'' + node.video[3] + '\')">🎬 查看视频</div>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user