# 11: 设备详情聚合与分区状态 **What to build:** 通过设备详情聚合契约加载设备档案、最新指标、预警、报警、工单状态和最新图片。各分区可独立成功、失败、为空或过期。 Blocked by: 10 Status: claimed Labels: ready-for-agent - [x] 设备详情按设备编码加载。 - [x] 档案、指标、预警、报警、工单和图片分区独立渲染。 - [x] 任一分区失败时不遮盖其他成功分区。 - [x] 空数据、字段缺失和过期状态均有明确 UI。 - [x] 最新图片只展示后端指定的一张。 - [x] 有分区状态映射单测。 ## 进度:95% 实现完成、逻辑闸门全绿;**待确认**:真机走查未跑(脚本已写好,见下)。 ### 交付物 - `domain/equipmentDetailView.js`(新):六个分区的**界面视图**(纯函数)。 面板渲染计划 `EQUIPMENT_SECTION_PANELS`(档案 / 指标 / 预警 / 报警 / 工单 / 图片)、 占位符与空/失败/不可用措辞、档案行 / 指标行 / 事件行 / 工单行 / 图片视图, 外加三个判据:`sectionHasContent`(过期算有值)、`equipmentDetailMatches`(这份载荷是不是当前这台)、 `aggregateUnavailable`(整包失败,见下)。 - `components/EquipmentDetailSections.vue`(新):六块分区各带自己的状态标签与内容, 一块坏了缩成一行提示、其余照常画。 - `components/PointDetailCard.vue`:第二级由「待接入」占位换成真分区容器。 - `stores/detailStore.js`:`loadEquipmentAggregate`(按编码取数)+ `deviceLoading`, 并把**进设备级**的两个入口(`selectDevice` / `openDevice`)改成自己负责取数。 - `mocks/contractMocks.js`:新增 `equipment-partial` 场景(档案/工单/图片各自 FAILED, 指标/预警/报警 SUCCESS)。原先那段分区级失败样例挂在 `failed` 分支下**永远走不到** (`failed` 在注册层就抛错),所以「任一分区失败不遮盖其它分区」在 mock 上一直无法演示。 ### 三条值得记下的判断 **1. 票 10 的「进第二级不取数」是延期,不是契约。** 票 10 票面原文写明「设备详情聚合的加载是票 11 的契约」, 所以本票把取数收进 `selectDevice` / `openDevice` 这两个**唯一**的进级入口: 「进了设备级却没取数」在结构上不可能发生,调用方不必记得多调一次。 竞态收口沿用票 10 的点位级同款(token 丢弃迟到响应),并追加两条: 已答过的同一台不再重复取(返回卡片列表再点回来不重拉), 失败/不可用**不算已答**(留重试路径,否则永远没有恢复路径)。 **2. 「整包失败」与「分区级失败」必须分开,否则界面对值班人员撒谎。** 聚合端点整体读不到时 `data` 为 null、六块一起没数据,若照常渲染六块, 每块都会说「该分区加载失败(其它分区不受影响)」—— 那句话在只有一块坏时是真的, 在六块一起坏时是**假的**。所以整包失败走一句实话 + 「重新加载」入口, 分区级失败(`aggregateState: PARTIAL`、各分区自带 state、`data` 在)才逐块渲染。 这是本票验收第 3 条的反面,专门写进了 `aggregateUnavailable` 的注释与单测。 (附带发现:失败分区 `data` 为 null、带不出设备编码,所以该判据**刻意不比对编码**, 靠 store 的 token 纪律保证「失败态必定属于当前那台」;D11 把这个前提钉住了。) **3. 不把接口词汇漏到大屏上。** 后端 `stateReason`(`SAMPLE_OUTDATED` 这类) 与字段缺失清单(`archive.deviceName` 这类路径)都是接口契约词汇。 票 05 的真机走查已经逮到过一次同类泄漏,所以:认得出的原因翻人话、 认不出的**回空串**;缺失只报**条数**,字段路径进 `title`(排查拿得到,值班人员看不到)。 两条都有单测 + 组件静态守卫(F4b/F4c),并且**都做了变异测试**确认守卫有牙 (F4b 第一版只查引号字面量,会放过模板里的裸文本,已修)。 ### 验证 逻辑闸门 **61/61**(`verify/t11-equipment-detail.mjs`,六段:分区映射、视图层、store 取数与竞态、 分区独立、图片单值、静态守卫)。回归 12 个既有逻辑闸门全绿 (t01 / t04 / t05 188 / t06 / t07 39 / t08 / t09 38 / t10 57 / t21 / t21-view / t29 / t30)。 **t05 的静态守卫面已扩到本票新增的两个组件与一个 domain** —— 不补进去,新代码就是检查不到的荒地。**t10 的 C4 按本票改写了**:它原本断言 「进第二级不取数」,那正是票 10 延期给票 11 的接缝,本票接手后该断言不再成立(已改为断言按编码取数)。 ### 待确认(95% 的原因) `verify/t11-equipment-detail-walkthrough.mjs` 已写好但**未运行**: 按 `AGENTS.md` 的项目规则,未获明确要求不得跑构建或调试命令(含 dev server 与浏览器走查)。 本机 8000/8002 端口有维护者的 dev server 在跑,脚本可用: node .scratch/cockpit-shell-panel-layout/verify/t11-equipment-detail-walkthrough.mjs 它覆盖 5 组:成功(六块画出来 + 整卡可见文本零代号零字段路径)、 `equipment-partial`(3 坏 3 好)、`empty`、`stale`、`failed`(一句实话 + 重试入口)。 用 `__COCKPIT_MOCK_ONLY__` 把异常样例只施加在聚合端点,地图与点位摘要保持 success —— 否则全局 `empty` 会把地图清空、没有可点的标记,走查会退化成假失败。 **跑通后方可 close。** ## Comments ### 2026-09-18 认领与阶段判定 已认领(`Status: claimed`)。本票无 `needs-triage` 标签、`Blocked by: 10` 且票 10 已 resolved, 故不经过诊断阶段,直接进入实施。