What to build: 在基础设施一级模式下同时展示基础档案、运行信息、管理信息、应急保障四个二级面板,并与中央地图保持联动。
Blocked by: 06, 08
Status: resolved Labels: ready-for-agent
已完成并关闭(2026-09-18)。 逻辑闸门 57/57、真机走查 28/28; 回归:15 个逻辑闸门全绿 + 4 个既有真机走查全绿(t01 20/20、t09 28/28、t10 28/28、t11 29/29)。
domain/infrastructurePanels.js(新):四块面板的契约适配 + 视图计划(纯函数)。
四个 to*() 把后端四份面板级聚合读模型翻成领域对象;四个 *Panel() 把领域对象翻成
组件可直接画的块(kpi / bars / rows / list)。domain/mapLocate.js(新):面板可定位对象 → 地图定位的共享接缝。三种"点不动"分得开
(不在已加载范围 / 后端判定不可上图 / 地图未就绪)。票 18 的台账与票 19 的事件定位复用同一条。services/infrastructureApi.js(新):四个端点的唯一出口,每块各自成败。stores/infrastructureStore.js(新):四块分区状态 + 按行业取数 + 定位联动。components/InfrastructurePanel.vue(新):四块面板的共享渲染层,按 block.kind 分派。domain/filterOptions.js:新增 INDUSTRY_CODES / industryQuery() / industryLabel()
(前端行业键 → 后端正式行业码)。domain/sectionState.js:BACKEND_TO_SECTION 认下基础设施三切片子分区用的 READY / UNAVAILABLE
(翻译同义词,不是放宽判定;认不出的取值仍一律落 FAILED)。mocks/contractMocks.js:四块面板的契约化样例 + infrastructure-partial 场景。1. 「责任区域」在后端契约里根本不存在,面板如实标缺口而不拿行业冒充。
逐类读过 CockpitInfrastructureManagementInformationResponse/Snapshot/Service:
SourceRelation 只有 objectCode/objectKind/industry/organizationCode/organizationName/ownershipType,
响应是 organizations + ownershipDistribution + mapFilter + excluded —— 全链路没有 region/area。
对比 base-archives 有 regionDistribution、emergency-support 有 area,所以不是漏读。
处理:MANAGEMENT_REGION_GAP 常量 + 界面一条缺口说明(指向票 25 的 E 组),不拿 industry 冒充区域。
这条修正了票 25 表格里「17 基础设施面板:D/E/F 契约已交付,可对接」的说法 —— E 交付了,
但票 17 验收点名的第三项(责任区域)不在该契约内。
2. 「可定位对象联动地图」是一条共享接缝,不是每个面板各写一套。
后端这些聚合切片只给点位编码、不给经纬度(operating-information 与 emergency-support 的
pointCodes),坐标在 mapStore 已加载的地图摘要里。所以联动本质是「回联 + 判定」:
按编码找对象 → 过票 07 的 isRenderable()(只认后端 renderable,前端绝不重算经纬度)
→ 票 03 的 setCameraFocus() 移动相机 → 票 10 的 openPoint() 打开同一张点位详情卡。
三种失败必须分得开(否则值班人员不知道是自己点错了还是数据有问题):
missing(不在已加载分页里)/ blocked(后端判定不可上图,原因用票 07 同一张词表)/ unavailable(地图未就绪)。
archive 与 management 没有可定位对象(前者是聚合分布口径、后端不返回逐设施坐标;
后者只到机构维度),如实说明而不是造假按钮。
3. 行业筛选只接行业一维,范围/区域不接。
CONTEXT.md 定义行业筛选是作用于整个驾驶舱的全局条件,所以本票把它落到四个端点的 industry 入参上。
但井盖在后端正式行业枚举里没有契约位(票 25 第八轮已记:库里另有 manhole_base,正式枚举只有三个),
所以 manhole 按「不传参」处理并在面板上如实说明,不拿一个后端不认的字符串去换 400。
票 18 更正(2026-09-28):这条判断的依据已被后端推翻。
MANHOLE现已是第四个正式行业,且全部 11 个 cockpit 服务都收(含map-snapshot)——git show HEAD可见CockpitMapSnapshotService第 39 行为Arrays.asList("GAS","WATER_SUPPLY","DRAINAGE","MANHOLE"),认不出的行业仍 400 但文案已改为 「must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE」。 因此manhole现在正常下发(domain/filterOptions.js的INDUSTRY_CODES已加manhole: 'MANHOLE', 该文件由本票与票 18 共用)。本票闸门t17-infrastructure-panels.mjs的 E1/E2 断言已同步更新 —— 它原来断言的industryQuery('manhole') === ''守的正是这条已被推翻的假设。 本票其余结论不受影响(它交付的四块面板与坐标准入/定位接缝都未变,逻辑闸门仍 57/57)。 范围/区域不接:archive 是region(CN/CB/…)、emergency 是area(CN/TC/…),与filterOptions的south/north/taichang对不上,那需要先裁定一套映射 —— 属票 18 的域,本票不预支。
4. 后端 Distribution 是三类维度共用结构,一律读 code 会全部落空。
CockpitInfrastructureEmergencySupportService 的三个构造函数把值分别填在
industry / area / category 上,code 一律是 null。所以适配层必须按维度给取键字段。
这条只有对着后端源码逐行读才会发现,光看演示载荷(那几行确实是 { industry: '…', count: n })
容易当成 code。已写进 B7b 断言,并做了变异测试确认它抓得到(改回 code 即红)。
5. BigDecimal 在线上是字符串,Number() 直吃会把真值变成假 0。
演示载荷里是 "0.32"、"1.2500",所以适配层用 numberish 宽进严出:数字与数字串都认,
真 0 是有效读数,认不出的回 null → 界面画占位符。变异测试确认:把 numberish 换成 Number()
会打红 4 条(含「能力基准缺失不显示虚假 0 负荷」)。
逻辑闸门 54/54(verify/t17-infrastructure-panels.mjs,七段):
node .scratch/cockpit-shell-panel-layout/verify/t17-infrastructure-panels.mjs
UNKNOWN/EMPTY)翻人话而不泄漏。UNAVAILABLE ≠ EMPTY。all/manhole 不传参;端到端请求串里真的带上了码。renderable(越界坐标只要后端说
可上图就放行、合法坐标只要后端说不可上图就拦下);store 只在成功那一次动相机+开详情。对抗式自查(变异测试,五条都确认守卫有牙):
① 把 isRenderable() 换成前端自算经纬度范围(票 07 明令禁止的第二套判据)→ 打红 4 条;
② Distribution 改回一律读 code → 打红 B7b;③ numberish 换成 Number() → 打红 4 条;
④ 去掉"未取数 → 正在取数"分支(首帧就会谎报"没有数据")→ 打红 C7;
⑤ 摘掉行业跟随(筛选只记在 store、不重取)→ 打红 G10。
五次变异后均已还原并复跑至全绿。
回归 14 个既有闸门 + 本票闸门全绿(exit=0): t01 / t04 / t05 206 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 / t21 / t21-view / t29 / t30(既有),t17 54(本票)。
<PanelPlaceholder :panel-id="panelId" /> 字面匹配):
外壳现在按 panelId 分派,占位组件前面出现了 v-else。放宽的是「前面不许有指令」,
保留的是它真正要守的两条契约(占位文本未变 + 占位组件仍带着 panelId 绑定被真实使用)。
改完做了变异测试:把占位组件从外壳里摘掉,C1 与 C5 仍然双红。node .scratch/cockpit-shell-panel-layout/verify/t17-infrastructure-panels-walkthrough.mjs
六组,面板内容一律从 DOM 读(证明界面真画出来了,而不是 store 里有值):
success 且各有真内容
(档案 设施总数=5处 / 里程=12.4km / 规模=中型、运行信息 供应=0.32 MPa / 输送=1200.5 m3/h / 承载=800 m3
= 验收点名的三类能力、管理信息 3 家管理单位 + 责任区域缺口说明、应急保障三段 危险源=1 / 防护目标=1 / 应急资源=2);
本模式 0 块占位(四块全是真面板),切到总体概览仍是 4 块占位(票 18/19/20 的域);
两栏可见文本零后端代号(查 26 个状态码与枚举值);无页面错误。MP-002 后
相机中心真的落在 110.14200,28.45300(目标 110.142,28.453)、打开了同一张点位详情卡
(level=point pointCode=MP-002)、成功时不给多余文字、地图标记数不变(2 → 2)(面板不往地图上加对象)。MP-003(后端判 UNVERIFIED)→ 给出 坐标未经校验、相机没动、
详情卡仍停在 MP-002(没被换成被拒的点位)。414 → 42px(收起真的缩回标题栏高度,不是只藏内容)。infrastructure-partial 下防护目标段「该段数据未取到」且 0 条,
危险源 1 条、应急资源 2 条照常画出(一段坏了不遮盖其它段);空场景四块都是明确占位说明且不伪造 0。走查逮到的真问题(这层证据的价值所在,三条都不是纯逻辑能发现的):
规模=medium —— 后端 scale 是数据源透传的自由字符串
(契约手册与 CockpitContractDictionaryTest 都只把它当值,后端测试断言的就是 "medium"),
我的 CODE_TEXT 漏了这三个词,于是英文枚举印到了大屏上。已修:补 large/medium/small → 大型/中型/小型,
并加两条断言(逐值 + 归纳版「登记过的码翻译结果不得等于原码」)。
我的 A9 守卫当时只查状态码、不查枚举值,所以纯逻辑全绿而真机露馅 —— 守卫面已按此加宽到 26 个词。data-item-state 挂在 v-else-if 上,而与本契约里 stateText 与 rows 互斥,
那个分支永远不执行,段状态实际走的是空列表分支(文案能显示但挂在错误的节点上)。
已改成 stateText 优先且不挂 v-else-if,并补 D6b 把两者的互斥性钉住。逻辑闸门 15 个全绿(exit=0): t01 / t04 / t05 206 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 / t21 / t21-view / t29 / t30(既有),t17 57(本票)。
既有真机走查 4 个全绿:t01 20/20、t09 28/28、t10 28/28、t11 29/29。
<PanelPlaceholder :panel-id="panelId" /> 字面匹配):
外壳现在按 panelId 分派,占位组件前面出现了 v-else。放宽的是「前面不许有指令」,
保留的是它真正要守的两条契约。改完做了变异测试:把占位组件从外壳里摘掉,C1 与 C5 仍然双红。body.split('·')[1]
从占位文案里反推 panelId,而本票把基础设施四块换成真实面板后占位组件不再渲染,
panelId 解析成空、A5 假红。断言钉在了"面板体长什么样"上 —— 那本是实现细节
(占位或真实内容都该通过)。已改为读外壳新给的稳定属性 data-panel-id
(CockpitPanel 新增 panelId prop,渲染为 data-panel-id),
A5 与四个既有走查随即全部恢复(t01 19/20 → 20/20)。① isRenderable() 换成前端自算经纬度范围(票 07 明令禁止的第二套判据)→ 打红 4 条;
② Distribution 改回一律读 code → 打红 B7b;③ numberish 换成 Number() → 打红 4 条;
④ 去掉"未取数 → 正在取数"分支(首帧就会谎报"没有数据")→ 打红 C7;
⑤ 摘掉行业跟随(筛选只记在 store、不重取)→ 打红 G10;
⑥ 撤掉 scale 直译(复现真机逮到的英文上屏)→ 打红 B4b/B4c;
⑦ 把占位组件从外壳摘掉 → t06 的 C1/C5 双红。
七次变异后均已还原并复跑至全绿。
基础设施模式四块面板落地:各接一个后端面板级聚合读模型端点、四块各自成败
(一块坏了其余三块照常画出真实内容),契约适配与视图计划收在一层纯函数里由四块共用;
「点面板里的可定位对象联动地图」做成一条共享接缝(按点位编码回联已加载的地图对象 →
只认后端 renderable 的坐标准入 → 移动相机 + 打开与地图同源的两级详情卡),
三种"点不动"分得开且不假装成功。
三条边界如实划清,不发明业务:
① 「责任区域」在后端管理信息契约里没有字段(逐类读过响应类与快照类),面板以缺口说明呈现,
不拿行业冒充区域 —— 这条修正了票 25「17 的 D/E/F 已交付可对接」的说法;
② 行业筛选只接行业一维(gas/water/drainage 1:1 映到后端码;all 不传参;
井盖在后端正式行业枚举里没有契约位,同样不传参并如实说明),范围/区域归票 18;
③ 基础档案与管理信息没有可定位对象(前者是聚合分布口径、后端不返回逐设施坐标),
如实说明而不是造假按钮。
票面七条验收全部达成(逐条见正文勾选)。收尾时走查逮到并修掉一处真泄漏
(后端透传的自由字符串 scale 未翻译,英文枚举 medium 直接印上大屏)与组件里一段死代码,
两者都只有真机走查能发现 —— 这也是本票坚持跑走查的原因。
已认领。本票无 needs-triage 标签,Blocked by: 06, 08 且两张前置票均已 resolved,
故不经过诊断阶段,直接进入实施。
实施前就把五处"不该猜"的地方提出来并拿到裁定:① 责任区域契约缺口的呈现方式(如实说明,不冒充); ② 可定位对象联动地图的接缝与语义;③ 是否跟随全局行业筛选(只接行业,范围归票 18); ④ 是否为 18/19/20 立共享接缝(立);⑤ 自查范围(跑逻辑脚本、写但不跑走查)。
维护者明确「同意执行真机走查」后执行:首轮 23/26(三条失败:两条是我自己走查脚本的错断言,
一条暴露出组件里的死代码),修完复跑 27/27;走查日志里读出 规模=medium,
顺藤查实是 scale 未翻译的真泄漏并修掉,扩宽泄漏守卫后末轮 28/28。
期间 t01 的 A5 因我改了面板内容而假红(它从占位文案反推 panelId),已改为读稳定的 data-panel-id,
t01 随之恢复 20/20。