# 05: Mock 领域适配层 **What to build:** 建立驾驶舱领域适配层,让 mock 数据与真实接口使用同一契约。页面组件只消费领域对象,不直接依赖后端字段。 Blocked by: 04 Status: resolved Labels: ready-for-agent - [x] 存在可切换的 mock 数据源;默认 mock,真实接口由部署配置显式开启。 - [x] 真实模式下适配层统一附加部署层注入的 `Authorization` 请求头。 - [x] 接口字段到领域对象的转换集中在适配层。 - [x] 页面和 store 不直接处理后端裸字段。 - [x] mock 数据覆盖成功、空数据、字段缺失、失败和过期样例。 - [x] 401 或凭据类失败只把受影响分区标为数据不可用,保留已成功分区与地图骨架。 - [x] 有适配器转换单测。 ## 进度:100% **已完成并关闭(2026-09-18)。** 逻辑闸门 **178/178**、真机走查 **10/10**,维护者已开启服务并要求跑测(当时撞上沙箱 `spawn EPERM`,经批准后放行执行)。 ### 交付边界(与维护者确认,2026-09-18) Q1 裁定 **(B) 接缝 + 共享详情域**:票 05 交出「领域词汇 + 转换机制 + common 详情域适配器」,16 个**面板级**聚合留给 17–20(它们各自的验收原句就写着「有面板数据适配和联动单测」)。 其余裁定:Q2 分区状态词汇照搬后端四态 + 前端特有 `unavailable`;Q3 mock 照抄后端真实字段名;Q4 做「纯逻辑 + 最小可演示」;Q5 不新开 ADR。 ### 改动 **领域词汇与工具**(`domain/`,纯函数、可脱浏览器断言,不 import service) - `sectionState.js`:分区状态词汇。后端 `CockpitSectionState` 四态(`SUCCESS`/`EMPTY`/`STALE`/`FAILED`)**1:1** 映射,另加前端特有 `UNAVAILABLE`(凭据/链路类失败,对应票 04 的 `isCredentialFailure()` 与统一文案)。`STALE`(有值但过期,`retained:true`)与 `EMPTY`(确实没有值)严格分开 —— 这是票 16 两条验收原句的落点。`sectionFromResult()` 是唯一收口,`sectionView()` 给色键与文案,`overallState()` 让 `partial` 可与全坏区分。 - `requestReason.js`:`REQUEST_REASON` 从 `services/cockpitApi.js` 提到 domain。原因:`sectionState` 要按失败原因判「数据不可用」还是「加载失败」,而 domain 反向 import service 会层次倒挂。`cockpitApi.js` 仍转出该常量,**票 04 的脚本与调用点零改动**。 - `fields.js`:字段读取器。核心是区分「本该有却没有」(记 `missing`,界面显示占位符)与「本来就允许为空」(静默 `null`);缺失一律回 `null`,**绝不用 0 冒充**,而后端真给的 0 是有效数据。 - `mapSummary.js`(票 07/08 的输入)、`pointDetail.js`(票 09/10)、`equipmentDetail.js`(票 11)、`workOrder.js`(票 11/12)、`events.js`(票 13/14/19)、`freshness.js`(票 16):逐字段转换,字段名以 `E:\pipe-ner-server\docs\cockpit\*.html` 与 `research/panel-field-coverage.md` 为准。 **服务层**(ADR 0005 的领域拆分) - `domainRequest.js`:三个领域模块共用的「拼查询串 → 走票 04 的 `requestCockpitJson` → 收成一个分区」。 - `mapApi.js` / `detailApi.js` / `runtimeApi.js`:领域接口。**每个分区各自成败**,调用方拿到的是一组并列 `section()` 而不是共用成败的详情对象。 - `cockpitApi.js`(改):新增 `findMockTransport()` 通配匹配,让带路径参数的接口(`/equipments/*/detail-aggregate`)也能 mock;mock 处理器可在异常时声明失败原因,但只认白名单取值。 - `mocks/contractMocks.js` + `mocks/index.js`:契约化 mock。字段名与嵌套形状**照抄后端演示载荷**,值用边界值构造(坐标 VALID/UNVERIFIED/INVALID/ZERO、指标真 0、未结案 `null`)。场景开关走宿主全局键,**不改代码不重新构建**即可演示五类样例与 401。 **最小可演示**(Q4=B) - `components/SectionStateTag.vue` + `stores/detailStore.js`(ADR 0004 的 `detailStore`)+ `CockpitShell.vue` 筛选条右侧的分区状态板:三条共享分区各带 `data-section-state` / `data-section-name` / `data-section-tone` / `data-section-retained`,走查可直接断言。**只有 mock 模式才自动取数**:real 模式后端未就位,自动取数只会刷 404(沿用票 04「没有显式配置就不发请求」的立场)。 ### 验证证据 - **纯逻辑**:`node .scratch/cockpit-shell-panel-layout/verify/t05-adapter.mjs` → **178/178 通过**。四段:A 分区状态(四态映射、401 归类、过期 vs 空、部分可用、视图映射、**界面文案零原因码泄漏**);B 领域转换(用后端演示载荷的真实字段名喂入,含 0 值/缺失/null-time/超范围坐标/七态/回退动作/阈值只在报警/未知配色);C 契约化 mock(五类样例逐一取数、401 与前缀隔离、real 分支同一适配层且带凭据、假 401、坏载荷、查询串、缺编码不发请求、WS 增量帧两个字段名、注册幂等);D 静态守卫(10 个页面/store 文件零后端裸字段、零凭据、零登录跳转;8 个领域模块不自己发请求、不 import service)。 - **真机走查**:`node .scratch/cockpit-shell-panel-layout/verify/t05-adapter-walkthrough.mjs` → **10/10 通过**(`yarn dev` @ localhost:8000,维护者开启并要求跑测)。逐条:默认三条分区 `success`+`lv4`、引擎 `ready`、宿主唯一、四面板齐全、零页面级报错;空数据样例显示「无数据」中性色且不误报失败;过期样例 `stale`+`lv2`+`retained=true` 且文案含「已过期」;失败样例三分区 `failed` 而外壳/四面板/地图骨架全保留;**401 只标受影响分区**(设备详情 `unavailable`+`lv1`+「数据不可用」+「凭据未通过校验(由部署层注入)」,地图摘要与新鲜度仍 `success`)、地图骨架未被牵连、不跳登录页、零页面级报错。截图归档在 `verify/walkthrough/t05-sections-{success,401}.png`(走查产物,gitignore)。 - **回归**:`t01-panel-catalog` / `t03-camera-contract` / `t04-config` / `t06-assets` / `t21-terrain` / `t29-basemap` **全部通过(exit 0)**(票 04 的凭据守卫与既有断言未受影响)。 - **布局**:分区板接入后实测筛选条仍为 34px 行高、`scrollWidth === clientWidth`(无横向溢出)、文档无横向滚动。 - **编译**:`@vue/compiler-sfc` 对 `CockpitShell.vue` 与 `SectionStateTag.vue` 的 template 与 `