# 指标变化曲线 · 指标速查 · 关联配置 - Web 管理端实施计划(P5) **版本:** v1.0 **日期:** 2026-10-09 **状态:** 待执行 **关联设计:** `docs/superpowers/specs/2026-10-09-indicator-change-curve-design.md`(v1.0 已确认) **依赖后端:** `docs/superpowers/plans/2026-10-09-indicator-change-curve-backend.md`(P4-P5 管理端 5 个 admin 接口必须先完成;本计划**不新增任何后端接口**) --- ## 面向 AI 代理的工作者 本计划由 AI 代理执行。目标:为 Vue 2 + Element UI Web 管理端(`cfc-web/`)实现「指标关联对配置页」「指标映射管理页」两个管理页面及对应 API 封装。所有接口复用后端计划 P4-P5 提供的 5 个 `/api/admin/indicator/*` 端点,**不新增、不修改任何后端代码**。 **执行纪律(违反即失败):** 1. **单一写者 TDD 闭环**:每个任务必须是「写测试 → 运行确认失败 → 实现 → 运行确认通过 → commit」的闭环。测试永远先行。`src/api/indicator.js` 与 `src/utils/indicatorSeeds.js` 必须配 axios-mock-adapter 单测(先例:`cfc-web/src/__tests__/api/guide.test.js`)。`.vue` 页面组件无组件单测先例 → 不做组件级 jest,用语法校验 + 构建门禁替代。 2. **禁止 `npm run build`**:`package.json` 的 `prebuild` 脚本会执行 `bump-version` + `generate-changelog`(污染 git 工作区)。构建门禁一律用 `npx vue-cli-service build`。禁止 `npm run serve`(占用端口且不产出验证结果)。 3. **请求层纪律**: - 所有 API 调用必须封装在 `cfc-web/src/api/indicator.js`,组件内禁止直接 `axios` / `this.$http`(`cfc-web/AGENTS.md` ANTI-PATTERNS)。 - 统一走 `@/utils/request`(`res.code === 200` 才 resolve、401 → clear+login、504/超时/网络错误分文案)。 - **禁止复制** `views/admin/indicators/index.vue` 的 `this.$http` + `res.code === 0` 旧模式(`main.js` 未注册 `$http`,属遗留写法,新页面不得沿用)。 4. **时间显示规范**:后端返回 ISO 8601(`2026-08-15T14:30:00`),页面统一 `formatTime(t) { return t ? t.replace('T', ' ').substring(0, 19) : '-' }`;禁止 `new Date(str).toLocaleString()`。 5. **只修改本计划列出的文件**,禁止顺手改动其他文件;不在本计划内做样式体系重构。 6. **无删除端点**:后端契约无删除接口 → 关联对列表**不提供删除按钮**;映射规则确认通过「携带 definitionId 重新 import 覆盖」实现(见 W4)。 7. **新增封装前查重**:`/api/admin/indicator/*` 5 个端点已在后端计划中记录至 `docs/superpowers/api/API_REFERENCE.md`,本计划仅前端复用,**不再改动 API_REFERENCE**;前端 `url: '/api/admin/indicator'` 必须在 `src/api/` 下单一封装(W1 审计确认)。 8. **API_REFERENCE / PROJECT-OVERVIEW 同步由编排者负责**,不在代理任务内(本计划任务不含文档同步)。 9. Element UI 组件一律按现有管理页模式:`admin-page` + `page-header` + `el-table border stripe v-loading` + `el-pagination` + `SortMixin`(参照 `views/admin/EnergyRuleManagement.vue` 261 行模板)。 --- ## 1. 目标与范围 ### 1.1 本期范围(P5) | # | 交付物 | 文件 | 说明 | |---|--------|------|------| | 1 | 指标 API 封装 | `cfc-web/src/api/indicator.js`(新建) | 6 个封装:listIndicatorDefinitions(复用)/ saveIndicatorCorrelation / listIndicatorCorrelations / calibrateIndicatorCorrelations / importIndicatorMappings / listIndicatorMappings | | 2 | 指标关联对配置页 | `cfc-web/src/views/admin/IndicatorCorrelations.vue`(新建) | 关联对列表 + 新增/编辑 dialog(A/B 定义搜索框、方向、人工强度、说明、人工锁定开关)+ 单对校准 + 一键校准 | | 3 | 指标映射管理页 | `cfc-web/src/views/admin/IndicatorMappings.vue`(新建) | 映射规则列表(sourceType/rawName/aliasText/关联指标/matchType/priority/status)+ 批量导入 dialog(JSON/CSV)+ 未匹配 raw review(待确认 → 选择定义重新 import 确认) | | 4 | 种子解析工具 | `cfc-web/src/utils/indicatorSeeds.js`(新建) | `parseSeedsText(text)` 纯函数:JSON 数组或 CSV 两列 → seeds 数组 | | 5 | 路由注册 | `cfc-web/src/router/index.js`(改) | 在 L799(indicators 路由后)追加 2 条子路由,perm `config:health` | | 6 | 菜单回退项 | `cfc-web/src/views/Layout.vue`(改) | 硬编码 `menuItems`「健康配置」组(L325-333)追加 2 项,perm `config:health`(动态菜单优先,此为回退可见性) | | 7 | 单测 | `cfc-web/src/__tests__/api/indicator.test.js`、`cfc-web/src/__tests__/utils/indicatorSeeds.test.js`(新建) | axios-mock-adapter 契约测试(先例 guide.test.js)+ 纯函数测试 | ### 1.2 明确不做 - **不做后端改动**:无新增 Controller/Service/实体/迁移;5 个 admin 端点全部由后端计划交付。 - **不做关联对删除**:后端无删除端点,本期不提供;后续需要时后端补 `delete` 端点再加按钮。 - **不做映射规则的独立更新/确认端点**:后端仅 import + list;「确认 AI 建议」= 携带 definitionId 重新 import 单条覆盖(status 0→1)。 - **不做动态菜单数据**:生产侧边栏可见性依赖后端动态菜单树(`/api/admin/menu/tree` 返回数据);本计划只保证路由可直达 + 硬编码菜单回退可见,动态菜单数据项需另行在管理端菜单配置或后端菜单种子中补充(编排者与用户确认)。 - **不做 e2e(强制)**:Playwright e2e 为可选验收形态(参照 `tests/e2e/energy-rules-config.spec.js`,`http://cfc.iwintrue.com/admin`),默认不下发。 ### 1.3 数据流(依赖后端 API 契约,与 backend 计划 L3113-3117 完全一致) ``` IndicatorCorrelations.vue ├─ POST /api/admin/indicator/correlation/list { page, size } → { code, message, data:{ records:[...], total } } │ record 字段:id, definitionIdA, definitionIdB, relationType, direction, manualStrength, │ computedStrength, method, sampleSize, confidence, effectiveStrength, manualLocked, │ cohortScope, status, computedAt, createdBy, description, sourceNote, createdAt, updatedAt ├─ POST /api/admin/indicator/correlation/save { definitionIdA, definitionIdB, direction, manualStrength, │ description, manualLocked, sourceNote } │ role!='admin' → code!=200 message='无权操作';缺 def → 'definitionIdA/definitionIdB 不能为空';IAE → 其 message ├─ POST /api/admin/indicator/correlation/calibrate { pairId? } → message='校准完成,共更新 N 对' │ pairId 传了但不存在 → message='关联对不存在' └─ POST /api/admin/indicator/list { domain?, category?, status? } → List(定义搜索框数据源,客户端过滤) IndicatorMappings.vue ├─ POST /api/admin/indicator/mapping/list { page, size, status?, sourceType? } → Page │ record 字段:id, sourceType, rawName, aliasText, definitionId, matchType, priority, status(0=待确认/1=生效), createdAt, updatedAt ├─ POST /api/admin/indicator/mapping/import { sourceType, seeds:[{rawName, definitionId?}] } │ → message='导入完成,共 N 条';sourceType 空 → 'sourceType 不能为空';seeds 空 → 'seeds 不能为空' └─ POST /api/admin/indicator/list (同上,映射编辑时选择 definitionId) ``` --- ## 2. 前置事实(实现依据,均已核验) | 事实 | 依据 | |------|------| | 复用 `POST /api/admin/indicator/list` 做定义搜索框 | `IndicatorAdminController` 既有端点(params domain/category/status → `List`),**无 keyword** → 前端按 name/code 客户端过滤(参照 `views/admin/indicators/index.vue` 的 filterKeyword 思路,但请求方式用 `@/api` 封装) | | `IndicatorDefinition` 字段 | id, domain, category, code, name, dataType, options, unit, refRange, sortOrder, status, createdAt, updatedAt | | `IndicatorCorrelation` 字段 | 见 1.3 契约(列表列 = 实体字段;`computedAt` 即设计文档的 `last_calibrated_at`) | | `IndicatorMappingRule` 字段 | 见 1.3 契约(`status=0` 为 ai_fallback 待确认;`status=1` 为生效) | | saveCorrelation 服务语义 | 后端 L2491:A/B 按 code 排序存储(defA 恒为 code 较小者),已存在则更新;单人工强度/方向/说明可空 | | calibrate 语义 | 样本量 ≥ 30 时 computed 覆盖 manual;未校准 effectiveStrength = manualStrength;返回文本「校准完成,共更新 N 对」 | | importSeeds 语义 | seed.rawName 必填(null/空跳过);definitionId 缺省 → lazyRegister;registerRule 按 sourceType+rawName upsert(已存在则更新字段)→ **重新 import 单条 = 覆盖确认** | | request.js 行为 | `res.code === 200` → resolve 整个 Result 体;非 200 → Message.error(res.message) + reject;401 → clear + /login;504/超时/网络错误分文案 | | jest 先例 | `cfc-web/src/__tests__/api/guide.test.js`:`MockAdapter(axios)`(axios=@/utils/request)+ `mock.onPost(url).reply(200, {code:200, data:...})` + `expect(res.code).toBe(200)`;`jest.config.js` testMatch 含 `**/__tests__/**/*.test.js`、moduleNameMapper `@/` → src | | 路由插入点 | `cfc-web/src/router/index.js` L794-799 = indicators 路由(perm `config:health`),L800 起为「虚拟服务商团队管理」注释 → **新路由插在 L799 与 L800 之间** | | 菜单回退插入点 | `cfc-web/src/views/Layout.vue`「健康配置」组 L325-333(子项 perm 均为 `config:health`)→ 追加 2 项 | | 构建门禁 | `npx vue-cli-service build`(禁 `npm run build`,prebuild 含 bump-version + generate-changelog 副作用) | | 语法校验 | 提取 `.vue` `