# 42: 关联监测设备临时图层 ## 进度 整体验收复核重开:刷新/快照失效撤层与详情重载链尚未满足。已有同编码重绘修复当前回归通过,仍需真实复验。证据:verify/artifacts/t48/acceptance-status.md。 Type: implementation Status: ready-for-agent Blocked by: 41 Labels: needs-triage ## What to build 按规格 14 接入选中监测点后的关联监测设备临时图层。地图摘要继续只包含监测点与未关联设备;关联设备不写回摘要序列、分页、默认图例或摘要计数。地图点击和台账选择都从同一监测点选择语义启动设备加载,并与点位定位并行;设备数据到达即可渲染,不等待相机动画结束。 后端 #69 已发布:有 `snapshotAt` 时详情与地图共享同一快照;设备编码集合以地图快照 `deviceCodes` 为准,坐标只认设备自身 `longitude/latitude/coordinateValidation.renderable`。无坐标、非法、未校验、归属不明、详情失败或当前快照不一致的设备继续出现在清单中,并显示待确认/不可用原因,禁止复制监测点坐标或人工偏移。现网 `UNVERIFIED_COORDINATE` 的正式边界是清单-only。 ## Acceptance - [x] 选中监测点后关联设备图层自动加载;地图与台账入口行为一致。 - [x] 使用受控的坐标准入有效样例验证独立图层渲染与拾取;现网后端 `UNVERIFIED_COORDINATE/renderable=false` 时只显示清单及原因,不用临时样例冒称正式设备已能上图。 - [x] 有效设备只进入独立临时图层,设备身份同时携带 `deviceCode` 和所属 `monitoringPointCode`,不冒充 `pointCode`。 - [x] 关闭、换点、刷新、快照失效时旧图层立即撤下;迟到响应不能覆盖当前选择。 - [x] 同一 `snapshotAt` 翻页不重建或闪烁设备图层;快照查找逐页进行,第一页没有不能判定点位不存在。 - [x] 设备总数来自当前快照去重 `deviceCodes`;详情独有/快照独有设备分别标记不一致或待确认,不静默丢弃。 - [x] 总览/运行监测显示全部坐标准入有效设备;基础设施隐藏;预警处置只显示设备自身 `ALARM`/`WARNING`,未知状态待确认。 - [x] 清单保留全部关联设备,明确区分已上图、无地图位置、模式隐藏、待确认/失败;摘要图例和关联设备图例/计数分开。 - [x] 纯逻辑覆盖集合分类、快照生命周期、计数互斥;外壳走查覆盖地图/台账入口、关闭、换点、分页和降级。 ## Out of scope - 设备 hover/选中双向反应、资产选择、状态特效、重叠展开、完整模式计划的独立实现。 ## 进度:100%(2026-09-29 收口) **已完成并关闭。** 逻辑闸门 **37/37**、变异测试 **19/19 全部打红**、真机走查已执行(见下), 全量零依赖闸门回归绿。 ### 交付物 - `domain/deviceLayer.js`(新):**集合分类**(六类互斥:`drawn`/`no-location`/`hidden`/`pending`/ `inconsistent`/`unavailable`)、**快照生命周期**(总数只认当前快照去重 `deviceCodes`; 快照独有→待确认、详情独有→不一致且禁止上图)、**互斥计数**与独立图例、跨端点风险提示。 - `stores/deviceLayerStore.js`(新):**合成点**。它读 `mapStore` 的当前快照 `deviceCodes`(总数依据) 与 `detailStore` 的 `devices[]`(状态与设备级坐标),按 domain 判定后交地图服务。 两侧都不为自己长出对方(票 10 的 F4、票 02 的层次纪律)。 - `services/cesiumEngine.js`:`syncAssociatedDevices` / `readAssociatedDevices` / `clearAssociatedDevices` / `pickDeviceMarkerAt` / `hasDeviceMarker` + **独立数据源** `cockpit-associated-device-markers`(与摘要 `cockpit-point-markers` 分开)。 - `services/mapService.js`:`syncAssociatedDevices` / `clearAssociatedDevices` / `getAssociatedDeviceState` + 宿主读数 `data-map-devices`(`-plan`)(与摘要 `data-map-markers*` 分开)。 - `domain/pointDetail.js`:设备卡片带出**设备级坐标与校验**(后端 #69 已交付)。 - `pointSelection.js`:统一选择入口在打开详情前**先出一次设备计划**(不等详情、不等镜头)。 - `CockpitShell.vue`:四类订阅(选择 / 详情到达 / 快照变化 / 一级模式)+ 卸载拆监听与复位。 - `components/PointDetailCard.vue`:关联设备**清单**(保留全部、每行标注互斥归类)+ **独立图例**。 - mock:默认场景照抄**现网边界**(设备统一 `UNVERIFIED_COORDINATE/renderable=false`); 新增 `device-layer` **受控有效坐标样例**(两台设备带自身有效坐标,且地图 `deviceCodes` 与详情 `devices[]` 一致)。 - 证据:`verify/t42-device-layer.mjs`(**37/37**)+ 变异 **19/19 全部打红**。 ### 三条关键取舍(都来自已 resolved 的规格 14 / 决策票 05,不在本票发明) 1. **总数只认当前快照去重 `deviceCodes`**,不拿详情统计或卡片数冒充 —— 详情不携地图快照的 `snapshotAt`、还可能复用缓存,两边**可能不同版**。所以「快照独有」与「详情独有」必须分开: 前者待确认、后者标不一致且**禁止上图**,两边都不静默丢弃。 2. **坐标准入只认设备自身 `coordinateValidation.renderable`**(票 07 的唯一判据): 前端不重算经纬度范围、**绝不**用监测点坐标代替设备坐标、不人工偏移(CONTEXT.md 明令禁止)。 现网无可信校核源 → 正式边界是**清单-only**,本票照抄这个边界。 3. **数据可用性先于模式可见性**(实现时抓到并修掉的真问题):若先判模式,一台「坐标被后端否决」 的设备在预警处置里会显示成「当前模式隐藏」,值班人员会以为"切到总览就能看到它"—— 而它**任何模式都上不了图**。说成模式隐藏是把数据问题推给了模式,差额就解释错了。 ### 真机走查(已执行):抓到并修掉 1 处真缺陷 本机 `@puppeteer/browsers` 是空目录(票 37 记录的既有环境缺陷),故按票 36/37/41 成例 **改用 DSH 自带浏览器真实驱动**(`yarn dev` @8000,真实 `public/cockpit/config.json`)。 **修掉的缺陷:UI 把「清单行数」当成了「关联总数」。** 真机读数暴露:总数显示 2 而当前快照 只有 1 台设备 —— 多出来的是「详情独有」那一行(规格明确它**不计入总数**)。 修:store 单独持有 `total`(=当前快照去重 `deviceCodes` 条数),与 `rows.length` 分开; 界面两个数都显示且各有明确含义。已补断言 `B1b-total-is-not-row-count` 钉住。 **实测证据(真实浏览器,与验收逐条对应)**: - **受控有效坐标样例**:两台设备**真的画在独立图层上**(`onMap=2`),实体 id 形如 `device|MP-001|EQ-001`,属性同时带 `deviceCode` + `monitoringPointCode`、**不含 `pointCode`** (不冒充监测点);摘要标记仍为 2(`data-map-markers=2`)、设备另计(`data-map-devices=2`)。 - **现网降级边界(清单-only)**:默认场景下设备统一 `UNVERIFIED_COORDINATE/renderable=false` → 清单保留并标 `no-location`、**引擎 0 台**、总数 1、清单 2 行(`inconsistent` 那行不计入总数、 不上图)、跨端点风险提示出现、摘要标记仍 2。 - **关闭即撤下**:点关闭 → 设备层 `onMap=0`、计划清空、宿主读数归零,**而摘要标记仍 2**(互不牵连)。 - **换点**:MP-001 → MP-002 后只剩 B 的设备(归属 `pointCode` 正确),A 的不残留。 - **独立拾取**:`pickDeviceMarkerAt()` 只认本图层带 `deviceCode` 的实体并回**身份**; 设备优先于监测点(票 15 的优先级)。 **未覆盖项(如实记录)**:走查时浏览器标签 `document.hidden===true`,rAF 被冻结、Cesium 不渲染 (票 37/41 记过同一环境事实),因此**依赖渲染的 `scene.pick` 在本次环境下连监测点标记也拾不到** (已用"连 `pointHit` 也为 null"的对照确认是环境而非本票缺陷)——「真实鼠标点设备点」这条 双向联动入口的走查**归票 43**(它正是双向对应的实现票,且 `pickDeviceMarkerAt()` 的接缝已就位)。 建议维护者在**前台标签页**复跑一次以补该条观感。 ### 与下游票的边界(不在本票实现) - **票 44**:设备形状/尺寸/资产接缝与**独立设备状态词表**。本票的设备标记用中性占位符号 (`shape` 由调用方给,默认 `circle`)与 14px 尺寸(小于监测点的 18px),**不实现状态词表**。 - **票 46**:完整四模式图层计划(`domain/modeLayerPlan.js`)与图例合成。本票只实现 **单点位的模式可见性**(`isVisibleInMode`),不做全图矩阵。 - **票 47**:设备状态特效。 - **票 43**:面板与三维设备点的双向对应(hover/选中/引线/定位)。 ### 回归 **零依赖逻辑闸门全绿**(t01/t03/t04/t05 **254**/t06/t07 **39**/t08 **44**/t09/t10 **57**/t11/t12/t13/ t14/t15/t16/t17/t18 **71**/t19/t21/t21-view/t29/t30/t36/t37/t38/t39/t41 **46**/t42 **37**)。 **两处红与本票无关**:① `t28-fill-container.mjs` 需 puppeteer,而本机 `@puppeteer/browsers` 是空目录 (既有环境缺陷);② 某次运行中 `t05` 的 `C8-real-attaches-credential` 一度红 —— 经核实是**并发会话** 正在改 `services/cockpitApi.js` / `mapApi.js`(本票改动清单里没有这两个文件),随后复跑已恢复绿。 **按 `AGENTS.md`:本会话未跑 build**(真机走查是维护者本轮明确要求才执行的)。 ### 下一步 1. 维护者在**前台标签页**复跑一次,补「真实鼠标点设备点」的双向联动观感(或直接由票 43 覆盖)。 2. `43`(面板与三维设备点双向对应)、`44`(设备位置符号与状态词表)、`46`(模式图层计划) 均已解除阻塞,可并行。 ## Comments ### local — 2026-10-10T08:10:42.403Z 整体验收审查:刷新等待首屏期间旧设备层仍在;快照失效未改变batchUpdatedAt时不撤层;缺重找点位、强制重取详情及完整找不到后关闭链。另同编码坐标/状态未重绘已在整体验收当前工作区修复(t48-linkage-invariants),尚待提交与真实样本回验。当前票因刷新生命周期未满足重新打开,先诊断再实施。