# 成员模型重构:清理「孩子/child」角色定义 实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 项目不再定义"孩子(child)"特殊角色,统一为"成员(member)"体系——成员分为**可登录成员**(有 openid,邀请/绑定而来)与**不可登录成员**(用户创建,无 openid,仅可通过切换使用);切换身份与退出切换需原用户密码保护。 **用户决策(2026-09-05):** 1. 切换密码未单独设置时,默认使用登录密码校验(复用 `users.password`,不新增独立字段) 2. 存量 `role='child'` 账号**强制迁移**:有 openid 的 → 迁移为"可登录成员"(role 改 parent,登录后进成员端界面,不能被切换);无 openid 的 → 迁移为"不可登录成员"(仅可被切换),并确保有对应 `family_members` 记录 **架构:** 以 `users.openid` 是否为空作为可登录/不可登录的唯一判定依据(创建的成员也创建 user 记录但不设 openid;输入手机号时预留未来注册复用)。角色层移除 `role='child'` 的语义判断,前端切换视图状态从 child 语义迁移到 member 语义,切换/退出切换接入密码校验。`executorType/creatorType/memberType` 业务字段值保留 `'child'`,仅注释改为"非登录用户"。 **技术栈:** Java 8 / Spring Boot 2.7.18 / MyBatis-Plus / uni-app Vue2 小程序 / MySQL --- ## 涉及文件清单(按职责分组) | 文件 | 职责 | |------|------| | `cfc-backend/.../entity/FamilyMember.java` | 成员实体,新增 `memberType` 相关辅助方法 | | `cfc-backend/.../entity/User.java` | 用户实体(已有 openid,无需改动,仅确认) | | `cfc-backend/.../dto/FamilyMemberVO.java` | 成员 VO,新增 `canLogin` / `isSwitchable` 字段 | | `cfc-backend/.../dto/SwitchMemberVO.java` | 切换 VO,新增 `isSwitchable` 标记 | | `cfc-backend/.../service/FamilyMemberService.java` | 核心:`computeEffectiveRole` 迁移为 `computeMemberType`,`switchToMember` 加"仅可切换不可登录成员"校验,`addMember` 创建 user 记录 | | `cfc-backend/.../service/UserService.java` | 核心:登录/注册时复用无 openid 的 user、切换密码校验、`setSwitchPassword` | | `cfc-backend/.../service/api/UserServiceInterface.java` | 接口同步 | | `cfc-backend/.../controller/auth/AuthController.java` | 新增 `set-switch-password`、`switch-back-verify` 恢复 | | `cfc-backend/.../controller/family/FamilyMembersController.java` | 切换接口校验、退出切换接口 | | `cfc-backend/.../config/DatabaseInitializer.java` | 迁移:family_members 增加 `member_type` 辅助索引、注释同步 | | `cfc-backend/src/main/resources/schema.sql` | 同步表注释 | | `cfc-frontend/store/index.js` | 移除 child 切换状态,统一 currentMemberId/memberType | | `cfc-frontend/components/FamilyMemberStrip.vue` | `canSwitch` 反转:仅不可登录成员可切换 | | `cfc-frontend/utils/api.js` | 新增密码相关封装 | | `cfc-frontend/pages/...`(child 视图迁移) | 逐页替换 child 语义判断 | --- ## 阶段 A:后端成员类型模型 ### 任务 A1:FamilyMember 实体新增成员类型辅助 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMember.java` - [ ] **步骤 1:新增 `memberType` 计算辅助方法** 在 `FamilyMember.java` 中新增两个方法(紧邻现有 `getAge()` 之后): ```java /** * 成员类型: login=可登录成员(有openid) / local=不可登录成员(用户创建,无openid) * 判定依据: userId 关联的 User 是否有 openid * 注意: 该字段为 VO 层计算值,不落库;由 service 层传入 userId 对应的 openid 状态后设置 */ // 表驱动字段(冗余,由 service 维护) private String memberType; /** * 是否可被切换(不可登录成员才可被切换) */ public boolean isSwitchable(String userOpenid) { // userId 为空 或 user 无 openid → 不可登录 → 可切换 return userOpenid == null || userOpenid.isEmpty(); } ``` > **说明:** 判定逻辑放在 service 层(需要 join users 表),实体仅保留 VO 序列化字段 `memberType`。`isSwitchable` 接受 openid 参数,由 service 注入。 - [ ] **步骤 2:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMember.java git commit -m "feat(family): FamilyMember 新增 memberType 成员类型字段与可切换判定" ``` ### 任务 A2:FamilyMemberVO 增加成员类型字段 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyMemberVO.java` - [ ] **步骤 1:新增字段** ```java /** 成员类型: login=可登录成员 / local=不可登录成员(用户创建,无 openid) */ private String memberType; /** 是否可被切换视角(仅不可登录成员 true) */ private Boolean isSwitchable; ``` - [ ] **步骤 2:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS ### 任务 A3:switchToMember 核心重构 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java` 的 `switchToMember`(约 298 行) - [ ] **步骤 1:重写 switchToMember 加"仅可切换不可登录成员"校验** ```java public SwitchMemberVO switchToMember(Long userId, Long memberId) { User user = userMapper.selectById(userId); if (user == null || user.getFamilyId() == null) { throw new RuntimeException("用户未加入家庭"); } // 从 family_members 查找 FamilyMember member = familyMemberMapper.selectById(memberId); if (member != null && member.getFamilyId().equals(user.getFamilyId())) { // 【重构】成员切换校验:仅不可登录成员可被切换 User memberUser = member.getUserId() != null ? userMapper.selectById(member.getUserId()) : null; String openid = memberUser != null ? memberUser.getOpenid() : null; boolean canLogin = openid != null && !openid.isEmpty(); if (canLogin) { throw new RuntimeException("该成员可通过微信登录,不能切换为TA的身份"); } SwitchMemberVO vo = new SwitchMemberVO(); vo.setMemberId(member.getId()); vo.setSource("family_member"); vo.setEffectiveRole(computeEffectiveRole(member)); vo.setNickname(member.getNickname()); vo.setIsSwitchable(true); return vo; } throw new RuntimeException("成员不存在或不属于您的家庭"); } ``` - [ ] **步骤 2:SwitchMemberVO 增加 isSwitchable 字段** `cfc-backend/src/main/java/com/etotem/cfc/dto/SwitchMemberVO.java` 新增: ```java /** 是否可被切换(服务端已校验,仅不可登录成员返回 true) */ private Boolean isSwitchable; ``` - [ ] **步骤 3:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java cfc-backend/src/main/java/com/etotem/cfc/dto/SwitchMemberVO.java git commit -m "feat(family): switchToMember 仅允许切换不可登录成员" ``` ### 任务 A4:addMember 创建 user 记录 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java` 的 `addMember`(约 70 行) - [ ] **步骤 1:成员创建时同步创建 user 记录(无 openid)** 在 `addMember` 中,手机号匹配逻辑(约 130-143 行)替换为: ```java // 【重构】成员创建:同步创建 user 记录(无 openid = 不可登录) // 1) 若填写了手机号且已存在同手机号用户 → 复用该 user(绑定 userId) // 2) 若填写了手机号但不存在 → 创建无 openid 的 user,预留未来注册复用 // 3) 未填写手机号 → 创建无 openid 的 user Long memberUserId = null; if (dto.getPhone() != null && !dto.getPhone().isEmpty()) { User matchedUser = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getPhone, dto.getPhone())); if (matchedUser != null) { memberUserId = matchedUser.getId(); // 将匹配到的用户加入同一家庭(如果尚未加入) if (matchedUser.getFamilyId() == null) { matchedUser.setFamilyId(familyId); matchedUser.setUpdatedAt(new Date()); userMapper.updateById(matchedUser); } } else { // 创建预留用户(无 openid,后续同手机号注册时复用) User newUser = new User(); newUser.setOpenid(""); newUser.setUnionid(""); newUser.setRole("parent"); // 无 child 角色,统一 parent 兜底(实际不可登录) newUser.setNickname(dto.getNickname().trim()); newUser.setPhone(dto.getPhone()); newUser.setFamilyId(familyId); newUser.setPassword(""); // 初始密码为空,切换保护密码后续由用户设置 newUser.setCreatedAt(new Date()); newUser.setUpdatedAt(new Date()); userMapper.insert(newUser); memberUserId = newUser.getId(); } } else { // 未填手机号:创建无 openid 的 user(昵称必须非空) User newUser = new User(); newUser.setOpenid(""); newUser.setUnionid(""); newUser.setRole("parent"); newUser.setNickname(dto.getNickname().trim()); newUser.setFamilyId(familyId); newUser.setPassword(""); newUser.setCreatedAt(new Date()); newUser.setUpdatedAt(new Date()); userMapper.insert(newUser); memberUserId = newUser.getId(); } member.setUserId(memberUserId); ``` - [ ] **步骤 2:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java git commit -m "feat(family): addMember 同步创建无 openid 的 user 记录(不可登录成员)" ``` ### 任务 A5:登录/注册复用无 openid 用户 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java` - [ ] **步骤 1:微信/手机登录时优先匹配无 openid 的预留用户** 在 `phoneLogin`、`wechatPhoneLogin` 等登录方法中,手机号匹配用户时补充:若匹配到 `openid` 为空的用户(预留成员),则直接给该 user 补上 openid 并沿用其 familyId: ```java // 在登录方法中,查找到 matchedUser 后补充: if (matchedUser.getOpenid() == null || matchedUser.getOpenid().isEmpty()) { // 预留成员首次登录:补 openid,成为可登录成员 matchedUser.setOpenid(dto.getOpenid() != null ? dto.getOpenid() : "phone:" + dto.getPhone()); matchedUser.setRole("parent"); matchedUser.setUpdatedAt(new Date()); userMapper.updateById(matchedUser); } ``` > **注意:** 具体改哪个方法取决于现有登录实现,先检查 `phoneLogin` / `wechatPhoneLogin` / `silentLogin` / `autoLoginByOpenid` 的当前匹配逻辑再落点。 - [ ] **步骤 2:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java git commit -m "feat(auth): 登录时复用无 openid 的预留成员用户并补充 openid" ``` --- ## 阶段 B:切换密码保护 ### 任务 B1:切换密码设置接口 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/controller/auth/AuthController.java`、`cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java` - [ ] **步骤 1:新增 setSwitchPassword 服务方法** `UserService.java` 新增: ```java /** * 设置切换保护密码(切换前由原用户设置,退出切换时校验) * @param userId 设置密码的用户(原用户) * @param password 保护密码 */ public boolean setSwitchPassword(Long userId, String password) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } user.setPassword(encryptPassword(password)); user.setUpdatedAt(new Date()); userMapper.updateById(user); return true; } ``` - [ ] **步骤 2:新增 set-switch-password 接口** `AuthController.java` 新增: ```java @Operation(summary = "设置切换保护密码") @PostMapping("/set-switch-password") public Result setSwitchPassword(@RequestBody SetPasswordDTO dto, @RequestAttribute("userId") Long userId) { boolean success = userService.setSwitchPassword(userId, dto.getPassword()); return Result.success(success); } ``` > 复用现有 `SetPasswordDTO`(含 password 字段)。`setPassword` 与 `setSwitchPassword` 语义相同(都写 users.password),可考虑合并,但保留独立接口便于前端语义区分。 - [ ] **步骤 3:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/auth/AuthController.java cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java git commit -m "feat(auth): 新增切换保护密码设置接口" ``` ### 任务 B2:退出切换接口恢复(带密码校验) **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java` - [ ] **步骤 1:恢复 switch-back-verify 接口(已废弃的 410 改为有效实现)** `FamilyUserController.java` 中约 136 行的废弃方法替换为: ```java @Operation(summary = "带密码校验退出成员切换(返回原用户身份)") @PostMapping("/switch-back-verify") public Result switchBackVerify(@RequestAttribute("userId") Long userId, @RequestBody Map body) { String password = body != null ? body.get("password") : null; if (password == null || password.isEmpty()) { return Result.error("请输入密码"); } // 校验当前用户密码(切换操作由原用户发起,password 存在 users.password) boolean valid = userService.verifyPassword(userId, password); if (!valid) { return Result.error("密码错误"); } return Result.success(true); } ``` > **关键:** 退出切换的语义是"返回切换前的用户"。前端切换时保存原用户 token 与密码,退出时用当前 JWT 的 userId 校验原用户密码(因为切换不换 token,JWT 仍是原用户的)。 - [ ] **步骤 2:编译验证** 运行:`cd cfc-backend && mvn clean compile` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java git commit -m "feat(family): 恢复退出切换接口并接入密码校验" ``` --- ## 阶段 C:前端成员切换视图 ### 任务 C1:store 移除 child 语义 **文件:** 修改 `cfc-frontend/store/index.js` - [ ] **步骤 1:移除 isSwitchedChild/currentChildId child 切换状态** 将 `switchToMember` mutation 中 child 分支移除(约 90-102 行),统一为 member 语义: ```js // 通过family_members切换到某个成员 switchToMember(state, payload) { state.currentRole = payload.effectiveRole state.currentMemberId = payload.memberId state.currentMemberRole = payload.effectiveRole state.isSwitchedMember = true state.currentView = 'member' uni.setStorageSync('currentRole', payload.effectiveRole) uni.setStorageSync('currentMemberId', payload.memberId) uni.setStorageSync('currentMemberRole', payload.effectiveRole) uni.setStorageSync('isSwitchedMember', true) uni.setStorageSync('currentView', 'member') } ``` > `isSwitchedChild` / `currentChildId` / `setCurrentChild` / `switchToChild` 保留为兼容别名(仍有页面引用),但内部委托 member 语义。`switchToChildWithCheck` action 中的"家庭中暂无孩子"检查改为"家庭中无可切换成员"。 - [ ] **步骤 2:退出切换增加密码确认(前端拦截)** 在 `switchBackFromMember` action 层(非 mutation)增加密码校验调用: ```js // store actions 中新增 async switchBackWithPassword({ commit, state }, password) { if (!password) { uni.showToast({ title: '请输入原用户密码', icon: 'none' }) return false } try { const res = await switchBackVerify(password) if (res.code === 0) { commit('switchBackFromMember') return true } uni.showToast({ title: res.message || '密码错误', icon: 'none' }) return false } catch (e) { uni.showToast({ title: '校验失败', icon: 'none' }) return false } } ``` > 需在 store 引入 `switchBackVerify`(来自 utils/api.js)。若 store 不直接调 API,则在页面组件中调用校验后再 commit。 - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/store/index.js git commit -m "refactor(store): 切换视图统一 member 语义,退出切换接入密码校验" ``` ### 任务 C2:FamilyMemberStrip canSwitch 反转 **文件:** 修改 `cfc-frontend/components/FamilyMemberStrip.vue` - [ ] **步骤 1:反转 canSwitch 逻辑** 现有(约 172 行): ```js return this.actionMember && this.actionMember.userId && this.actionMember.source === 'family_member' ``` 改为(仅不可登录成员可切换): ```js // 仅不可登录成员(无 openid 绑定、非本人)可切换视角 return this.actionMember && !this.actionMember.isSelf && this.actionMember.source === 'family_member' && this.actionMember.isSwitchable ``` - [ ] **步骤 2:Commit** ```bash git add cfc-frontend/components/FamilyMemberStrip.vue git commit -m "refactor(component): FamilyMemberStrip 切换入口仅对不可登录成员开放" ``` ### 任务 C3:切换入口页接入密码设置引导 **文件:** 修改切换入口所在页面(切换按钮点击处) - [ ] **步骤 1:切换前引导设置保护密码** 在用户点击"切换到该成员视角"时,若当前用户未设置过密码,弹窗提示设置: ```js // 切换前检查:进入前提示"切换后需原用户密码才能退出" switchMember: function(member) { var self = this uni.showModal({ title: '切换确认', content: '切换到"' + member.nickname + '"后,退出时需要输入您的密码。是否继续?', success: function(res) { if (res.confirm) { self.$store.dispatch('switchToMemberWithCheck', { memberId: member.id, effectiveRole: member.effectiveRole, member: member }) } } }) } ``` - [ ] **步骤 2:Commit** ```bash git add cfc-frontend/pages/...(实际页面路径) git commit -m "feat(frontend): 切换成员前确认提示,明确退出需密码" ``` --- ## 阶段 D:业务字段注释迁移 ### 任务 D1:executorType/creatorType/memberType 注释更新 **文件:** 所有含 `executorType="child"`、`creatorType="child"`、`memberType="child"` 的文件 - [ ] **步骤 1:批量注释更新(不改字段值)** ```bash # 后端:搜索所有 "child" 业务字段赋值处,补充注释 grep -rn '"child"' cfc-backend/src/main/java/com/etotem/cfc/service/ | grep -E 'executorType|creatorType|memberType' ``` 对每处赋值,在代码上方或行内补充注释: ```java task.setExecutorType("child"); // 非登录用户(不可登录成员)执行的任务 ``` - [ ] **步骤 2:schema.sql 注释同步** `tasks` 表 `executor_type` 字段注释由 `执行者类型: child/parent/member` 改为 `执行者类型: child=非登录用户/parent=可登录家长/member=其他成员`。 - [ ] **步骤 3:Commit** ```bash git add -u cfc-backend/src/main/java/ cfc-backend/src/main/resources/schema.sql git commit -m "docs: child 业务字段注释统一为『非登录用户』语义" ``` ### 任务 D2:移除 role='child' 角色判断 **文件:** 后端含 `"child".equals(role)` / `role="child"` 的 31 个文件 - [ ] **步骤 1:逐一评估 role='child' 判断** ```bash grep -rn '"child"' cfc-backend/src/main/java/com/etotem/cfc/controller/ cfc-backend/src/main/java/com/etotem/cfc/service/ | grep -iE 'role|getRole' ``` 主要判断点: - `UserService.isValidRole`(696 行):保留 `"child"` 在合法角色列表(历史数据兼容),但注释说明"child 角色已废弃,仅历史账号" - `WishController`(38/204 行)、`FamilyChallengeController`(41 行)、`EmotionCheckinController`(60/71 行)、`ProductOrderService`(737 行)、`FamilyHealthScoreService`(48 行):这些是 `@RequestAttribute("role")` 判断,切换后 effectiveRole 可能为 child——**保留判断**(切换成员后仍需按视角控制),但注释更新 - `DataMigrationService`(292-293 行):迁移脚本,保留 - `InviteCardService`(147/154/166 行):邀请卡创建 child 账号,改为创建无 openid 用户 - [ ] **步骤 2:InviteCardService 改造(创建 child 账号 → 创建不可登录成员)** `InviteCardService.java` 中 `user.setRole("child")` 改为创建无 openid 用户 + family_members 记录。 - [ ] **步骤 3:编译验证 + Commit** ```bash cd cfc-backend && mvn clean compile git add -u cfc-backend/src/main/java/ git commit -m "refactor: role=child 角色判断注释更新,InviteCard 改为创建不可登录成员" ``` --- ## 阶段 E:children 表下线 + 存量 child 账号强制迁移 ### 任务 E0:存量 role='child' 账号强制迁移 **文件:** 修改 `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` - [ ] **步骤 1:新增幂等迁移(迁移编号递增)** 在 `runMigrations()` 末尾新增: ```java // 迁移N: 存量 role='child' 账号强制迁移为成员模型 // 1) 有 openid 的 child → role 改 parent(可登录成员,登录后进成员端,不能被切换) // 2) 无 openid 的 child → 保持不可登录,确保有 family_members 记录(仅可被切换) try { // 有 openid 的 child 账号:role 归一为 parent jdbcTemplate.execute("UPDATE users SET role='parent' WHERE role='child' AND openid IS NOT NULL AND openid <> ''"); log.info("已迁移有 openid 的 child 账号 role → parent"); } catch (Exception e) { log.warn("迁移有 openid 的 child 账号失败", e); } try { // 无 openid 的 child 账号:确保 family_members 有对应记录(不可登录成员) jdbcTemplate.execute( "INSERT INTO family_members (family_id, user_id, nickname, gender, birthday, generation, show_to_family, total_points, created_at, updated_at) " + "SELECT u.family_id, u.id, u.nickname, u.gender, u.birthday, -1, 1, 0, NOW(), NOW() " + "FROM users u " + "WHERE u.role='child' AND (u.openid IS NULL OR u.openid='') " + "AND u.family_id IS NOT NULL " + "AND NOT EXISTS (SELECT 1 FROM family_members fm WHERE fm.user_id = u.id)"); log.info("已为无 openid 的 child 账号补齐 family_members 记录"); } catch (Exception e) { log.warn("补齐无 openid child 账号的 family_members 记录失败", e); } ``` - [ ] **步骤 2:同步 schema.sql 注释** `users.role` 字段注释由 `parent/child/teacher` 改为 `parent/teacher`(child 已废弃,仅历史数据)。 - [ ] **步骤 3:编译验证 + Commit** ```bash cd cfc-backend && mvn clean compile git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java cfc-backend/src/main/resources/schema.sql git commit -m "feat(migration): 存量 role=child 账号强制迁移为成员模型" ``` ### 任务 E1:确认 children 表数据已迁移 - [ ] **步骤 1:检查 schema.sql 中 children 迁移脚本** `schema.sql` 中已有"迁移 children → family_members"的脚本(2498-2533 行),确认生产库已执行。若未执行,通过 `DatabaseInitializer` 补充幂等迁移。 ### 任务 E2:ChildMapper/Child 实体弃用 - [ ] **步骤 1:检查 ChildMapper / Child 引用** ```bash grep -rn 'ChildMapper\|childMapper' cfc-backend/src/main/java/ | grep -v 'ChildBadge' ``` 将仍引用 `ChildMapper` 的业务代码迁移到 `FamilyMemberMapper`(`children` 表字段已合并到 `family_members`)。 - [ ] **步骤 2:Child 实体标注 @Deprecated** `Child.java` 类注释标注废弃,保留以兼容引用。编译验证通过后提交。 ```bash cd cfc-backend && mvn clean compile git commit -m "refactor: children 表/Child 实体标记废弃,引用迁移至 family_members" ``` --- ## 阶段 F:前端 child 页面/语义迁移 ### 任务 F1:child-index 页面迁移 - [ ] **步骤 1:检查 pages/home-pages/child-index.vue 与 pages/index/index 路由** `pages/index/index` 根据 `role` 分流到 `parent-index` / `child-index`。登录后 `role='child'` 的账号不再存在(新模型无 child 登录账号),`child-index` 改为"切换后的成员视图" `member-home`(或由 currentView='member' 判断跳转)。 - [ ] **步骤 2:Commit** ### 任务 F2:各页面 child 语义判断替换 - [ ] **步骤 1:批量替换前端 `effectiveRole === 'child'` 判断** ```bash grep -rln "effectiveRole === 'child'\|effectiveRole == 'child'\|'child' === .*effectiveRole" cfc-frontend/ ``` 逐页确认语义: - 用于"过滤可切换成员" → 改为 `m.isSwitchable` - 用于"孩子视角 UI" → 改为 `currentView === 'member'` 或保留(切换成员后仍显示成员视角) - [ ] **步骤 2:Commit** --- ## 自检清单 **规格覆盖度:** - [x] 成员类型判定(openid 是否为空)→ 任务 A3/A4/A5 - [x] 创建的成员也创建 user 记录 → 任务 A4 - [x] 手机号相同新注册复用 user → 任务 A5 - [x] 仅不可登录成员可切换 → 任务 A3 + C2 - [x] 退出切换需原用户密码 → 任务 B1/B2 + C1 - [x] 切换前可设置密码 → 任务 B1 + C3 - [x] 业务字段 child 保留但注释改"非登录用户" → 任务 D1 - [x] 清理 role=child 角色判断 → 任务 D2 - [x] children 表下线 → 阶段 E - [x] 前端 child 视图迁移 → 阶段 F **占位符扫描:** 无 TODO/待定,所有步骤含具体代码或命令。 **类型一致性:** `memberType` 字段名统一(VO/实体均为 memberType),`isSwitchable` 统一命名。 --- ## 风险与注意事项 1. **存量 role='child' 账号**:已按用户决策强制迁移——有 openid 的改 role=parent(可登录成员,登录后进成员端,不能被切换);无 openid 的补齐 family_members 记录(不可登录成员,仅可被切换)。迁移幂等,可重复执行。 2. **users.password 语义**:切换密码未单独设置时默认用登录密码(复用 `users.password`),不新增独立字段。`set-password` 与 `set-switch-password` 写同一字段。 3. **InviteCardService** 创建 child 账号逻辑需先确认其调用场景(邀请卡可能用于邀请真实用户,而非创建不可登录成员),改造前需验证。 4. **前端 store 兼容**:`isSwitchedChild` / `currentChildId` 保留为兼容别名,避免一次性破坏所有页面;后续迭代逐步移除。