# report-confirm 被检测人选择面板整合 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking. **Goal:** 将 `report-confirm.vue` 中被检测人的选择/添加功能整合进"基本信息"卡片:点击被检测人字段原地展开选择面板(候选匹配 + 全量成员 + 新建入口),移除独立"选择家庭成员"卡片与"新建家庭成员表单"卡片。 **Architecture:** 单文件前端改造(`cfc-frontend/pages/health/report-confirm.vue`)。模板层把原独立卡片(L57-131)的内容移入基本信息卡片的被检测人字段下方,包在 `v-if="showMemberPanel"` 面板容器内;逻辑层新增 `showMemberPanel`/`selectedMemberObj`/`selectAndClose`/`toggleMemberPanel`/`syncSelectedMemberObj`/`memberDisplayText`,移除 `selectMember`/`selectCandidate`(其逻辑并入 `selectAndClose`),`loadFamilyMembers` 改为始终调用。`canConfirm`/`doConfirm` 完全不变。无后端改动。 **Tech Stack:** uni-app Vue 2 (Options API) / 微信小程序 mp-weixin ## Global Constraints - 仅前端:`cfc-frontend/pages/health/report-confirm.vue`,禁止改动后端 - Vue 2 Options API,禁止 Composition API - 禁止可选链 `?.`(用 `&&` 链式判断) - 禁止在 `:class` 绑定中调用方法(用内联表达式或计算属性) - 禁止中文 CSS 类名/选择器 - 小程序用 flexbox,禁止 CSS Grid - 禁止 `:key` 表达式(用 `idx`/`mIdx` 下标) - 统一 `@PostMapping`(本任务无接口变更) - 提交信息遵循仓库风格(`feat:` / `fix:` / `docs:` 前缀,中文描述) --- ### Task 1: 核心改造 — 模板 + 脚本 **Files:** - Modify: `cfc-frontend/pages/health/report-confirm.vue`(模板 L28-55 基本信息卡片、移除 L57-131、脚本 data/computed/onLoad/methods) **Interfaces:** - Produces: 新 data 字段 `showMemberPanel: false`、`selectedMemberObj: null`;新计算属性 `memberDisplayText`;新方法 `toggleMemberPanel()`、`selectAndClose(member)`、`syncSelectedMemberObj()`;`loadFamilyMembers()` 成功回调内新增 `syncSelectedMemberObj()` 调用;`doAutoCreate()` 成功分支新增回填+收起逻辑;移除 `selectMember(member)`、`selectCandidate(candidate)`。 - [x] **Step 1: 修改 data() — 新增两个字段** 在 `showCandidatesOnly: false,`(约 L308)之后插入: ```js showCandidatesOnly: false, // 被检测人选择面板 showMemberPanel: false, selectedMemberObj: null, ``` - [x] **Step 2: 修改 computed — 新增 memberDisplayText** 在 `confidenceDesc()`(约 L351 结束)与 `indicatorGroups()` 之间插入: ```js }, memberDisplayText() { var m = this.selectedMemberObj if (m && (m.nickname || m.name)) return m.nickname || m.name if (this.needBind) return '需手动绑定' return this.extractedName || '未识别' }, // 指标按类型(category)分组,保持解析顺序,统计需处理(非正常)数量 ``` - [x] **Step 3: 修改 onLoad — loadFamilyMembers 改为始终调用** 将 L427-438 的: ```js // 有候选时优先展示候选列表 if (this.candidates.length > 0) { this.showCandidatesOnly = true } if (this.needBind) { this.loadFamilyMembers() // 高置信度且已匹配 → 直接选中 if (this.matchedMemberId && this.matchConfidence >= 0.8) { this.selectedMemberId = this.matchedMemberId } } ``` 替换为: ```js // 有候选时优先展示候选列表 if (this.candidates.length > 0) { this.showCandidatesOnly = true } // 始终加载家庭成员(面板始终可点击,needBind=false 也允许改选) this.loadFamilyMembers() if (this.needBind) { // 高置信度且已匹配 → 直接选中 if (this.matchedMemberId && this.matchConfidence >= 0.8) { this.selectedMemberId = this.matchedMemberId } } ``` - [x] **Step 4: 修改 loadFamilyMembers — 成功回调同步选中成员** 在 `self.familyMembers = parents.concat(children)` 之后、`.catch` 之前插入 `self.syncSelectedMemberObj()`: ```js loadFamilyMembers() { var self = this // 从后端获取真实家庭成员(家长 + 孩子),storage 中并无该数据 getFamilyMembers().then(function(res) { var data = (res && res.data) || {} var parents = (data.parents || []).map(function(p) { return { id: p.id, nickname: p.realName || p.nickname, gender: p.gender, role: 'parent' } }) var children = (data.children || []).map(function(c) { return { id: c.id, nickname: c.nickname, gender: c.gender, age: c.age, role: 'child' } }) self.familyMembers = parents.concat(children) self.syncSelectedMemberObj() }).catch(function(e) { console.log('加载家庭成员失败', e) self.familyMembers = [] }) }, ``` - [x] **Step 5: 替换 selectMember/selectCandidate — 新增 toggleMemberPanel / selectAndClose / syncSelectedMemberObj** 删除 `selectMember(member)`(L458-460)与 `selectCandidate(candidate)`(L467-470)两个方法,在原 `selectMember` 位置插入: ```js toggleMemberPanel() { this.showMemberPanel = !this.showMemberPanel if (!this.showMemberPanel) { this.showAutoCreateForm = false } }, selectAndClose(member) { var memberId = member.id || member.userId this.selectedMemberId = memberId this.selectedMemberObj = member this.extractedName = member.nickname || member.name || this.extractedName this.extractedGender = member.gender || this.extractedGender this.extractedAge = member.age || this.extractedAge this.showAutoCreateForm = false this.showMemberPanel = false }, syncSelectedMemberObj() { if (!this.selectedMemberId) { this.selectedMemberObj = null return } var all = this.familyMembers.concat(this.candidates) for (var i = 0; i < all.length; i++) { var m = all[i] if ((m.id || m.userId) === this.selectedMemberId) { this.selectedMemberObj = m return } } }, ``` 注意:删除 `selectCandidate` 时保留其后的 `// === Phase 2: 候选匹配与新建成员 ===` 注释块位置不变。 - [x] **Step 6: 修改 doAutoCreate — 成功分支回填 + 收起面板** 将成功分支(约 L495-502): ```js if (memberId) { self.createdMemberId = memberId self.selectedMemberId = memberId self.showAutoCreateForm = false uni.showToast({ title: '创建成功', icon: 'success' }) } ``` 替换为: ```js if (memberId) { self.createdMemberId = memberId self.selectedMemberId = memberId self.selectedMemberObj = { id: memberId, nickname: self.newMember.name, gender: self.newMember.gender, age: self.newMember.age } self.extractedName = self.newMember.name self.extractedGender = self.newMember.gender self.extractedAge = self.newMember.age self.showAutoCreateForm = false self.showMemberPanel = false uni.showToast({ title: '创建成功', icon: 'success' }) } ``` - [x] **Step 7: 修改模板 — 被检测人行可点击 + 内联面板** 将 L30-34 的被检测人字段行: ```html 被检测人 {{ extractedName || '未识别' }} 需手动绑定 ``` 替换为: ```html 被检测人 {{ memberDisplayText }} {{ selectedMemberObj ? '修改 ›' : '选择 ›' }} 以下成员与报告信息匹配,请选择: {{ (cand.nickname || cand.name || '?').substring(0, 1) }} {{ cand.nickname || cand.name || '未知' }} {{ genderText(cand.gender) }}{{ cand.age ? ' · ' + cand.age + '岁' : '' }} 匹配: {{ cand.matchFields }} {{ Math.round(cand.similarityScore * 100) }}% {{ (member.nickname || '?').substring(0, 1) }} {{ member.nickname || '未知' }} + {{ showAutoCreateForm ? '收起新建表单' : '不在列表中?新建家庭成员' }} 姓名 性别 男 女 年龄 创建中... 确认创建 ``` - [x] **Step 8: 修改模板 — 删除独立"选择家庭成员"卡片与"新建家庭成员表单"卡片** 删除整个 L57-107(`` 卡片,`v-if="needBind"`)与 L109-131(`` 卡片,`v-if="showAutoCreateForm"`)。删除后,`` 注释成为基本信息卡片后的下一块。 - [x] **Step 9: 验证脚本层无残留引用** ```bash cd cfc-frontend grep -n "selectCandidate\|selectMember(" pages/health/report-confirm.vue grep -n "选择家庭成员\|auto-create-card" pages/health/report-confirm.vue ``` Expected: 无输出(0 匹配)。若有残留(如 `showCandidatesOnly` 定义仍在 data 中是正常的),逐个确认属于新增逻辑或保留逻辑。 - [x] **Step 10: 构建验证** ```bash cd cfc-frontend npm run build:mp-weixin ``` Expected: 退出码 0,输出无 `ERROR`(允许 3 个既有 `"export 'components' was not found"` warning)。构建产物出现在 `dist/build/mp-weixin/pages/health/`。 - [x] **Step 11: 检查编译产物 — 无方法调用泄漏进 WXML 类绑定** ```bash cd cfc-frontend Select-String -Path "dist/build/mp-weixin/pages/health/report-confirm.wxml" -Pattern "selectAndClose|selectCandidate|selectMember|memberDisplayText|toggleMemberPanel" | ForEach-Object { $_.Line.Substring(0, [Math]::Min(200, $_.Line.Length)) } ``` Expected: `selectAndClose`/`toggleMemberPanel` 以 `data-event-opts` 事件绑定形式存在;`memberDisplayText` 被编译为预计算值(如 `{{...mN}}` 形式,非函数调用);`selectCandidate`/`selectMember` 不出现。 - [x] **Step 12: Commit** ```bash cd D:\workspace\cfc git add cfc-frontend/pages/health/report-confirm.vue git commit -m "feat: report-confirm 被检测人选择整合进基本信息卡片(内联面板+移除独立卡片)" ``` --- ### Task 2: 样式补充与死样式清理 **Files:** - Modify: `cfc-frontend/pages/health/report-confirm.vue`(`