# 规格文档:新用户注册引导 + 个人信息按需补充与回写 **日期:** 2026-08-04 **状态:** 设计稿(3 个默认值待确认) **前置文档:** - `2026-07-16-first-login-auto-complete.md`(首次登录自动完善,已实施) - `2026-08-01-activity-registration-upgrade-design.md`(活动报名升级,已实施) - `2026-07-31-activity-registration-form-design.md`(报名表单,已实施) --- ## 1. 概述 新用户注册引导重构,核心诉求:**注册时补充个人信息,而不是创建家庭**;注册可跳过,跳过后需有快捷入口随时补充;各功能用到个人信息时,在功能场景内就地补充并**自动回写**到个人信息。 背景痛点(源于 2026-07-16 文档):信息补全率低于 40%,注册流程强绑定家庭创建导致流失。 三个目标: 1. **注册解耦**:注册流程不再创建家庭,只收集最小集个人信息(头像 + 昵称,均可跳过) 2. **主动入口**:个人中心常驻「补充个人信息」卡片,显示已填/总字段数 3. **被动回写**:功能场景「选择用户 → 预填 → 就地补充 → 自动回写」,修改已有值需用户确认 ## 2. 用户故事 - 作为新用户,我想注册时只填头像和昵称(可跳过),以便快速进入应用,不被家庭创建流程打断 - 作为跳过了资料补充的用户,我想在个人中心找到明确的补充入口,以便随时完善个人信息 - 作为活动报名用户,我想在选择报名人后看到其已有信息自动预填,缺失的手机号在报名处直接补充,以便减少重复输入 - 作为在功能中补充/修改了个人信息的用户,我想补充的值自动回写进我的资料(修改时需确认),以便资料保持最新 ## 3. 现状分析 | 项目 | 现状 | 问题 | |------|------|------| | 注册流程 | 微信快捷登录即注册;新用户 `needsRoleSelect=true` → 跳转 `user-edit?autoParent=true`(默认 parent → 信息补充页) | 引导页仍展示「创建家庭/加入家庭」三步绑定区 | | 家庭创建 | 后端注册时自动创建「我的家庭」并设置 `familyId`(`wechatLogin`/`phoneLogin` 等 5 条注册路径 + `ensureUserHasFamily` 懒创建兜底) | **注册即建家庭**,与「注册不建家庭」诉求冲突 | | 资料更新 | `POST /api/user/update`(支持 20 个字段)、`POST /api/family/user/update` | 无字段级完善度统计、无回写语义 | | 新手任务 | `COMPLETE_PROFILE` 任务在调用 update 时自动完成(奖励 50 积分 + 5 能量) | 保留,与本次设计不冲突 | | 个人中心 | 头像点击 → `user-edit` 编辑页 | 入口不显眼,无完善度提示 | | 活动报名 | 家庭成员列表点选预填 name/phone;手动添加外部报名人(memberId=null);手机号非必填 | 「就地补充 + 回写」机制缺失 | | 无家庭降级 | 各页自行 `if (!familyId) return` 静默跳过 | 无引导,用户不知道为什么功能不可用 | ## 4. 设计决策记录 | # | 决策 | 结论 | |---|------|------| | 1 | 注册与家庭解耦程度 | **彻底解耦**:注册不建家庭,`familyId=null`;首次用到家庭功能时引导创建/加入 | | 2 | 注册引导字段范围 | **最小集**:头像 + 昵称(可跳过,跳过用默认昵称「用户+手机尾号」);其余字段按需补充 | | 3 | 补充机制形式 | **选择用户 → 预填 → 就地补充 → 回写**(非弹窗横幅式提醒) | | 4 | 回写字段白名单 | 手机号 / 生日 / 性别 / 饮食偏好 / 出生时辰 / 地址(6 个字段) | | 5 | 个人信息使用关联表 | **前端配置文件** `utils/profile-field-usage.js`(字段 → 使用场景映射,只从使用处回写) | | 6 | 修改已有值 | 弹确认「是否同步更新个人信息」,用户确认才回写 | | 7 | 快捷补充入口 | **个人中心卡片**(显示已填 X/14 字段,点击进编辑页),首页不加 | | 8 | 无家庭使用家庭类功能 | **引导创建后继续**:提示「去创建家庭」→ 弹窗输入家庭名 → 调 `create-family` → 返回原功能 | | 9 | 外部报名人(memberId=null) | 不回写(无归属主体,仅本次报名使用) | | 10 | 回写接口形式 | **统一 backfill 接口 + 后端字段白名单**(防越权写其他字段) | ## 5. 总体架构(三层机制) ``` ┌─────────────────────────────────────────────────────────┐ │ ① 注册引导(轻量,一次性) │ │ 登录/注册 → 最小集表单(头像+昵称,可跳过)→ 首页 │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ ② 主动补充入口(常驻) │ │ 个人中心卡片:个人信息 已填 X/14 → 点击进编辑页 │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ ③ 被动补充机制(功能场景,核心) │ │ 选择用户(自己/家人/外部) → 已有字段预填 │ │ → 缺失字段就地补充 → 提交时自动回写该人的个人信息 │ │ → 若为「修改已有值」→ 弹确认「是否同步修改个人信息」 │ └─────────────────────────────────────────────────────────┘ ``` 三层互不依赖:注册时跳过的,②随时补;功能用到的,③顺手补。 ## 6. 后端改动(cfc-backend) ### 6.1 注册不再创建家庭(核心) **文件:** `service/UserService.java` - 5 条注册路径(`wechatLogin`/`phoneLogin`/`directRegister`/`registerWithIdCard`/`registerWithInviteCode`):删除新用户自动创建 `Family` 的逻辑,`user.setFamilyId(null)` - `ensureUserHasFamily`(定义 L922,调用点 L143/L227/L289/L325):删除懒创建逻辑,改为**直接 return**(不再兜底建家庭)。注意 L227 注释「P3: admin 创建的用户可能 familyId=0L」,评估该场景:admin 建用户也不应自动建家庭,保持 `familyId` 为空,由前端引导 - `wechatLogin` L143 前即 `familyId` 相关逻辑一并调整 ### 6.2 数据库迁移(迁移 155) **文件:** `config/DatabaseInitializer.java` + `resources/schema.sql` ```sql -- 迁移155: users.family_id 允许 NULL(注册不再自动创建家庭) ALTER TABLE users MODIFY COLUMN family_id BIGINT NULL COMMENT '所属家庭ID(注册时可为空,首次使用家庭功能时创建/加入)'; ``` 同步 `schema.sql` 中 `users` 表定义:`family_id BIGINT NOT NULL` → `BIGINT NULL`。 迁移编号当前最高 154,新增编号为 155。 ### 6.3 登录响应新增标记 **文件:** `dto/LoginResultDTO.java` ```java private Boolean needsFamily; // familyId == null 时为 true,前端据此决定是否引导建家庭 ``` 在登录/注册成功装配响应处(`UserService` 各 login 方法末尾)设置 `dto.setNeedsFamily(user.getFamilyId() == null)`。 ### 6.4 新增统一回写接口 ``` POST /api/user/profile/backfill 请求: { "targetType": "user" | "member", // 自己 or 家庭成员 "targetId": 123, // targetType=member 时的 family_members.id;user 时可为空 "fields": { "phone": "13800138000", "birthday": "2015-06-01", "gender": "male", "birthHour": "巳时", "dietPreferences": "少油少盐", "address": "北京市朝阳区..." } } 响应: { "code": 200, "message": "success", "data": { "updatedFields": ["phone", "birthday"], "profile": { /* 该人最新完整个人信息,前端刷新缓存用 */ } } } ``` **后端逻辑:** 1. 字段白名单校验:仅允许 `phone / gender / birthday / birthHour / dietPreferences / address` 6 个字段,其余字段拒绝(防越权写 teacherNo 等敏感字段) 2. `targetType=user`:校验 `targetId == userId`(仅能回写自己),写 `users` 表 3. `targetType=member`:校验该成员属于当前用户家庭(`family_members.family_id == user.familyId`),写 `family_members` 表 4. 字段值格式校验(phone 正则、birthday 日期格式、gender 枚举、birthHour 十二时辰枚举) 5. 回写成功后返回该人最新信息(`User` 或 `FamilyMember` 的完整字段) **新增文件:** `controller/UserController.java` 内新增方法(或独立 `ProfileBackfillController`)、`dto/ProfileBackfillDTO.java`、`service/UserService.backfillProfile()`。 ### 6.5 家庭类接口行为 保持现状(无 familyId 时报错/返回空),由前端负责引导。不改后端各 Service 的 familyId 校验逻辑。 ## 7. 前端改动(cfc-frontend) ### 7.1 注册引导最小化 **文件:** `pages/user-edit/user-edit.vue` - `autoParent=true` 新用户流程:仅展示头像 + 昵称表单,昵称可跳过(跳过则默认「用户+手机尾号」) - 移除新用户流程的「创建家庭/加入家庭/跳过」家庭绑定区(家庭创建彻底移出注册) - 保存调 `POST /api/user/update`(现有逻辑,触发 `COMPLETE_PROFILE` 任务完成)→ 进首页 - 保持 `isEdit`(非新用户从个人中心进入)时的完整表单不变 **文件:** `pages/login/login.vue` - `needsRoleSelect` 跳转保持(user-edit?autoParent=true) - 读取新增的 `needsFamily`:暂不强制处理(注册完成直接进首页),由各功能页引导 ### 7.2 个人中心补充卡片 **文件:** `pages/profile/profile.vue`(或 `components/ProfileHeader.vue` 下方) - 新增卡片:`个人信息 已完善 X/14 字段 ›`,点击 → `user-edit`(非 autoParent 编辑模式) - X = 14 个可补充字段中已填数量(字段口径见第 10 节待确认默认值 #3) - 全部填满后卡片隐藏 - 字段数据来源:登录缓存 `userInfo` + `POST /api/user/info`(现有接口,返回完整 User 字段) ### 7.3 个人信息使用关联表 **新增文件:** `utils/profile-field-usage.js` ```js /** * 个人信息使用关联表 * 记录个人信息的各个字段在哪些功能场景中被使用。 * 用途: * 1. 功能页自查「本场景用到哪些字段、当前人是否缺失」 * 2. 回写时校验字段确实属于本场景(只从使用处回写) * 3. 个人中心卡片统计已填字段数 */ module.exports = { phone: { label: '手机号', scenes: [ { scene: 'activity-signup', page: 'pages/activity/activity-detail', usage: '活动报名联系人手机号', backfillTarget: ['user', 'member'] }, { scene: 'shop-order', page: 'pages/shop/checkout', usage: '订单收货人手机号', backfillTarget: ['user'] }, { scene: 'assessment-order', page: 'pages/assessment/purchase', usage: '测评预约联系人手机号', backfillTarget: ['user'] } ] }, birthday: { label: '生日', scenes: [ { scene: 'assessment', page: 'pages/assessment/*', usage: '测评年龄计算', backfillTarget: ['user', 'member'] }, { scene: 'tianpan', page: 'pages/tianpan/*', usage: '天盘命理计算', backfillTarget: ['user', 'member'] }, { scene: 'health-report', page: 'pages/health/*', usage: '健康报告年龄相关', backfillTarget: ['member'] } ] }, gender: { label: '性别', scenes: [ { scene: 'assessment', page: 'pages/assessment/*', usage: '测评维度计算', backfillTarget: ['user', 'member'] }, { scene: 'tianpan', page: 'pages/tianpan/*', usage: '天盘计算', backfillTarget: ['user', 'member'] } ] }, birthHour: { label: '出生时辰', scenes: [ { scene: 'tianpan', page: 'pages/tianpan/*', usage: '天盘命理计算', backfillTarget: ['user', 'member'] } ] }, dietPreferences: { label: '饮食偏好', scenes: [ { scene: 'nutrition-recommend', page: 'pages/health/nutrition-profile', usage: '饮食推荐个性化', backfillTarget: ['user', 'member'] } ] }, address: { label: '地址', scenes: [ { scene: 'shop-order', page: 'pages/shop/checkout', usage: '收货地址', backfillTarget: ['user'] }, { scene: 'activity-signup', page: 'pages/activity/activity-detail', usage: '活动地点信息', backfillTarget: ['user'] } ] } } ``` > **约定:** 功能页接入时在 `scenes` 中登记本场景;回写前先查本场景是否登记了该字段,未登记则不允许回写(只从使用处回写)。 ### 7.4 回写工具 **新增文件:** `utils/profile-backfill.js` ```js /** * 就地补充 → 自动回写个人信息 * @param {Object} opts * targetType: 'user' | 'member' * targetId: memberId(targetType=member 时) * fields: { phone: '138...', birthday: '...' } * scene: 'activity-signup'(必传,校验字段是否属于本场景) * @returns {Promise} 回写结果 */ function backfillProfile(opts) { ... } ``` 逻辑: 1. **场景校验**:对 `opts.fields` 的每个字段,查 `profile-field-usage.js` 中该字段的 `scenes` 是否包含 `opts.scene`,不包含则跳过该字段 2. **修改确认**:对每个字段,比对当前人已有值(从缓存/接口获取): - 原值为空(首次补充)→ 不打扰,直接回写 - 原值非空且与表单值不同(修改)→ `uni.showModal('检测到您修改了{字段名},是否同步更新个人信息?')`,确认才回写 3. 调 `POST /api/user/profile/backfill`,成功后更新本地缓存(`uni.setStorageSync('userInfo')` / Vuex) 4. 返回成功/跳过字段列表 ### 7.5 功能页接入(试点:活动报名) **文件:** `pages/activity/activity-detail/activity-detail.vue` 现有结构(保留):家庭成员列表点选预填 name/phone + 手动添加报名人(memberId=null)+ 手机号非必填。 新增: - 家庭成员点选后,若该成员(memberId 非空)的 phone 为空 → 输入框旁小字提示「将同步到家人资料」 - 提交报名(`submitRegistration`)时:对每个 `memberId 非空` 的报名人,收集其表单中新增/修改的字段,调 `backfillProfile({ targetType: 'member', targetId, fields, scene: 'activity-signup' })` - 手动添加的外部报名人(memberId=null):**不回写**,仅本次报名使用 - 无家庭用户选「家人」→ 无家庭成员列表 → 走 7.6 引导建家庭 ### 7.6 无家庭引导(家庭类功能入口) **改动范围:** 任务 / 五维能量 / 测评 / 天盘 / 成长档案 / 保险规划等现有 `if (!familyId) return` / toast「请先加入家庭」的页面。 统一引导组件(新增)或逐页改造: - 检测 `!familyId` 且用户已登录 → 显示轻提示「创建家庭后可开始使用」+ 「去创建家庭」按钮 - 点击 → 弹窗输入家庭名(默认「我的家庭」)→ 调 `POST /api/family/user/create-family` → 成功 → 刷新 `familyId`(localStorage + Vuex)→ 返回原功能继续操作 - 同时提供「加入家庭」(输入邀请码,调现有 `join-family`)入口 - 逐页改造时保持 `mvn clean compile` 与页面可用性 ## 8. 试点范围与顺序 | 阶段 | 内容 | 说明 | |------|------|------| | **P0** | 后端彻底解耦(注册不建家庭 + 迁移 155 + needsFamily + backfill 接口)+ 注册引导最小化 + 个人中心卡片 + profile-field-usage.js/backfill.js | 地基,单独可上线 | | **P1** | 活动报名接入「就地补充 + 回写」 | 验证回写模式,含修改确认 | | **P2** | 无家庭引导(家庭类功能入口逐页接入) | 依赖 P0 | | **P3** | 其他功能页接入(测评 / 天盘 / 商城 / 饮食推荐按关联表铺开) | 每页独立小改动 | ## 9. 验收标准 - [ ] 新用户注册:不再自动创建家庭,`familyId` 为 null;注册引导页仅头像 + 昵称(可跳过) - [ ] `users.family_id` 允许 NULL(迁移 155 幂等可重复执行);schema.sql 已同步 - [ ] `POST /api/user/profile/backfill` 通过字段白名单校验(6 字段);`targetType=member` 校验家庭归属;非法字段被拒绝 - [ ] 登录响应返回 `needsFamily`(familyId 为空时 true) - [ ] 个人中心显示「个人信息 已完善 X/14 字段」卡片,点击进入编辑页,填满后隐藏 - [ ] 活动报名:点选家庭成员预填手机号;缺失手机号就地补充并回写;修改已有手机号弹确认;外部报名人不回写 - [ ] 无家庭用户进入家庭类功能:提示 + 「去创建家庭」→ 创建成功 → 返回原功能继续 - [ ] `mvn clean compile` 通过;小程序构建通过 ## 10. 待确认的默认值(实现前需用户确认) | # | 默认值 | 说明 | |---|--------|------| | 1 | 统一 backfill 接口 + 后端字段白名单 | 备选:前端直接复用现有 updateUser/updateFamilyMember 接口 | | 2 | 外部报名人(memberId=null)不回写 | 仅本次报名使用 | | 3 | 个人中心卡片口径 = 14 个字段 | 昵称/头像/真实姓名/性别/生日/出生时辰/民族/血型/最高学历/婚姻状态/地区/兴趣爱好/饮食偏好/手机号 |