# 设备云菜单 & UI 重构 — 设计稿 (v1, 待评审)

> 工作流: UI/UX设计 → **设计稿评审(当前)** → 前端代码生成 → 用户验证 → 发布
> 协议来源: `infimaker_iot_be/src/main/proto/infimaker/iot/v1/*.proto`
> 目标仓库: `infimaker-admin-fe-react-next` (React 19 + Umi Max + AntD v6 + ProComponents)

---

## 0. 结论速览

设备 = **五轴 CNC 加工中心**(主轴 / MQL / 回零 / JOG / G 代码 / 刀具表)。重构把现有 **5 个菜单收敛为 3 个**,把"看 + 控"合进一个驾驶舱,把"查"拆成指令史 + 上报史两条线。

**新 IA:**
| # | 菜单 | 角色 | 实时性 |
|---|---|---|---|
| 1 | **远程加工仓** | 实时驾驶舱:高频遥测 + 运行态 + 告警 + 加工控制(FPV 可选·默认折叠) | 实时(WS/3s 轮询) |
| 2 | **指令列表** | 下行指令史:分页查询 + 单指令生命周期(ack/progress/reply) | 离线查询 |
| 3 | **上报记录** | 上行上报史:SN + 时间范围统一查询(可选类型过滤,默认全部;不分 Tab) | 离线查询 |

**迁移映射(旧 → 新):**
- `设备网联/connect` → 折入 **远程加工仓**(选 SN + 实时通道 + 状态面板)
- `指令模拟仓/command-simulator`(17 指令 catalog) → 折入 **远程加工仓** 的"控制台抽屉"(常用指令) + 保留高级指令入口
- `远程加工仓/remote-machining` → 升级为新 **远程加工仓** 主页
- `指令列表/commands` → **保留**,微调
- `上报记录/records` → **保留**,改为统一查询(SN + 时间范围,不分 Tab)

---

## 1. 协议 → UI 语义参考(设计依据)

| 流 | proto | 频率 | 含义 | 落点 |
|---|---|---|---|---|
| **status** | `BusinessStatus` | 高频 | 连续采样的运动/加工实时值(坐标、进度、主轴转速/温度、进给) | 远程加工仓·实时遥测 + 上报记录·status |
| **state** | `BusinessState` | 低频 | 运行态与元数据(显示态、控制器、任务、安全、工艺、外设、机器/程序/刀具元数据) | 远程加工仓·运行态 + 上报记录·state |
| **hms** | `BusinessHms` | 事件 | 故障/告警(严重度 + 活跃告警码 + 任务/驱动/外设/控制器诊断) | 远程加工仓·告警条 + 上报记录·hms |
| **command** | `Command`+Ack+Progress+Reply | 双向 | 下行指令及其生命周期 | 远程加工仓·控制台 + 指令列表 + 上报记录·command |
| **events** | *(无独立 proto)* | — | 设备生命周期/业务事件(见 §5 待定) | 上报记录·events |

`DisplayState`(10 态,含 UNKNOWN)是 UI 主状态灯:`UNKNOWN / OFFLINE / ESTOP / FAULT / SELF_TESTING / HOMING / PAUSE / MACHINING / DISABLED / READY`。

---

## 2. 远程加工仓(主页 · 实时驾驶舱)

**布局(桌面 12 栅格):**

```
┌─────────────────────────────────────────────────────────────────────┐
│ 顶栏:[SN]  实时●  上报于 12:03:01   [🎥FPV·默认关][刷新][⚠急停!]    │
├─────────────────────────────────────────────────────────────────────┤
│ (FPV 默认折叠;展开限宽 ~460px,大部分用户非必须)                          │
├─────────────────────────────────────────────────────────────────────┤
│ 实时遥测(主区):●MACHINING  进度%·行·主轴·温度·进给·已用时  [进度条]   │
├─────────────────────────────────────────────────────────────────────┤
│ 五轴坐标(高频 status):实际/指令/工件/关节 (X/Y/Z/B/C)                │
├─────────────────────────────────────────────────────────────────────┤
│ 🧩 全量状态位(控制器/任务/安全/工艺/外设) .......... [展开▾](默认折叠) │
├─────────────────────────────────────────────────────────────────────┤
│ ⚠ 告警(hms):CRITICAL  DOOR_OPEN · MQL_LOW_LEVEL           [展开]    │
├─────────────────────────────────────────────────────────────────────┤
│ 🎬 加工控制 = ① 选命令▼ → ② 参数(localStorage 记忆)                    │
│              → ③ 相关状态位(单行 pill,实时门控)→ ④ 动作               │
│   ③: 使能·ENABLED  模式·AUTO  加工阶段·RUNNING  门·OPEN(阻断)…       │
│   ④:[▶执行(门未关·置灰)] [⏸保持][⏵恢复][⏹停止] [⚠急停] [⚙高级(17)]  │
└─────────────────────────────────────────────────────────────────────┘
```

**FPV(可选·默认折叠,⚠ 用户强调大部分用户非必须):**
- 位置:顶栏 `[🎥 视频流(FPV·默认关)]` 开关控制;展开时限宽 ~460px、16:9,不再占用主视觉 7/12。
- 占位态:灰底 + 居中 `VideoCameraOutlined` + "FPV 视频流未对接"。`VideoStreamBar`(开流/断流)disabled,接 `live.session` 信令后激活。
- 理由:FPV 对大部分用户非必须;默认折叠让遥测/坐标/控制上移为主视觉。

**遥测卡字段(status 高频):** 进度% · 当前行/总行 · 主轴转速 · 主轴温度 · 进给率 · 已用时(秒)。坐标表:实际/指令/工件/关节(各 5 轴)。

**运行态卡(state 低频)分组(对应 proto 子结构):**
- 控制器卡:`connection / estop / enable / mode / taskState / interpState / recoveryStatus`
- 任务卡:加工 phase/result · 回零 phase/axis · 自检 · 自动对中/对刀
- 安全卡:驱动 · 软限位 · 门(位+联锁+暂停) · MQL 联锁 · 硬件急停
- 工艺卡:主轴(态/命令/使能/倍率) · 进给(倍率/保持) · 当前刀号 · WCS
- 外设卡:MQL · 手轮 · 延时 · 环境灯 · 工作灯 · 蜂鸣(各 `available` 灰显不可用项)

**告警条(hms):** 顶部按 `AlarmSeverity` 着色(NONE→隐藏 / INFO→蓝 / WARNING→橙 / ERROR→红 / CRITICAL→深红+置顶),点击展开 `active_codes + items`(code/severity/message/source/首发时间)。

**加工控制(命令面板,自上而下):** ① 选命令(下拉,8 常用)→ ② 参数输入(**自动记住上次输入,键 `omp:cmdParams:{SN}:{commandId}:{param}` → `localStorage`**)→ ③ 相关状态位(与命令相互影响的位,**同区单行 pill 展示**;实时驱动门控)→ ④ 动作按钮(执行/保持/恢复/停止 + 急停)。"高级指令抽屉"承载 `catalog.ts` 全 17 指令(JOG/MDI/回零/倍率/自检/INI/标定/灯控/MQL/安全策略)。全量状态位(控制器/任务/安全/工艺/外设)默认折叠,命令相关位已在 ③ 内联。


**控制安全门控(⚠ 远程数控强制):** 控制键不是自由按钮,每个动作按 `state`+`hms` 实时计算**使能谓词**与**禁用原因**,不可点时显示原因 tooltip,可点时按风险等级要求确认。来源对齐 proto `ErrorCode`。

| 动作 | 使能谓词(state / hms) | 禁用原因(取首个命中) | 确认 |
|---|---|---|---|
| **急停 ENGAGE** | SN 已选 ∧ 指令通道可用 | 通道不可用/离线 → **置灰 +「远程急停不可用」**(不可用态,非可点确认) | **无确认,点击立即下发**(紧急) |
| **急停/机器 RESET** | `estop==ACTIVE` ∨ `displayState∈{ESTOP,FAULT}` ∨ `recoveryStatus==RUNNING` | 硬件急停仍按下(`hardwareEstop.active==TRUE`)→「请先松开物理急停」 | Popconfirm「确认复位?须已排故」 |
| **启动 START** | 已选程序 ∧ `connection==CONNECTED` ∧ `taskState==ON` ∧ `enable==ENABLED` ∧ `mode==AUTO` ∧ 门已关 ∧ MQL 正常 ∧ `hms.severity ∈ {NONE, INFO, WARNING}`(显式白名单) ∧ `allHomed==TRUE` | 依次判:`CNC_NOT_CONNECTED`→`ESTOP_ACTIVE`→`DOOR_OPEN`→`MQL_LOW_WATER`→`FILE_NOT_SELECTED`→驱动故障(`safetyStatus.drive.state==FAULT`/HMS)→HMS 未知/ERROR/CRITICAL→未回零 | Modal「启动 {path} 自第 {line} 行」 |
| **进给保持 HOLD** | `machining.phase==RUNNING` | 非运行态→「当前未在加工」 | 无(幂等) |
| **恢复 RESUME** | `machining.phase==PAUSE` ∧ 保持期间无新增阻断(门关/急停释放/无新 ERROR·CRITICAL;HMS 已知且 ∈ {NONE,INFO,WARNING}) | 保持期间开门→`DOOR_OPEN`;急停→`ESTOP_ACTIVE`;新故障/HMS 未知→阻断 | Popconfirm「确认恢复加工?」 |
| **停止 STOP** | `machining.phase∈{RUNNING,PAUSE}` | 非加工态→「当前未在加工」 | Popconfirm「确认停止程序?」 |
| **机器 ENABLE** | `connection==CONNECTED` ∧ `estop!=ACTIVE` ∧ `hms.severity ∈ {NONE, INFO, WARNING}`(UNSPECIFIED 阻断) | `CNC_NOT_CONNECTED` / `ESTOP_ACTIVE` / HMS 未知或故障 | 轻确认 |
| **回零 HOME** | `connection==CONNECTED` ∧ `enable==ENABLED` ∧ `estop!=ACTIVE` ∧ 门已关 | 同上 + 门开 | Popconfirm「回零 {target}?」 |

**实现要点:** 单一 `useControlGating(snapshot)` hook 返回 `{ enabled, reason, confirm }`,所有控制键消费它;`reason` 文案常量表复用 `ErrorCode` 枚举名→中文映射(扩充 `catalog.ts`)。急停钮**视觉优先级最高**(顶栏固定 + 控制条各一,红色实心),其余键 disabled 态灰显 + tooltip 原因。

---

## 3. 指令列表(下行指令史 · 离线查询)

```
┌─────────────────────────────────────────────────────────────────────┐
│ [SN 选择器]                                                          │
├─────────────────────────────────────────────────────────────────────┤
│ ProTable: 指令ID │ SN │ 类型 │ 模式 │ 状态 │ 排队/送达/完成时间 │ 操作│
│           ...                                                        │
│                                              [查看生命周期]          │
└─────────────────────────────────────────────────────────────────────┘
点"查看生命周期" → 抽屉:
  Command 概要 + Timeline(ack → progress* → reply),逐条 payloadJson
```
**复用现有 `Commands.tsx`**:`listCommands` / `getCommand` / `listCommandEvents`。改动很小(标题/面包屑对齐新 IA)。

---

## 4. 上报记录(上行上报史 · 统一查询,不分 Tab)

```
┌─────────────────────────────────────────────────────────────────────┐
│ [SN 选择器]  [时间范围]  [类型▼ 全部/status/state/hms/command/events] [查询]│
├─────────────────────────────────────────────────────────────────────┤
│ ProTable: ID │ SN │ 事件类型 │ 来源 │ 指令ID │ 方向 │ 时间 │ 载荷      │
└─────────────────────────────────────────────────────────────────────┘
点行 → 抽屉:payloadJson 格式化详情
```

**查询语义:** 以 **SN + 时间范围** 为主键统一查询,**不分 Tab**(按你指示);「类型」下拉做可选过滤(默认全部),事件类型作为列展示。

**后端事实(已核 `iot_be`):** `iot_device_event` 按精确 `eventType` 存上行事件,`CommandService` 把 `command.ack`/`progress`/`result` 也写入此表(带 commandId,CommandService:165/184/203);`/reports/{type}` 是精确单类型查询(status/state 别名→`business.status`/`business.state`;hms 特判走 message-log)。hms 数据在 `iot_device_message_log`(`%hms%`),不在 device_event。

**类型过滤 → 后端路由:**

| 类型 | 后端存储 | 现有端点 | 状态 |
|---|---|---|---|
| status | `business.status`(device_event) | `/reports/status` | ✅ |
| state | `business.state`(device_event) | `/reports/state` | ✅ |
| hms | message_log `%hms%` | `/reports/hms` | ✅ |
| command | `command.ack`/`progress`/`result`(device_event) | 需 `command.*` 聚合(决策1 已选后端聚合) | ⚠ 后端 |
| events | device_event 其余类型 | `/api/v1/events`(已存在,缺 FE wrapper) | ⚠ FE |
| **全部** | device_event ∪ message_log 跨库 | **需后端统一聚合端点** | ⚠ 后端 |

**⚠ 实现缺口:**
- **「全部」跨库统一查询**:device_event + message_log 两库,精确单类型端点无法跨库全局排序分页。需后端新增**统一上报查询端点**(SN + 时间 + 可选类型,合并两库按时间全局排序分页)—— 与决策1 的 `command.*` 聚合合并为一个端点最经济。
- **command.* 聚合** + **events FE wrapper**:同决策1/2。

---

## 5. 已确认决策(评审拍板)

1. ✅ **command 聚合**:后端新增 `command.*` 聚合端点(全局排序 + 正确分页)。
2. ✅ **events**:FE wrapper 接 `/api/v1/events`,通用事件浏览器(eventType 过滤)。
3. ✅ **指令模拟仓**:17 指令 catalog 折进远程加工仓「高级抽屉」。
4. ✅ **FPV**:可选·默认折叠(大部分用户非必须);展开限宽 ~460px、16:9。命令相关状态位与命令同区单行展示。
5. ✅ **急停**:顶栏 + 加工控制条各一个。

**⚠ 由「上报记录统一查询」新引入(需确认):**
6. **统一上报查询端点**:「全部」跨 device_event ∪ message_log 需后端聚合(SN + 时间 + 可选类型,全局排序分页),建议与决策1 合并为一个端点。默认按此推进,如不同意请指正。

---

## 6. 评审通过后的交付物

1. 用真实 AntD token(`config.ts` 主色 #EF8B22)出 **HTML 高保真原型**,browser 截图给你过稿
2. 过稿后落 `routes.ts` + 3 个页面 + 折入 connect/simulator 逻辑
3. 烟测:本地 `max dev`,选 SMOKE-0D5305C6F1 走通实时通道 + 控制下发
