2026-10-09-indicator-change-curve-webadmin.md 26 KB

指标变化曲线 · 指标速查 · 关联配置 - 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<IndicatorDefinition>(定义搜索框数据源,客户端过滤)

IndicatorMappings.vue
  ├─ POST /api/admin/indicator/mapping/list       { page, size, status?, sourceType? } → Page<IndicatorMappingRule>
  │     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<IndicatorDefinition>),无 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 <script> 块写 /tmp/*.mjs 后 node --check(ESM 语法需 .mjs 扩展名)

3. 任务分解(TDD 小步,每步 commit)

每个 W 任务结尾必须 git commit(message 遵循仓库既有风格,如 feat(web): ...)。禁止一次 commit 覆盖多个 W。

W1 · 指标 API 封装(TDD:红 → 绿)

W1a(红) 新建 cfc-web/src/__tests__/api/indicator.test.js(照抄 guide.test.js 骨架):

  • beforeEach mock = new MockAdapter(axios);afterEach mock.restore()
  • 对 6 个封装各写 1 个用例,断言三件事:URL(mock.history.post[i].url)、method=POST、res.code===200 透传(expect(res.code).toBe(200))
  • 用例清单:
    • listIndicatorDefinitions({domain:'gut', status:1}) → /api/admin/indicator/list,断言 data 数组透传
    • saveIndicatorCorrelation({definitionIdA:1, definitionIdB:2, direction:'positive', manualStrength:0.6, description:'x', manualLocked:false, sourceNote:'y'}) → /api/admin/indicator/correlation/save
    • listIndicatorCorrelations({page:1, size:10}) → /api/admin/indicator/correlation/list,断言 res.data.records 透传
    • calibrateIndicatorCorrelations({}) → /api/admin/indicator/correlation/calibrate;calibrateIndicatorCorrelations({pairId:1}) 断言 body 含 pairId:1
    • importIndicatorMappings({sourceType:'gut', seeds:[{rawName:'乳酸菌', definitionId:3}]}) → /api/admin/indicator/mapping/import
    • listIndicatorMappings({page:1, size:10, status:0, sourceType:'gut'}) → /api/admin/indicator/mapping/list,断言 body 含 status/sourceType
  • 运行 npx jest src/__tests__/api/indicator.test.js → 确认红灯(模块不存在,import 报错)

W1b(实现) 新建 cfc-web/src/api/indicator.js:

import request from '@/utils/request'

export function listIndicatorDefinitions(params) {
  return request({ url: '/api/admin/indicator/list', method: 'post', data: params })
}

export function saveIndicatorCorrelation(data) {
  return request({ url: '/api/admin/indicator/correlation/save', method: 'post', data })
}

export function listIndicatorCorrelations(params) {
  return request({ url: '/api/admin/indicator/correlation/list', method: 'post', data: params })
}

export function calibrateIndicatorCorrelations(params) {
  return request({ url: '/api/admin/indicator/correlation/calibrate', method: 'post', data: params })
}

export function importIndicatorMappings(data) {
  return request({ url: '/api/admin/indicator/mapping/import', method: 'post', data })
}

export function listIndicatorMappings(params) {
  return request({ url: '/api/admin/indicator/mapping/list', method: 'post', data: params })
}

W1c(绿) 运行 npx jest src/__tests__/api/indicator.test.js → 全绿。

W1d(审计) grep -rn "indicator" cfc-web/src/api/ cfc-web/src/views/ | grep -v __tests__ → 确认 /api/admin/indicator 仅在本封装出现(views/admin/indicators/index.vue 的 this.$http 遗留调用的 URL 已失效,标记为遗留不改,但本计划新增页面一律走新封装)。commit。

W2 · 路由注册 + 菜单回退项

W2a cfc-web/src/router/index.js:在 L799(indicators 路由)与 L800(// ========== 虚拟服务商团队管理 ==========)之间插入:

      {
        path: 'indicator-correlations',
        name: 'IndicatorCorrelations',
        component: () => import('@/views/admin/IndicatorCorrelations.vue'),
        meta: { title: '指标关联配置', perm: 'config:health' }
      },
      {
        path: 'indicator-mappings',
        name: 'IndicatorMappings',
        component: () => import('@/views/admin/IndicatorMappings.vue'),
        meta: { title: '指标映射管理', perm: 'config:health' }
      },

W2b cfc-web/src/views/Layout.vue「健康配置」组(L325-333)children 末尾追加:

                { path: '/indicator-correlations', label: '指标关联配置', icon: 'el-icon-connection', perm: 'config:health' },
                { path: '/indicator-mappings', label: '指标映射管理', icon: 'el-icon-sort', perm: 'config:health' },

W2c(审计) 路由 path 去重:grep -n "path: 'indicator" cfc-web/src/router/index.js → 各 1 条;grep -n "indicator-correlations\|indicator-mappings" cfc-web/src/views/Layout.vue → 各 1 条。commit。

W3 · 指标关联对配置页(views/admin/IndicatorCorrelations.vue)

模板参照 EnergyRuleManagement.vue(admin-page + header + table + pagination + dialog + SortMixin)。结构:

模板区:

  • admin-page > page-header(标题「指标关联配置」+ 右侧「一键校准」按钮 el-button type=warning)
  • el-table border stripe v-loading,列:ID / 指标A(显示定义名称)/ 指标B / 方向(positive→tag「正向」type=success,negative→tag「负向」type=danger)/ 人工强度 manualStrength / 计算强度 computedStrength / 生效强度 effectiveStrength / 样本量 sampleSize / 置信度 confidence / 最近校准时间 formatTime(row.computedAt) / 人工锁定(manualLocked → tag「锁定」)/ 状态(status tag)/ 操作(编辑 · 单对校准)
  • el-pagination(page/total → listCorrelations() 刷新)
  • 新增/编辑 dialog el-dialog:
    • 指标A 定义搜索框:el-select filterable clearable :filter-method="filterDefsA"(选项来自 W3 加载的定义数组,显示 ${name}(${code}),value=id)
    • 指标B 同(filterDefsB);A 与 B 不能选同一个(save 前校验,this.$message.error('指标A与指标B不能相同'))
    • 方向 el-select(positive/negative,默认 positive)
    • 人工强度 el-input-number(min 0 / max 1 / step 0.05 / precision 2)
    • 说明 el-input type=textarea(description)
    • 人工锁定 el-switch(manualLocked,开启时后续校准不覆盖人工值)
    • 来源备注 el-input(sourceNote,可空)

脚本区:

  • data:list、total、page、size、loading、dialogVisible、form(含 definitionIdA/B/direction/manualStrength/description/manualLocked/sourceNote)、definitions、defMap(id→name)、defOptionsA/B(过滤后的候选数组)、editingId
  • created() { this.loadDefinitions(); this.listCorrelations() }
  • loadDefinitions():listIndicatorDefinitions({ status: 1 }).then(res => { this.definitions = res.data || []; 构建 defMap })——status=1 仅取启用定义;失败静默(列表仍可展示,名称显示 #id)
  • listCorrelations():listIndicatorCorrelations({ page, size }).then(res => { this.list = res.data.records; this.total = res.data.total }),.finally 关 loading
  • openCreate() / openEdit(row)(回填 form + editingId)
  • subCorrelations():校验 A≠B(if (!form.definitionIdA || !form.definitionIdB) return message('请选择指标A和指标B'))→ saveIndicatorCorrelation({...form}) → message 成功 → 关 dialog → 刷新列表
  • calibrateOne(row):this.$confirm('确定对该关联对执行校准?', ...) → calibrateIndicatorCorrelations({ pairId: row.id }).then(res => this.$message.success(res.message)) → 刷新
  • calibrateAll():this.$confirm('将基于样本数据重新计算全部关联对的有效强度,确定?') → calibrateIndicatorCorrelations({}).then(res => this.$message.success(res.message)) → 刷新
  • 定义名称解析:defName(id) { return (this.defMap[id] ||#${id}) }
  • filterDefsA(keyword) / filterDefsB(keyword):按 name/code 包含过滤 → defOptionsA/B(keyword 空则回退全量)

不做: 删除按钮(无端点);row 直接编辑(走 dialog)。

验证: node --check 提取 script 块(见 §4 命令)→ commit。

W4 · 指标映射管理页(views/admin/IndicatorMappings.vue)

W4a(红) 新建 cfc-web/src/__tests__/utils/indicatorSeeds.test.js:

  • parseSeedsText('') → [];null/undefined → []
  • JSON 数组:'[{"rawName":"乳酸菌","definitionId":3},{"rawName":"双歧杆菌"}]' → 2 条,definitionId 缺省为 null
  • CSV 两列:'乳酸菌,3\n双歧杆菌\n' → 第 1 条 {rawName:'乳酸菌', definitionId:3},第 2 条 {rawName:'双歧杆菌', definitionId:null}
  • 空行/纯空白行跳过;rawName 为空的条目跳过
  • 非法 JSON 但行结构可解析 → 按行解析不抛异常(回退 CSV 逻辑)
  • 运行 npx jest src/__tests__/utils/indicatorSeeds.test.js → 红灯

W4b(实现) 新建 cfc-web/src/utils/indicatorSeeds.js:

  • parseSeedsText(text):trim 空 → [];先尝试 JSON.parse 为数组 → 逐条规范化 {rawName: String(item.rawName||'').trim(), definitionId: item.definitionId || null},rawName 空跳过;JSON 解析失败 → 按行 CSV 解析:line.split(/[,,]/),col0=rawName(trim,空跳过),col1=definitionId(可空,空 → null,非数字 → null)
  • 纯函数、无副作用、无状态(模块内不 import request)

W4c(绿) jest 全绿 → commit。

W4d 页面模板/views/admin/IndicatorMappings.vue:

模板区:

  • admin-page > page-header(标题「指标映射管理」+ 右侧「批量导入」按钮)
  • 筛选区:el-select(状态:全部 / 生效 status=1 / 待确认 status=0)+ el-input(来源 sourceType)+ 「查询」按钮 → listMappings()
  • el-table border stripe v-loading,列:ID / 来源 sourceType / 原始名称 rawName / 别名 aliasText / 关联指标 defName(definitionId) / 匹配方式 matchType(ai_fallback → tag「AI 建议」warning,其余原样)/ 优先级 priority / 状态(status=0 → tag「待确认」warning,1 → tag「生效」success)/ 创建时间 formatTime(row.createdAt) / 操作(status=0 时显示「确认映射」)
  • el-pagination + SortMixin
  • 批量导入 dialog:
    • 来源 el-input(sourceType,必填)
    • 数据 el-input type=textarea(支持 JSON 数组 或 CSV 两列,placeholder 示例 [{"rawName":"乳酸菌","definitionId":3}] 或 乳酸菌,3)
    • 提交:parseSeedsText(text) → importIndicatorMappings({ sourceType, seeds }) → message 显示返回文本 → 关 dialog → 刷新
  • 确认映射 dialog(status=0 行):
    • 展示待确认 rawName(已确认 AI 建议:defName(aliasText 或 definitionId) 若无关联定义则空)
    • 定义搜索框:el-select filterable(选项=definitions,同 W3 加载与过滤方式)选择目标定义
    • 确认提交:importIndicatorMappings({ sourceType: row.sourceType, seeds: [{ rawName: row.rawName, definitionId: 选中的 id }] }) → 覆盖 status→1 → message 成功 → 刷新

脚本区:

  • data:list、total、page、size、loading、statusFilter、sourceTypeFilter、importDialogVisible、importForm{sourceType, text}、confirmDialogVisible、confirmRow、confirmDefId、definitions、defMap、defOptions
  • created() { this.loadDefinitions(); this.listMappings() }(loadDefinitions 同 W3,可抽公共但本期不新增公共组件,两页各保留一份轻量实现,注释标注来源即可——遵循「只改计划列出文件」)
  • listMappings():listIndicatorMappings({ page, size, status: this.statusFilter || undefined, sourceType: this.sourceTypeFilter || undefined })
  • submitImport():校验 sourceType/text 非空 → parse → if (!seeds.length) return message('未解析到有效条目') → import → 刷新
  • openConfirm(row) / submitConfirm():校验 confirmDefId 非空 → 单条 import → 刷新
  • defName(id) 同 W3

验证: node --check 两页 → commit。

W5 · 验证门禁(全部通过才算完成)

  1. 语法校验:对 src/api/indicator.js、src/utils/indicatorSeeds.js 及两个 .vue 提取的 script 块执行 node --check(命令见 §4),全部 exit 0。
  2. 单测:npx jest 全量 → 通过(含既有 guide 测试不得被破坏)。
  3. 构建:npx vue-cli-service build → BUILD SUCCESSFUL(严禁 npm run build)。
  4. 路由/封装去重审计:见 W1d/W2c 命令,exit 0。
  5. 契约对齐抽查:grep -n "correlation/save\|correlation/list\|correlation/calibrate\|mapping/import\|mapping/list" cfc-web/src/api/indicator.js → 5 个 URL 与 backend 计划 L3113-3117 完全一致。commit(如需)。

W6 · 收尾

  • 汇总变更文件清单(仅本计划列表内文件)。
  • 交付说明:页面入口 = 侧边栏「健康配置」组(动态菜单缺失时回退)或直连 /admin#/indicator-correlations、/admin#/indicator-mappings;依赖后端 P4-P5 已部署。
  • 不做任何文档同步(编排者负责 PROJECT-OVERVIEW / API_REFERENCE)。

4. 命令清单

# 单测(每步 TDD 红/绿)
cd cfc-web && npx jest src/__tests__/api/indicator.test.js --silent
cd cfc-web && npx jest src/__tests__/utils/indicatorSeeds.test.js --silent
cd cfc-web && npx jest --silent                      # W5 全量

# .vue script 提取 + 语法校验(ESM 需 .mjs 扩展名)
node -e "const fs=require('fs');const s=fs.readFileSync('cfc-web/src/views/admin/IndicatorCorrelations.vue','utf8');const m=s.match(/<script>([\\s\\S]*?)<\\/script>/);if(!m){console.error('no script');process.exit(1)}fs.writeFileSync('/tmp/IndicatorCorrelations.mjs',m[1])"
node --check /tmp/IndicatorCorrelations.mjs
# 同上对 IndicatorMappings.vue
cp cfc-web/src/api/indicator.js /tmp/indicator-api.mjs && node --check /tmp/indicator-api.mjs
cp cfc-web/src/utils/indicatorSeeds.js /tmp/indicatorSeeds.mjs && node --check /tmp/indicatorSeeds.mjs

# 构建门禁(禁 npm run build)
cd cfc-web && npx vue-cli-service build

# 去重审计
grep -n "path: 'indicator" cfc-web/src/router/index.js
grep -n "indicator-correlations\|indicator-mappings" cfc-web/src/views/Layout.vue
grep -rn "indicator/correlation\|indicator/mapping" cfc-web/src/api/ cfc-web/src/views/ | grep -v __tests__

# 契约对齐抽查
grep -n "correlation/save\|correlation/list\|correlation/calibrate\|mapping/import\|mapping/list" cfc-web/src/api/indicator.js

5. 验收标准(映射设计 §9)

# 验收项 判定
1 管理端可配置关联对(A/B 定义搜索框、方向、人工强度、说明、manualLocked 开关),保存后列表立即可见 手测:保存 → 列表刷新出现新行,字段回显正确
2 列表展示 computed_strength / sample_size / confidence / last_calibrated_at(=computedAt 格式化) 手测:有真实配对数据显示校准强度+样本量;样本 <30 显示人工值(effectiveStrength=manualStrength)
3 一键校准 + 单对校准,返回「校准完成,共更新 N 对」 手测:message 提示正确,列表刷新
4 映射规则管理:sourceType + rawName → definitionId;批量导入 JSON/CSV;status=0 待确认行可确认(重新 import 覆盖) 手测:导入返回「导入完成,共 N 条」;确认后行状态 tag 变「生效」
5 node --check 通过、npx jest 通过、npx vue-cli-service build 通过、API 去重审计通过(对应设计 §9 #8) §4 命令全部 exit 0
6 只修改计划列出文件;git 提交记录每个 W 独立 commit git show --stat 抽查

6. 风险与备注

  • 动态菜单可见性:生产侧边栏由后端 /api/admin/menu/tree 驱动,硬编码 menuItems 仅回退。若动态菜单数据未含新页面,入口不可见但路由可直达(URL 直连)。需与用户确认是否同步补充菜单配置数据(超出本计划代码范围)。
  • views/admin/indicators/index.vue 遗留 this.$http:其 URL 实为失效旧模式,不在本计划范围;新页面严格走 @/api/indicator.js。若后续清理该页可另立任务。
  • A/B 同指标:save 无后端校验同对重复,前端 dialog 拦截 A=B;同 defA/defB 重复保存由后端 upsert 处理(更新而非报错)。
  • no-delete 约束:产品侧如要求删除功能,需后端新增 delete 端点后追加(本期明确不做)。