# 20: 总体概览模式面板 **What to build:** 在总体概览一级模式下同时展示态势总览、基础设施、运行监测、预警设置四个二级面板,形成城市生命线整体态势摘要。 Blocked by: 17, 18, 19 Status: resolved Labels: ready-for-agent - [x] 态势总览展示跨行业 KPI:设施底数、监测规模、在线率、当前预警/报警、处置率、近 7 日趋势。 - [x] 基础设施面板汇总设施底数、行业构成和区域分布。 - [x] 运行监测面板汇总在线率、离线/故障统计和监测覆盖。 - [x] 预警设置面板保持合同名称,只读展示预警/报警统计。 - [x] 面板指标可联动地图或事件列表。 - [x] 面板可独立展开/收起。 - [x] 有汇总指标单测。 ## 进度:100%(已关闭,2026-09-29) **逻辑闸门 63/63 · 变异测试 14/14 · 真机走查逐组通过(DSH 浏览器驱动)。** 四块面板接三个概览端点(`/overview/infrastructure`、`/overview/monitoring`、 `/overview/warning-settings`)+ **借票 17 的 `/infrastructure/base-archives`** 取区域分布; **三块各自成败**(一块端点坏了其余照常画出真实内容),态势总览**不自己取数**、 由另外三块合成,所以「预警总数」在态势总览与预警设置里**按构造是同一个数**。 ### 交付物 - `domain/overviewPanels.js`(新):四块的契约适配 + 视图计划(纯函数)。 - `services/overviewApi.js`(新):四个端点(含借用的 base-archives)的唯一出口。 - `stores/overviewStore.js`(新):四分区 + 态势总览的合成 + 两条联动出口。 - `mocks/contractMocks.js`:三个 `/overview/*` 端点(**按请求真的回显/过滤**)。 - `components/PanelBlocks.vue`:新增**通用** `panel-action` 转发与可点 KPI/分布行 (渲染层仍**不认识 panelId**,票 18 解除的耦合没长回来)。 - `domain/panelViews.js` + `CockpitShell.vue`:注册并接进共享接缝(16 块面板至此全部接上真实视图)。 - `verify/t20-overview-panels.mjs`(新,63 项)+ `verify/t20-mutations.mjs`(新,14 条变异)。 ### 九条值得记下的判断 **1. 预警与报警**两族都画、不做切换,且态势总览与预警设置**共用一次取数**。** `/overview/warning-settings` 一次响应就同时给 `warning` 与 `alarm`(逐类读过响应类)。 做切换等于把已经拿到的数据藏起来,还会把概览与预警处置的 `eventStore.mode` 耦上。 两处读的是**同一个对象引用**(D2b 断言),所以数字不可能对不上。 **2. 「近 7 日趋势」必须真的声明窗口,否则只是一句空话。** 后端趋势桶**跟随声明窗口过滤**(`CockpitOverviewWarningSettingsService.resolveWindow`); 不声明时维持 `CURRENT_BASELINE`(无界)。本票固定声明 `LAST_7_DAYS`,界面并回显窗口类型。 **3. 「区域分布」不在概览端点上 —— 借兄弟端点,但只借纯适配层。** `/overview/infrastructure` 只有 `Totals` + `Industries` + `Excluded`(无区域字段)。 区域数据真实存在于票 17 的 `base-archives`,故复用其**纯适配函数** `toBaseArchives()`; **只引适配层、不引块构造器**(那才是票 18 抽出 `panelBlocks.js` 要避免的耦合)。 它同时带来里程与规模两个读数,一并呈现。 **4. 「故障统计」后端没有这个桶 —— 不发明,如实标缺口(维护者 2026-09-29 裁定)。** `Counts.Status` 的真实四桶是 `online/offline/stale/unknownOrMissing`(服务第 175-182 行 只认 `ONLINE/OFFLINE/STALE`)。维护者指出「数据过期未必是故障,可能只是网络波动」, 故**不合成**一个"故障"数,四桶按本义呈现(滞后 ≠ 故障),缺口写成 `MONITORING_FAULT_GAP` 印在面板上。 **5. 「联动地图」在概览数据上做不到 —— 改走两条真实存在的路。** 三个概览端点**都不含点位编码或坐标**(逐类读过),票 17 的 `mapLocate` 接缝没有可绑对象。 故联动走:① 指标读数 → 切到预警处置模式落到**真实事件列表**(那里的事件带 `deviceCode`, 再由票 13/14 既有链路跳点位);② 行业构成行 → 写全局 `filterStore.setIndustry` (**真的**同时驱动一张图与各面板)。**不伪造**一次地图定位。 **6. 分母为 0 不是 0% —— 且这条判据只有一份实现。** 票 18 在在线率上用 M3/M14 两条变异测试守过这一族;本票的在线率与处置率都要它。 故把 `handlingRateView()`/`handlingRateHint()` 从票 19 的模块**导出**供两处共用, 并给 `toHandlingEfficiency()` 补带 `handlingState`("分母为 0"与"真的是 0%"分得开)。 **7. ⚠️ 真机走查逮到真缺陷:两块面板的读数全是 `—`,而逻辑闸门 59/59 全绿。** `monitoringSummaryPanel` 与 `alertSettingsPanel` 以为 `data` 是 `{monitoring}`/`{warningSettings}` 包装对象,而外壳传的是**裸 `section.data`**。**测试喂的正是那个错误的包装形状** —— 测试与实现犯了同一个错,所以闸门看不见。 已修,并补三条护栏:C3/C4(按真实形状断言读数是数字)、**D8(经 `panelView()` 端到端逐块读数字)**、 **F6c(钉住外壳逐块的数据源分派)**。 **教训:手喂 data 的单元断言证明不了"外壳喂进来的形状是对的"——必须有一条走外壳真实入口的断言。** **8. 变异测试又逮到两条"守卫比它要守的东西宽/窄"(本仓第 N 次)。** ① `F6b` 第一版只查 `linkToMode`/`linkToIndustry` 在文件里**出现过** —— 而它们在 `handlePanelAction` 的定义里当然出现,于是**删掉 `@panel-action` 绑定**(联动出口实际断掉) 根本测不出来(M13 全绿)→ 改为断言**两处模板绑定**都在。 ② `F3` 的裸字段词表只列了本票用到的几个,比 t05 的 D1 **窄** → 改为与 D1 同宽。 **9. 工单状态字典按后端权威枚举对齐到 0–7(维护者裁定),并删除冗余副本。** 后端 `CockpitWorkOrderStateDictionary` 的类注释原文:「与运行时 `WorkOrderStatusEnum`(0–7)对齐… 终态为 5、7,延期中是处理旁路」。前端旧表是 1–7 且把「已结案」标在码 6 上 → **每态错位一格**。 已对齐(含 `WORK_ORDER_CLOSED_CODE` 6→5、主线 `1→6` 改为 `0→5`、新增 `BYPASS` 动作), 并删掉 mock 里那份重复字典(改为从唯一源派生)。跨票影响见下。 ### 验证 **逻辑闸门 63/63**(`verify/t20-overview-panels.mjs`,六段): node .scratch/cockpit-shell-panel-layout/verify/t20-overview-panels.mjs - **A 面板映射与注册**(6):四块与 ADR 0003 一致、左右各二、构造器与目录逐项一致、 注册表覆盖四个模式、未注册 panelId 仍回 null、不越界接别模式的面板。 - **B 契约适配**(9):BigDecimal 字符串、行业 label 回退字典、**本端点无区域字段**(如实)、 四态桶、**不发明「故障」桶**、逐设备采集时间(票 18 的 I2/BT-8 在这里有值)、 两族同源、处置率带 `state`、**0–7 共 8 个码全铺**、窗口回显。 - **C 视图层**(12):验收点名的画面元素逐项在场;两条趋势;三个读数带联动出口; **分母为 0 显示占位符而不是 0.0%**;区域缺口与故障缺口如实说明; **C3/C4 按外壳真实形状断言读数是数字**(真机缺陷的护栏);失败态仍返回结构完整的视图。 - **D 分区独立**(9):三块各自成败、一块坏不遮其它 + **对抗式反证**; 态势总览由三块合成且**与预警设置同一引用**;区域并进基础设施; **D8 经 `panelView()` 端到端逐块读数字**;行业筛选真的重算且不重取不可筛的块; 窗口真的声明;空 ≠ 失败。 - **E 联动**(4):指标 → 切模式;行业行 → 写全局筛选;空动作被拒; **概览 store 不提供任何地图定位入口**(定位只能伪造,故结构上不该有)。 - **F 静态守卫**(10):domain 纯函数、service 不拼绝对地址不带凭据、 **store 零后端裸字段(与 t05 的 D1 同宽)**、只读、渲染层不认识 panelId 也不拼路径、 外壳保持两处 `hasPanelView` 与三态措辞、**F6b 断言两处模板绑定**、**F6c 钉住逐块数据源**。 **对抗式自查(变异测试,14 条全部以断言失败打红闸门)**: node .scratch/cockpit-shell-panel-layout/verify/t20-mutations.mjs M1 凭空造「故障」桶 → B2b;M2 不声明故障缺口 → C3c;M3 分母为 0 直画 0% → C1d; M4 状态分布退回 1–7 → B3c;M5 态势总览改读拷贝 → D2b;M6 区域失败遮整块 → D3; M7 指标联动变空操作 → E1;M8 行业联动不写筛选 → E2;M9 行业筛选不重取 → D6; M10 渲染层长出 panelId → F5;M11 domain 反向 import service → F1; M12 store 写后端裸字段 → F3;M13 外壳不接联动动作 → F6b;M14 不声明窗口 → D7。 **14/14 被抓住,还原后复跑全绿。** 变异脚本自身的两处坑(都已修,记下来免得下次再踩): ① 子进程输出用管道会被本机沙箱以 `EPERM` 拒(Windows confined mode 不允许开命名管道)→ 改成把 stdout/stderr 写进临时文件描述符; ② 本仓 `.vue` 是 CRLF,而变异源文本按 `\n` 写 → 出现"变异源文本未找到"的**假失败** → 按目标文件换行风格归一。 **真机走查(已执行:2026-09-29,逐组通过)** 本机 `@puppeteer/browsers` 是**空目录**(票 01 记录的既有环境问题,非本票引入), 故沿用票 36 的办法:**用 DSH 自带浏览器真实驱动同一份页面**,逐项跑同一批断言。 | 组 | 验的是什么 | 实测 | | --- | --- | --- | | A | 四块真面板、四块 `success`、零占位 | 4 块面板 / 0 占位 | | A | 态势总览 KPI 从 DOM 读 | 设施底数 5处 · 监测规模 9台 · 智能设备 9台 · 监测点 7个 · **在线率 75.0%** · 当前预警 8条 · 当前报警 9条 · **处置率 62.5%** | | A | 两条近 7 日趋势 | 近 7 日预警趋势 / 近 7 日报警趋势(14 个趋势点) | | A | 基础设施含行业构成与区域分布 | 设施 5处 · 里程 12.4km · 规模 中型 · 行业 3 个 · 区域 CN3/CB1/未知1 | | A | 运行监测四态桶 + **故障缺口印在界面上** | 在线 6台 / 离线 1台 / 滞后 1台 / 状态未知 1台 + 缺口说明 | | A | 预警设置两族 + **0–7 共 8 个状态码** | 预警 8条/报警 9条;状态分布含**延期中 1**(新状态真的画出来了) | | A | 零后端代号泄漏 | 两栏可见文本零 `GAS`/`CURRENT_BASELINE`/`LAST_7_DAYS`/`medium` 等 | | B | 行业行 → 全局筛选 + **真的重算** | 筛选 `all → gas`;在线 6→**3台**、智能设备 9→**4台**、监测点 7→**3个**、正式行业范围=燃气 | | B | 不可筛的块不假装生效 | 基础设施设施总数 5处 → 5处(该端点不收行业入参) | | C | 指标读数 → 切预警处置模式 | 点「当前预警」→ 一级模式 = **预警处置**,落到真实事件列表 | | D | 只坏一块不遮其它 | 监测 `failed`("数据加载失败"),基础设施 9 行分布 / 预警设置 16 行分布**照常画出真实内容** | | E | 空场景 ≠ 失败、不伪造 0 | 三块 `empty`("无数据"/该面板当前没有数据);**`0.0%` 未出现** | 证据:`verify/walkthrough/t20-overview-panels.png`。 ### 跨票改动(已重开对应票记录,请维护者复核) 按维护者裁定「以后端 `WorkOrderStatusEnum`(0–7)为唯一真理,删除其他不必要的字典」, 本票改动了**三张已关闭票**的交付面(详见各票的重开记录): - **票 12**(工单时间线):`workOrder.js`/`workOrderTimeline.js` 对齐 0–7; 主线 `1→6` 改为 `0→5`;新增 `BYPASS`(延期中是**处理旁路**,与已取消同为路径外)。 票 12 原验收「**不展示延期**」与后端权威枚举直接冲突,**已作废并同步改 ADR 0003**。 闸门 49 → **52 项**(回退语义一字未改)。 - **票 19**(预警处置):查出**两处真缺陷** —— ① 「工单状态分布」用错字典, 标签整体错位一格(已随字典对齐修正,标题改为中性的「工单状态分布」); ② 处置率"分母为 0"会显示虚假 `0.0%`(已修,并导出共用判据)。 另外本票发现**票 19 从未把新文件补进 t05 的静态守卫面**(票 11/17/18 都补过), 实测那两个文件干净后已补入(t05 255 → **270** 项)。 - **票 11**(设备详情):仅断言/样例的**码值**跟随字典更新(行为未变),闸门 61/61 保持。 - **票 17**(基础设施走查):其 `A8b` 断言"总体概览恰好 4 块占位",被本票接真面板后**必然假红** —— 这是**钉在别人票进度上**的断言(票 18 在完全相同处境下已栽过一次并已自修)。 按 Q9=(a) 去魔数化:只断言本模式,其余模式只汇报。 ### 边界与下一步 - **本票交付面是前端页面**;后端真实数据接线(概览三端点的真实适配器)归 [驾驶舱真实后端数据接线(决策地图)](../../cockpit-real-data-wayfinding/map.md), 按票 18 的收口口径**不阻塞**本票。 - 「故障统计」的口径是**业务问题**(票 25 的三条待答之一),本票按维护者裁定 **不发明故障数**、四态如实呈现并标缺口。 - 本部署的 `map.ui.debug` 决定顶栏是否显示诊断读数(票 32),与本次改动无关。 ## Answer **总体概览四块面板落地:接三个概览端点 + 借票 17 的档案端点取区域分布, 三块各自成败(一块坏了其余照常画出真实内容),态势总览由另外三块合成 (故与预警设置的数字按构造同源)。** 四条边界如实划清,不发明业务: ① **「故障统计」后端没有该桶** —— 四态如实呈现并标缺口,不把「滞后/状态未知」折算成故障 (维护者 2026-09-29 裁定:故障语义复杂,不合成); ② **「区域分布」不在概览端点上** —— 借票 17 的 `base-archives` 纯适配层,只引适配不引构造器; ③ **「联动地图」在概览数据上做不到**(三端点都不含点位编码/坐标)—— 改走两条真实存在的路 (指标→切预警处置模式落到真实事件列表;行业行→写全局筛选真的驱动地图),**不伪造定位**; ④ **分母为 0 不是 0%** —— 在线率与处置率共用同一条判据,不画虚假零值。 票面七条验收**全部达成**(逐条见正文勾选)。 **本票真机走查逮到一个逻辑闸门看不见的真缺陷**(两块面板读数全是 `—`, 而闸门 59/59 全绿 —— 因为**测试与实现犯了同一个错**,都以为 `data` 是包装对象), 已修并补了三条护栏(含一条**经外壳真实入口**的端到端断言)。 跨票改动三张已关闭票(12/19/11)与一张走查脚本(17),均已重开记录、请维护者复核。 ## Comments ### 2026-09-29 认领与阶段闸门 已认领(`Status: claimed`)。本票**无 `needs-triage` 标签**,`Blocked by: 17, 18, 19` 三张前置票**均已 resolved**,故按闸门规则**不经过诊断阶段,直接进入实施**; 但票面含多处"不该猜"的地方,故按本仓成例(票 17/18)先做澄清。 读现状时核出的关键事实:本票票面写「0% 未动工」是**准确的** —— 前端仓库对 `/overview/*` **零实现**(无 mock、无 service、无 api); 三个端点在**后端**已存在;且票 24 调研里 `situationSummary` 那处「趋势窗口不可声明」的缺口 **已由后端 BT-3 关闭**(`warning-settings` 现收 `range`),故「近 7 日趋势」可做。 ### 2026-09-29 实施前的三轮澄清(维护者裁定) **第一轮**:预警/报警并列与共用取数(A)、区域分布读兄弟端点(A)、 故障统计按真实四态不发明(A)、联动走事件列表+全局筛选(B)、真机走查授权(B)、 处置率不加工口径说明(A)、全局筛选只作用于能收它的那块(A)、 补 t05 守卫面(C)、修票 17 假红(A)。 **第二轮**:维护者补充了故障语义的完整口径(点位四态与设备五态都算异常; **但"数据过期"未必是故障**,建议只保留离线统计,语义过复杂则删掉故障统计); 并明确 Q4 的联动语义是"面板中的设备/报警 → 跳到场景中对应点位"。 据此核出:**三个概览端点都不含点位编码或坐标**,故该语义在本模式数据上无法直接实现 —— 改走"切到预警处置模式落到真实事件列表"(那里有 `deviceCode` 可再跳点位)。 **第三轮**:Q10 维护者先答 (c) 后更正为 **(b)**(一次收口、同步票 12/19 断言); Q13 裁定**以后端 `WorkOrderStatusEnum`(0–7)为唯一真理并删除冗余字典**; Q14=(a) 延期中画成路径外旁路、口径改中性词;Q16 按此落地。 期间向维护者指出两处硬冲突:**「七态」在 0–7 下不成立**(是 8 个码)、 **ADR 0003「不展示延期」被后端枚举推翻**(6 延期中是正式状态)。 ### 2026-09-29 实施、自查与收口 实施中自查(变异测试)逮到**两条自己的守卫缺陷**(F6b 只查词出现过、F3 词表比 t05 窄), 均已修并用变异确认有牙。真机走查又逮到**一条真缺陷**(两块面板读数全 `—`, 测试与实现犯同一个错),已修并补三条护栏。逻辑闸门 **63/63**、变异 **14/14**、 全量回归 29 个闸门绿(`t04-config` 与 `t28-fill-container` 的失败是**既有环境问题**: 前者因另一张票在 `cockpitApi.js` 注释里写了 `/api/cockpit` 而误报, 后者需要 puppeteer 的 `@puppeteer/browsers`,两者都与本票无关、本票未触碰)。