14-event-detail-and-locate.md 13 KB

14: 事件详情与设备定位

What to build: 点击事件后打开事件详情,并通过设备编码定位设备。地图高亮设备和关联管线,返回时保留原列表上下文。

Blocked by: 13

Status: resolved Labels: ready-for-agent

  • 事件详情包含编码、设备编码、指标、阈值、实际值、等级、时间和状态。
  • 点击事件可定位关联设备。—— 真机实测:相机真的飞到设备点位。
  • 地图高亮设备和关联管线。—— 设备侧已完成并真机实测(真机 data-map-emphasis=1、点位数不变;强调已有实体、零新建);关联管线的接缝已就绪并按设备码匹配,等票 22 的管线图层落地即生效(见下文分工)。
  • 返回路径保留列表位置和筛选条件。—— 真机实测:返回后 12 行逐位相同、族不变。
  • 无有效坐标时事件仍可查看并显示无地图位置原因。—— 逻辑闸门 5 项(含走查样例 E4);真机 D 组未跑成,已由维护者人工验收通过。
  • 有事件定位与返回状态单测。

进度:100%(已关闭)

2026-09-24 维护者人工验收通过,票 14 关闭。代码 + 逻辑闸门(44/44)+ 真机 DOM 走查 A/B/C/E 组全绿; D 组(无坐标样例)在真机上没跑成(@puppeteer/browsers 空目录致走查脚本起不来,运行时替代操作把浏览器宿主弄卡), 其路径由逻辑闸门 E4/E1/E2/D2/D4 覆盖,并由维护者人工验收补上最后一段确认。

真机走查结果(本次实测,从 DOM 读,不读 store)

  • A 详情真的画出来:真实鼠标点第 1 行 WRN-1001 → 详情卡出现、data-event-detail-code = 所点那一行;9 行字段全部从 DOM 读到(名称=燃气浓度预警、设备编码=EQ-002、 指标=GAS_CONCENTRATION、实际值=0.9 %LEL、等级=1级、时间、状态=进行中、位置=城区A); 列表 12 行逐位不变;点位数 2→2。
  • B 定位真动:点「定位关联设备」→ locateResult=ok、data-map-emphasis=1、 相机中心真的从家视角 (110.39028164, 28.4436787) 飞到设备点位 (110.142, 28.453)、 点位数 2→2(只高亮已有实体)。
  • C/E 返回 + 还原:点「返回事件列表」→ 详情收起、12 行逐位相同、族仍是「预警处置」、 data-map-emphasis 从 1 回到 0(这正是对抗式自查抓到的"收起不还原"那个 bug 的回归点)。
  • D 未跑成:要换 event-detail-no-coordinate 样例得让 mock 重新注册(场景在注册时被闭包捕获), 尝试「改全局键 + 切 hash 触发重挂载」把页面弄成空白,随后浏览器宿主卡死(截图/a11y/导航全超时; 同一时刻两个 dev server 仍 9–38 ms 正常响应,故非服务问题)。D 的等价证据留在逻辑闸门 E4 (直连 mock:12 行仍可看、hasLocation=false、位置「城区A · 坐标未经校验」、定位 blocked), 以及 E1/E2/D2/D4。没有把 D 说成跑过了。

一个副产品结论(写给以后要跑走查的人):本机 @puppeteer/browsers 目录是空的, 所以 t14-event-detail-walkthrough.mjs(以及 t28)起不来,只能改用内建浏览器手工驱动; 而内建浏览器窗口一旦被遮挡/隐藏,requestAnimationFrame 归零(实测 0 fps), cameraControl 的阻尼飞行就永远推进不了 —— 首轮我把「相机没动」误读成产品缺陷, 实为标签页不可见(document.hidden=true)。跑相机类走查前先确认标签页可见。

交付物

  • domain/eventDetail.js(新):详情视图(8 项字段 + 阈值半开区间 + 缺失不伪装零值)、 位置视图(「无地图位置原因」沿用票 07 的同一张中文词表)、 定位计划(只认后端 renderable)、高亮编码。
  • domain/mapEmphasis.js(新):强调判定的纯函数 + EMPHASIS_STYLE。 抽出来的理由是它能脱浏览器断言(引擎层 import 'cesium/...widgets.css',node 里加载不了)。
  • components/EventDetailCard.vue(新):详情卡(9 行字段 + 定位反馈 + 定位按钮 + 返回)。只画不判。
  • components/EventListPane.vue:事件行可点、可键盘回车打开详情;卡片挂在列表上方。
  • stores/eventStore.js:selectedEvent / locate / openEvent / closeEvent / locateEvent (打开与返回都不取数;定位注入点 { objects, mapReady, focus, highlight } 供单测)。
  • services/cesiumEngine.js:highlightEntities() —— 只改已存在实体的 billboard.scale / polyline.width,零 entities.add;emphasisBase(WeakMap)记住初值, 传空数组 = 还原全部;额外扫描票 22 的 cockpit-pipelines 数据源。
  • services/mapService.js:highlightMapObject(codes)(设备码 → 点位码解析,只读 markerPlan)
    • 宿主 data-map-emphasis 可观察信号(销毁时一并清)。
  • domain/mapSummary.js / domain/pointStatus.js / mocks/contractMocks.js:地图对象与标记计划带 deviceCodes(事件→设备的关联键;管线也按设备关联),样例与默认页数不变。
  • verify/t14-event-detail.mjs(新,44 项逻辑闸门)、 verify/t14-event-detail-walkthrough.mjs(新,真机走查,待执行)。
  • mock 新样例 event-detail-no-coordinate(事件照常给全,coordinateValidation.renderable=false): 让「无地图位置」这条路径在真机上真的走得通(逻辑闸门 E4 已证明它有效)。

验收自查(逐条对着验收原句)

  1. 8 项字段:A1/A2/A3 —— 编码/设备编码/指标/阈值/实际值/等级(色键+文字)/时间/状态齐全; 缺失走占位符「—」、真实的 0 照实显示;阈值只给下界时显示 ≥ x(不编一个上界)。
  2. 点击定位:列表行 → openEvent → 详情;locateEvent 按设备编码回联点位并下发相机目标 (C1/C3;没有点位码时也能按 deviceCodes 命中)。
  3. 高亮设备与关联管线:C2(编码含点位码 + 设备码)、F5(按设备码一次命中两个实体: 点位标记 + 关联管线)、F1/F2(零新建实体 —— planPointMarkers 逐位不变、highlightEntities 无 entities.add)。
  4. 返回保留上下文:G1/G2/G5 —— 返回不触发任何重取,行序、族、分区对象引用全原样 (H6 守住「closeEvent 零取数」这条构造性保证)。
  5. 无坐标仍可看:E1/E2/E4 + D2/D4 —— 详情 8 项照旧,位置一栏给原因(票 07 的词表), 定位如实报「不可上图」。
  6. 单测:t14 逻辑闸门 44/44 + 两次变异测试确认守卫有牙(见下)。

验证证据

  • node .scratch/cockpit-shell-panel-layout/verify/t14-event-detail.mjs → 44/44(八段:详情字段、 等级/状态同表、定位计划、四种定位结果、无坐标路径、高亮判定与还原、store 上下文、静态守卫)。
  • 变异测试(守卫确实会变红,不是假绿): ① 在引擎里恢复旧写法「目标为空就提前 return」→ F7 红(还原路径断掉); ② 把判定改成「目标为空 = 全亮」→ F6 红。
  • 回归全绿(全部逻辑闸门):t01、t04、t05(227)、t06、t07、t08、t09、t10(57)、t11(61)、 t12、t13(49)、t17(57)、t18(67)、t14(44)**、t21-terrain、t21-view-modes、t29、t30。 另有 t28 属 puppeteer 走查,在本机因缺 @puppeteer/browsers 起不来(环境问题,与本票无关)。
  • 按项目规则未跑 yarn build / yarn dev(AGENTS.md:改完代码不自动跑构建与调试命令, 另见「真实启动路径是 yarn dev」)。

真机走查(待执行,一条命令)

 node .scratch/cockpit-shell-panel-layout/verify/t14-event-detail-walkthrough.mjs

A 点行 → 详情真的从 DOM 画出来(8 项字段 + 编码=所点那一行);B 点定位 → 相机真动、 data-map-emphasis > 0、点位数不变;C 返回 → 详情收起、行码逐位相同、族不变; D 用 event-detail-no-coordinate 样例 → 详情完整 + 位置写出原因 + 定位 blocked; E 收起后 data-map-emphasis 回到 0(高亮真的还原)。

本次(2026-09-24)A / B / C / E 已在真机上逐项实测通过(证据见上文「真机走查结果」), D 未跑成(换样例的运行时操作把页面弄白、随后宿主卡死),其等价证据在逻辑闸门 E4/E1/E2/D2/D4。

与票 22(Cesium 管线渲染)的分工

本票交付「事件 → 设备 → 点位标记」的完整闭环,并给管线留了一条按设备码的现成接缝: highlightEntities() 已经扫描数据源 cockpit-pipelines,命中属性 pipelineCode / deviceCode。 票 22 只要把管线实体放进该数据源、带上这两个属性之一,「高亮关联管线」即自动生效 (不需要回头改本票)。在此之前管线层扫到 0 个 —— 如实报「关联设备尚未出现在地图上」, 不伪造成成功(G4)。

对抗式自查抓到的两个真 bug

  1. 详情把列表行的 level 当成等级数字:列表行上 level 已是算好的 {tone,label}, 详情再喂一次 eventLevelTag() → 恒判「等级未知」、status 恒为「—」(逻辑闸门第一版 在 A1/B1/E1 抓红)。改为优先直接沿用行上那一份(同表由"取同一份数据"保证)。 —— 又一次「测试写得像样,才看得见"能加载 ≠ 能渲染"」。
  2. 收起详情不还原高亮:highlightEntities 原先在目标为空时提前 return,一趟实体都没走, 被放大的标记会一直胖着、管线宽度也回不去。现改为每个实体都过判定, 未命中写回 emphasisBase 里记下的原值。走查 E1 就盯这一条。

移交给后续票(本票不追认)

  1. 管线侧强调的运行时断言:票 22 落地后,data-map-emphasis 的命中数会从「1(点位)」 变成「≥2(点位 + 管线)」,届时应补一条真机断言。本票的判定已按设备码匹配,无需改判定。
  2. D 组的 DOM 层补测(可选):待 puppeteer 可用后跑 node .scratch/cockpit-shell-panel-layout/verify/t14-event-detail-walkthrough.mjs (本机 @puppeteer/browsers 是空目录故起不来)。逻辑闸门 E4 已覆盖同一条路径。
  3. 视觉终稿:详情卡沿用现有 token 与本票局部样式;设计公司终稿替换时只改 styles/tokens.css。

Answer

事件详情与设备定位落地:点事件行开详情(8 项字段 + 位置/原因)、按设备编码回联点位并定位、 只强调已存在的地图实体(设备点位 + 关联管线)、返回不丢列表上下文。

  • 详情是列表行的纯函数投影(domain/eventDetail.js):等级/状态直接沿用行上那一份 (「列表与详情一致」是取同一份数据的结构保证,不是两边各算一遍),阈值支持半开区间、 缺失值走占位符而不伪装零值、0 是有效数据照实显示。
  • 定位复用票 17/18 立的那条接缝(domain/mapLocate.js 的四结果 + 只认后端 renderable), 不新造第二套判定或结果码;设备码优先回联、无点位码也能命中(deviceCodes)。
  • 高亮不新建实体(票 13 的硬纪律):highlightEntities() 只改已存在实体的 billboard.scale / polyline.width,零 entities.add;emphasisBase(WeakMap)记住初值, 空目标 = 全部还原(收起详情后标记不会一直胖着)。判定抽到 domain/mapEmphasis.js, 因此能脱浏览器断言(引擎层 import widgets.css,node 里加载不了)。
  • 返回保留上下文是构造性的:列表状态全在 store,收起详情只清选择与高亮,零取数 (H6 守住这条,否则「返回」会把列表滚回第 1 页)。
  • 无坐标路径用票 07 的同一张词表:新增 mock 样例 event-detail-no-coordinate 让这条路真走得通。

验证:逻辑闸门 verify/t14-event-detail.mjs 44/44(八段:详情字段 / 同表 / 定位计划 / 四结果 / 无坐标 / 高亮判定与还原 / store 上下文 / 静态守卫)+ 两个变异测试确认守卫有牙; 真机 DOM 走查 A/B/C/E 组逐项通过(详情真画出、相机真从家视角飞到设备点位、返回后 12 行逐位相同、 data-map-emphasis 由 1 回到 0);全量回归绿(t01/t04/t05(227)/t06/t07/t08/t09/t10(57)/t11(61)/t12/ t13(49)/t17(57)/t18(67)/t21×2/t29/t30)。2026-09-24 维护者人工验收通过。

两个只有对抗式自查才暴露的真 bug 已修:① 详情把列表行的 level(已是 {tone,label}) 当等级数字再喂一次判定 → 恒判「等级未知」、状态恒「—」;② highlightEntities 目标为空时提前 return → 收起详情不还原高亮(走查 E1 专盯这条)。

边界:事件不产生独立地图标记(只回联已有实体);本票不判断预警真伪/误报(后端契约没有该字段); 「高亮关联管线」的实际可见性等票 22 的管线图层落地(接缝已就绪、无需回头改本票)。