|
@@ -0,0 +1,774 @@
|
|
|
|
|
+# 注册零填写 + 信息按需补充 实施计划
|
|
|
|
|
+
|
|
|
|
|
+> **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 (`- [ ]`) syntax for tracking.
|
|
|
|
|
+
|
|
|
|
|
+**Goal:** 取消小程序注册时的信息填写步骤(注册成功直接进首页),新增接受家庭邀请独立页(真实姓名+家庭身份必填),更新功能→信息字段映射表,添加成员手机号冲突时提示邀请加入。
|
|
|
|
|
+
|
|
|
|
|
+**Architecture:** 纯前端为主的最小改动:登录页去掉新用户跳转;`user-edit.vue` 停用新用户流程只留编辑用途;新增 `pages/invite/join-form.vue` 承接家庭邀请;`join.vue` 改跳新页;映射表 `profile-field-usage.js` 补全;后端 backfill 白名单加 `realName`;`add-member.vue` 手机号冲突改邀请提示。
|
|
|
|
|
+
|
|
|
|
|
+**Tech Stack:** uni-app Vue 2 (小程序) + Spring Boot 2.7.18 (仅 1 处后端白名单改动)
|
|
|
|
|
+
|
|
|
|
|
+**Spec:** `docs/superpowers/specs/2026-09-28-registration-zero-fill-design.md`
|
|
|
|
|
+
|
|
|
|
|
+## Global Constraints
|
|
|
|
|
+
|
|
|
|
|
+- 后端验证唯一方式:`cd cfc-backend && mvn clean compile`(测试目录有 ~166 个预先存在失败,**不要**尝试修复)
|
|
|
|
|
+- 前端**禁止打包**(`npm run build:mp-weixin` 等一律不执行),只做语法/结构校验(`node --check` 提取 script 块)
|
|
|
|
|
+- 接口一律 `@PostMapping`;DI 用 `@Resource`;响应统一 `Result<T>`
|
|
|
|
|
+- 小程序限制:禁止可选链 `?.`(用 `&&`)、禁止 CSS Grid(用 flexbox)、禁止 `:key` 表达式、禁止 `new Date(string)`(用 `parseDate()`)
|
|
|
|
|
+- Vue 2 Options API,禁止 Composition API
|
|
|
|
|
+- 迁移幂等;`docs/superpowers/` 文件增改必须同步 `PROJECT-OVERVIEW.md`
|
|
|
|
|
+- 提交风格:`feat: 注册零填写-xxx`
|
|
|
|
|
+- 不破坏:服务用户注册流程(`pages/guide/register`)、个人中心编辑、现有活动报名回写
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 文件结构
|
|
|
|
|
+
|
|
|
|
|
+| 文件 | 操作 | 责任 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `cfc-frontend/pages/login/login.vue` | 修改 | 注册零填写:登录成功直接进首页 / 有家庭邀请则跳 join-form |
|
|
|
|
|
+| `cfc-frontend/pages/user-edit/user-edit.vue` | 修改 | 停用新用户流程,仅保留个人中心编辑 |
|
|
|
|
|
+| `cfc-frontend/pages/invite/join-form.vue` | **新建** | 接受家庭邀请:真实姓名+家庭身份必填 |
|
|
|
|
|
+| `cfc-frontend/pages.json` | 修改 | 注册 join-form 页面 |
|
|
|
|
|
+| `cfc-frontend/pages/invite/join.vue` | 修改 | 已登录跳 join-form 而非 user-edit |
|
|
|
|
|
+| `cfc-frontend/utils/profile-field-usage.js` | 修改 | 功能→信息字段映射表补全 |
|
|
|
|
|
+| `cfc-backend/.../service/UserService.java` | 修改 | backfill 白名单加 realName |
|
|
|
|
|
+| `cfc-frontend/pages/family/add-member.vue` | 修改 | 手机号冲突 → 邀请加入提示 |
|
|
|
|
|
+| `docs/superpowers/PROJECT-OVERVIEW.md` | 修改 | 状态同步 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 1: login.vue 注册零填写改造
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/pages/login/login.vue:174-181`
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: `uni.getStorageSync('pendingFamilyInviteCode')`(join.vue 写入)
|
|
|
|
|
+- Produces: 登录成功后的跳转决策(直接进首页 或 跳 join-form)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 `completeLogin()` 跳转逻辑**
|
|
|
|
|
+
|
|
|
|
|
+把 L174-181 的以下代码:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ uni.showToast({ title: '登录成功', icon: 'success' })
|
|
|
|
|
+ self.loading = false
|
|
|
|
|
+ // 新用户,或存在待加入的家庭邀请(join.vue 已写入 pendingFamilyInviteCode):
|
|
|
|
|
+ // 都进入个人信息完善页,填写家庭身份并提交加入申请
|
|
|
|
|
+ var hasPendingFamilyInvite = !!uni.getStorageSync('pendingFamilyInviteCode')
|
|
|
|
|
+ if (data.needsRoleSelect || hasPendingFamilyInvite) {
|
|
|
|
|
+ uni.redirectTo({ url: '/pages/user-edit/user-edit?token=' + data.token + '&userId=' + data.userId + '&autoParent=true' })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.handlePostLoginRouting()
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+替换为:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ uni.showToast({ title: '登录成功', icon: 'success' })
|
|
|
|
|
+ self.loading = false
|
|
|
|
|
+ // 注册零填写:新用户(needsRoleSelect)不再跳信息填写页,直接进首页
|
|
|
|
|
+ // 家庭邀请衔接:有 pendingFamilyInviteCode(join.vue 写入)→ 直接进 join-form 填真实姓名+家庭身份
|
|
|
|
|
+ var pendingInvite = uni.getStorageSync('pendingFamilyInviteCode')
|
|
|
|
|
+ if (pendingInvite) {
|
|
|
|
|
+ uni.redirectTo({ url: '/pages/invite/join-form?familyInviteCode=' + encodeURIComponent(pendingInvite) })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.handlePostLoginRouting()
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 校验改动**
|
|
|
|
|
+
|
|
|
|
|
+`node --check` 无法直接检查 .vue,改为人工核对:确认 L174-181 区域替换正确、`hasPendingFamilyInvite` 变量不再被引用、无遗留语法错误(`uni.showToast`/`self.loading` 保持原位置)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages/login/login.vue
|
|
|
|
|
+git commit -m "feat: 注册零填写-登录成功直接进首页,家庭邀请改跳join-form"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 2: user-edit.vue 停用新用户流程
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/pages/user-edit/user-edit.vue:418-463`(onLoad)、`:754-768`(saveUserInfo 家庭邀请块)、`:48-57`(家庭身份模板区)、`:400-403`(hasPendingFamilyInvite computed)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 无(不再接收 autoParent/needsRoleSelect 参数)
|
|
|
|
|
+- Produces: 纯编辑模式页面(个人中心入口)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 替换 `onLoad` 为新用户流程废弃版**
|
|
|
|
|
+
|
|
|
|
|
+把 L418-463 整个 `onLoad(options) {...}` 替换为:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ onLoad(options) {
|
|
|
|
|
+ // 注册零填写改造:本页仅保留「个人中心编辑资料」用途
|
|
|
|
|
+ // 新用户注册不再跳转本页(autoParent / needsRoleSelect 参数已废弃,不再处理)
|
|
|
|
|
+ this.showRoleSelect = false
|
|
|
|
|
+ this.isEdit = true
|
|
|
|
|
+ this.isNewUserFlow = false
|
|
|
|
|
+ // 加载已有用户信息
|
|
|
|
|
+ this.loadUserInfo()
|
|
|
|
|
+ // 加载会员等级(用于助手解锁)
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ getMyMembership().then(function(res) {
|
|
|
|
|
+ if (res.data && res.data.memberLevel) {
|
|
|
|
|
+ self.memberLevel = res.data.memberLevel
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {})
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 注意:`options` 参数不再使用,保留形参避免调用方报错。原 L419-449 的 autoParent/needsRoleSelect/phone 测试分支全部删除。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 删除模板中家庭身份区块(L48-57)**
|
|
|
|
|
+
|
|
|
|
|
+删除以下整段:
|
|
|
|
|
+
|
|
|
|
|
+```html
|
|
|
|
|
+ <!-- 家庭身份(接受家庭邀请时置顶显示,方便第一时间选择家庭身份) -->
|
|
|
|
|
+ <view class="family-section" v-if="isEdit && form.role === 'parent' && isNewUserFlow && hasPendingFamilyInvite">
|
|
|
|
|
+ <view class="section-title">👨👩👧👦 家庭身份(可选)</view>
|
|
|
|
|
+ <view class="form-item">
|
|
|
|
|
+ <text class="label">您在家庭中的身份</text>
|
|
|
|
|
+ <picker mode="selector" :range="familyRoleOptions" :value="familyRoleIndex" @change="onFamilyRoleChange">
|
|
|
|
|
+ <view class="picker-input">{{ form.familyRole || '请选择(可跳过)' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="hint-text">不选也可先保存,后续在家庭页面发起申请</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 删除 `hasPendingFamilyInvite` computed**
|
|
|
|
|
+
|
|
|
|
|
+删除 L400-403:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ // 是否有待处理的家庭邀请码
|
|
|
|
|
+ hasPendingFamilyInvite: function() {
|
|
|
|
|
+ return !!uni.getStorageSync('pendingFamilyInviteCode')
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 简化 `saveUserInfo()` 家庭邀请分支**
|
|
|
|
|
+
|
|
|
|
|
+L753-778 当前结构(含 `if (this.isNewUserFlow && this.hasPendingFamilyInvite) {...} else {...}`)。由于 `isNewUserFlow` 恒为 false,将整个 if/else 替换为 else 分支内容:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (this.isNewUserFlow) {
|
|
|
|
|
+ this.navigateToHome()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.navigateBack()
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 1500)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 保留 `if (this.isNewUserFlow)` 判断(恒 false 走 else 返回上一页),不硬删判断以最小化 diff。删除原 `var inviteCode = uni.getStorageSync('pendingFamilyInviteCode')`、`var familyRole = ...`、`requestJoinByCode` try/catch 块、`uni.removeStorageSync('pendingFamilyInviteCode')`。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 校验**
|
|
|
|
|
+
|
|
|
|
|
+人工核对:模板中不再引用 `hasPendingFamilyInvite`;`saveUserInfo` 无 `requestJoinByCode` / `pendingFamilyInviteCode` 引用;`isNewUserFlow` 仍存在 data 定义(恒 false,其他 v-if 引用无害)。检查 `onFamilyRoleChange` / `familyRoleOptions` / `familyRoleIndex` 若模板无引用则保留方法体不删(无害死代码)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 6: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages/user-edit/user-edit.vue
|
|
|
|
|
+git commit -m "feat: 注册零填写-user-edit停用新用户流程仅保留编辑用途"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 3: 新增 pages/invite/join-form.vue(接受家庭邀请页)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `cfc-frontend/pages/invite/join-form.vue`
|
|
|
|
|
+- Modify: `cfc-frontend/pages.json`(pages/invite 分包注册)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: `uni.getStorageSync('token')`、路由参数 `familyInviteCode`、`checkUserFamily`(可不用)、`updateUserInfo`、`requestJoinByCode`(均来自 `@/utils/api.js`)
|
|
|
|
|
+- Produces: 接受家庭邀请提交(真实姓名+家庭身份必填);`uni.removeStorageSync('pendingFamilyInviteCode')` 消费完成
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 在 pages.json 注册页面**
|
|
|
|
|
+
|
|
|
|
|
+`pages.json` L1448-1458 的 `pages/invite` 分包 `pages` 数组中,`join` 条目后追加:
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+ {
|
|
|
|
|
+ "path": "join-form",
|
|
|
|
|
+ "style": {
|
|
|
|
|
+ "navigationBarTitleText": "加入家庭"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 新建页面文件**
|
|
|
|
|
+
|
|
|
|
|
+创建 `cfc-frontend/pages/invite/join-form.vue`,完整内容:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="join-form-container">
|
|
|
|
|
+ <view class="header">
|
|
|
|
|
+ <text class="title">加入家庭</text>
|
|
|
|
|
+ <text class="subtitle">请填写您的真实姓名和家庭身份,提交后等待管理员审批</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="form-section">
|
|
|
|
|
+ <!-- 真实姓名(必填) -->
|
|
|
|
|
+ <view class="form-item">
|
|
|
|
|
+ <text class="label">真实姓名 <text class="required">*</text></text>
|
|
|
|
|
+ <input type="text" v-model="form.realName" placeholder="请输入真实姓名" class="input" maxlength="20" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 家庭身份(必填) -->
|
|
|
|
|
+ <view class="form-item">
|
|
|
|
|
+ <text class="label">家庭身份 <text class="required">*</text></text>
|
|
|
|
|
+ <picker mode="selector" :range="familyRoleOptions" :value="familyRoleIndex" @change="onFamilyRoleChange">
|
|
|
|
|
+ <view class="picker-input">{{ form.familyRole || '请选择(如:爸爸、妈妈)' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 昵称(选填,默认用户+手机尾号) -->
|
|
|
|
|
+ <view class="form-item">
|
|
|
|
|
+ <text class="label">昵称</text>
|
|
|
|
|
+ <input type="text" v-model="form.nickname" placeholder="选填,默认:用户+手机尾号" class="input" maxlength="20" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 头像(选填) -->
|
|
|
|
|
+ <view class="form-item">
|
|
|
|
|
+ <text class="label">头像</text>
|
|
|
|
|
+ <view class="avatar-upload" @click="chooseAvatar">
|
|
|
|
|
+ <image v-if="form.avatar" :src="form.avatar" class="avatar-preview"></image>
|
|
|
|
|
+ <view v-else class="avatar-placeholder">
|
|
|
|
|
+ <text class="plus-icon">+</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="actions">
|
|
|
|
|
+ <button class="btn-primary" @click="submit" :loading="submitting" :disabled="submitting">
|
|
|
|
|
+ {{ submitting ? '提交中...' : '提交加入申请' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { updateUserInfo, requestJoinByCode } from '@/utils/api.js'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ familyInviteCode: '',
|
|
|
|
|
+ familyRoleOptions: ['爸爸', '妈妈', '爷爷', '奶奶', '姥爷', '姥姥', '其他'],
|
|
|
|
|
+ form: {
|
|
|
|
|
+ realName: '',
|
|
|
|
|
+ familyRole: '',
|
|
|
|
|
+ nickname: '',
|
|
|
|
|
+ avatar: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ submitting: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ familyRoleIndex: {
|
|
|
|
|
+ get() {
|
|
|
|
|
+ return this.familyRoleOptions.indexOf(this.form.familyRole)
|
|
|
|
|
+ },
|
|
|
|
|
+ set(val) {
|
|
|
|
|
+ this.form.familyRole = this.familyRoleOptions[val] || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad(options) {
|
|
|
|
|
+ // 接收家庭邀请码:join.vue 传 familyInviteCode;登录页衔接传 pendingFamilyInviteCode 对应值
|
|
|
|
|
+ var code = (options && options.familyInviteCode) || uni.getStorageSync('pendingFamilyInviteCode') || ''
|
|
|
|
|
+ this.familyInviteCode = code
|
|
|
|
|
+ if (code) {
|
|
|
|
|
+ uni.setStorageSync('pendingFamilyInviteCode', code)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 已有资料预填(如有昵称/头像/真实姓名则带出)
|
|
|
|
|
+ this.prefill()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ prefill() {
|
|
|
|
|
+ var userInfo = uni.getStorageSync('userInfo') || {}
|
|
|
|
|
+ if (userInfo.realName) this.form.realName = userInfo.realName
|
|
|
|
|
+ if (userInfo.nickname) this.form.nickname = userInfo.nickname
|
|
|
|
|
+ if (userInfo.avatar) this.form.avatar = userInfo.avatar
|
|
|
|
|
+ },
|
|
|
|
|
+ chooseAvatar() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ uni.chooseImage({
|
|
|
|
|
+ count: 1,
|
|
|
|
|
+ success: function(res) {
|
|
|
|
|
+ self.form.avatar = res.tempFilePaths[0]
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onFamilyRoleChange(e) {
|
|
|
|
|
+ this.form.familyRole = this.familyRoleOptions[e.detail.value] || ''
|
|
|
|
|
+ },
|
|
|
|
|
+ async submit() {
|
|
|
|
|
+ if (this.submitting) return
|
|
|
|
|
+ if (!this.form.realName || !this.form.realName.trim()) {
|
|
|
|
|
+ uni.showToast({ title: '请输入真实姓名', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.form.familyRole) {
|
|
|
|
|
+ uni.showToast({ title: '请选择家庭身份', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.familyInviteCode) {
|
|
|
|
|
+ uni.showToast({ title: '缺少家庭邀请码', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.submitting = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 先保存基本信息(真实姓名/昵称/头像),失败不阻塞加入申请
|
|
|
|
|
+ try {
|
|
|
|
|
+ await updateUserInfo({
|
|
|
|
|
+ realName: this.form.realName.trim(),
|
|
|
|
|
+ nickname: this.form.nickname ? this.form.nickname.trim() : '',
|
|
|
|
|
+ avatar: this.form.avatar
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.log('[join-form] 保存基本信息失败,继续提交加入申请', e)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 提交加入家庭申请
|
|
|
|
|
+ await requestJoinByCode(this.familyInviteCode, this.form.familyRole)
|
|
|
|
|
+ uni.removeStorageSync('pendingFamilyInviteCode')
|
|
|
|
|
+ uni.showToast({ title: '加入家庭申请已提交,等待管理员审批', icon: 'success' })
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ uni.reLaunch({ url: '/pages/index-home/index' })
|
|
|
|
|
+ }, 1500)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: (e && e.message) || '提交失败', icon: 'none' })
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.submitting = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.join-form-container {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ padding: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.header {
|
|
|
|
|
+ margin-bottom: 40rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+.title {
|
|
|
|
|
+ font-size: 40rpx;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 10rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.subtitle {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+.form-section {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 30rpx;
|
|
|
|
|
+ margin-bottom: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.form-item {
|
|
|
|
|
+ margin-bottom: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.form-item:last-child {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.required {
|
|
|
|
|
+ color: #f44336;
|
|
|
|
|
+}
|
|
|
|
|
+.input, .picker-input {
|
|
|
|
|
+ padding: 20rpx;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.avatar-upload {
|
|
|
|
|
+ width: 150rpx;
|
|
|
|
|
+ height: 150rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.avatar-preview {
|
|
|
|
|
+ width: 150rpx;
|
|
|
|
|
+ height: 150rpx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+}
|
|
|
|
|
+.avatar-placeholder {
|
|
|
|
|
+ width: 150rpx;
|
|
|
|
|
+ height: 150rpx;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+}
|
|
|
|
|
+.plus-icon {
|
|
|
|
|
+ font-size: 60rpx;
|
|
|
|
|
+ color: #ccc;
|
|
|
|
|
+}
|
|
|
|
|
+.actions {
|
|
|
|
|
+ padding-bottom: 40rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-primary {
|
|
|
|
|
+ background: #4CAF50;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border-radius: 40rpx;
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 校验**
|
|
|
|
|
+
|
|
|
|
|
+`node --check`:从文件中提取 `<script>` 块存为临时 `.js` 后运行 `node --check`(或人工核对模板无 `?.`、无 `:key` 表达式、无 CSS Grid)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages.json cfc-frontend/pages/invite/join-form.vue
|
|
|
|
|
+git commit -m "feat: 注册零填写-新增接受家庭邀请页join-form(真实姓名+家庭身份必填)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 4: join.vue 已登录跳转改 join-form
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/pages/invite/join.vue:280-296`
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: `this.familyInviteCode`
|
|
|
|
|
+- Produces: 已登录不在目标家庭 → 跳 `/pages/invite/join-form`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 `goUserEdit` 为 `goJoinForm`**
|
|
|
|
|
+
|
|
|
|
|
+把 L280-296 的注释与 `goUserEdit()` 方法替换:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ // 已登录但不在目标家庭:跳加入家庭表单页。
|
|
|
|
|
+ // join-form 填真实姓名 + 家庭身份后提交加入申请(requestJoinByCode)
|
|
|
|
|
+ this.goJoinForm()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 已登录用户跳转加入家庭表单页(接受家庭邀请流程)
|
|
|
|
|
+ goJoinForm() {
|
|
|
|
|
+ var inviteCode = this.familyInviteCode || uni.getStorageSync('pendingFamilyInviteCode') || ''
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/invite/join-form?familyInviteCode=' + encodeURIComponent(inviteCode)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 删除原 `goUserEdit()` 方法(含 token/userId 拼接与 `familyInviteCode` 参数),模板无直接引用该方法名,仅 `goNext()` 调用。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 确认模板无残留引用**
|
|
|
|
|
+
|
|
|
|
|
+grep 确认 `join.vue` 中不再引用 `goUserEdit`:`grep -n "goUserEdit" cfc-frontend/pages/invite/join.vue` 应无输出。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages/invite/join.vue
|
|
|
|
|
+git commit -m "feat: 注册零填写-接受邀请已登录跳join-form替代user-edit"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 5: profile-field-usage.js 映射表补全
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/utils/profile-field-usage.js`(全文件替换)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 无
|
|
|
|
|
+- Produces: 完整字段→场景映射(供 profile-backfill.js 场景校验 + 后续各功能接入)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 全文件替换为补全版**
|
|
|
|
|
+
|
|
|
|
|
+把整个 `profile-field-usage.js` 内容替换为(新增 realName 及补齐场景):
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+/**
|
|
|
|
|
+ * 个人信息使用关联表
|
|
|
|
|
+ * 记录个人信息的各个字段在哪些功能场景中被使用。
|
|
|
|
|
+ * 用途:
|
|
|
|
|
+ * 1. 功能页自查「本场景用到哪些字段、当前人是否缺失」
|
|
|
|
|
+ * 2. 回写时校验字段确实属于本场景(只从使用处回写)
|
|
|
|
|
+ * 3. 个人中心卡片统计已填字段数
|
|
|
|
|
+ * 对齐:docs/superpowers/specs/2026-09-28-registration-zero-fill-design.md 5.3
|
|
|
|
|
+ */
|
|
|
|
|
+module.exports = {
|
|
|
|
|
+ realName: {
|
|
|
|
|
+ label: '真实姓名',
|
|
|
|
|
+ scenes: [
|
|
|
|
|
+ { scene: 'activity-signup', page: 'pages/activity/activity-detail', usage: '活动报名报名人姓名', backfillTarget: ['user', 'member'] },
|
|
|
|
|
+ { scene: 'family-join', page: 'pages/invite/join-form', usage: '接受家庭邀请申请人姓名', backfillTarget: ['user'] },
|
|
|
|
|
+ { scene: 'contact-create', page: 'components/ContactImport', usage: '创建联系人(独立实体,暂不回写)', backfillTarget: [] }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ 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'] },
|
|
|
|
|
+ { scene: 'member-add', page: 'pages/family/add-member', usage: '添加成员手机号冲突→邀请加入', backfillTarget: ['member'] }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ 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'] },
|
|
|
|
|
+ { scene: 'nutrition-recommend', page: 'pages/health/nutrition-profile', usage: '饮食推荐年龄相关', backfillTarget: ['member'] }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ gender: {
|
|
|
|
|
+ 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'] }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ 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'] }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 注意:`contact-create` 的 `backfillTarget: []` 表示「只登记场景、不回写」(联系人走独立实体),`profile-backfill.js` 的 target 校验逻辑对空数组自然跳过(`indexOf` 不命中即 skip),无需改动 backfill.js。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 校验**
|
|
|
|
|
+
|
|
|
|
|
+`node --check cfc-frontend/utils/profile-field-usage.js` 退出码 0。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/utils/profile-field-usage.js
|
|
|
|
|
+git commit -m "feat: 注册零填写-profile-field-usage映射表补全(realName+各字段场景)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 6: 后端 backfill 白名单增加 realName
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java`(`USER_BACKFILL_WHITELIST` 定义 + `applyUserBackfillField` 方法)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 现有 `backfillProfile(Long userId, ProfileBackfillDTO dto)`
|
|
|
|
|
+- Produces: user 目标可回写 `realName`(member 目标无 realName 列,白名单不变)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 白名单加 realName**
|
|
|
|
|
+
|
|
|
|
|
+找到:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ private static final String[] USER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour", "dietPreferences", "address"};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+替换为:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ private static final String[] USER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour", "dietPreferences", "address", "realName"};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: `applyUserBackfillField` 加 realName 分支**
|
|
|
|
|
+
|
|
|
|
|
+在 `applyUserBackfillField(User user, String field, String value)` 的 switch 中,`case "address":` 之后追加:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ case "realName":
|
|
|
|
|
+ if (value.length() > 20) throw new RuntimeException("真实姓名不能超过20字");
|
|
|
|
|
+ user.setRealName(value);
|
|
|
|
|
+ break;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 确认 `User` 实体有 `realName` 字段与 setter(存在,`updateUserInfo` 已用)。`MEMBER_BACKFILL_WHITELIST` **不加** realName(family_members 表无 real_name 列)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile`
|
|
|
|
|
+Expected: BUILD SUCCESS,退出码 0。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
|
|
|
|
|
+git commit -m "feat: 注册零填写-backfill白名单增加realName回写"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 7: add-member.vue 手机号冲突 → 邀请加入提示
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/pages/family/add-member.vue:77-81`(手机号 hint 文案)、`:204-241`(submit 校验)、`:255-308`(doSubmit)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: `checkPhone`(`@/utils/api.js` 已封装 `POST /api/auth/check-phone`)
|
|
|
|
|
+- Produces: 手机号已注册 → 弹确认「邀请加入」;取消则继续添加
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: import checkPhone**
|
|
|
|
|
+
|
|
|
|
|
+把 L110:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+import { addFamilyMember, updateFamilyMember, recordConsent } from '../../utils/api.js'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+替换为:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+import { addFamilyMember, updateFamilyMember, recordConsent, checkPhone } from '../../utils/api.js'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修改手机号 hint 文案**
|
|
|
|
|
+
|
|
|
|
|
+把 L80:
|
|
|
|
|
+
|
|
|
|
|
+```html
|
|
|
|
|
+<text class="hint">若手机号已注册,将自动关联该账号</text>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+替换为:
|
|
|
|
|
+
|
|
|
|
|
+```html
|
|
|
|
|
+<text class="hint">若手机号已注册,将提示是否邀请该用户加入家庭</text>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: submit() 加手机号冲突检查**
|
|
|
|
|
+
|
|
|
|
|
+在 `submit()` 的 L222-225(手机号格式校验)之后、L227(儿童弹窗检查)之前插入:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+if (this.form.phone && !this.isEdit) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var phoneCheck = await checkPhone(this.form.phone)
|
|
|
|
|
+ if (phoneCheck && phoneCheck.code === 200 && phoneCheck.data === true) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ var confirmed = await new Promise(function(resolve) {
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '手机号已注册',
|
|
|
|
|
+ content: '该手机号已被其他用户注册,是否邀请该用户加入家庭?',
|
|
|
|
|
+ confirmText: '去邀请',
|
|
|
|
|
+ cancelText: '继续添加',
|
|
|
|
|
+ success: function(res2) {
|
|
|
|
|
+ resolve(res2.confirm)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ if (confirmed) {
|
|
|
|
|
+ // 跳转家庭邀请分享页(生成邀请卡片/链接发给该用户)
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/guide/invite/share' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 取消:继续以不可登录用户添加(手机号照常保存,后端不做关联)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.log('手机号存在性检查失败,忽略', e)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 注意:`submit()` 已是 `async`(L204 `async submit()`),`await` 可直接使用。检查仅在**新增**(`!this.isEdit`)时执行,编辑成员跳过。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 校验**
|
|
|
|
|
+
|
|
|
|
|
+人工核对:`submit()` 中新增块的位置在手机号格式校验之后;`self` 变量不与外层冲突(`submit` 内无 `var self` 声明,新增块内 `var self = this` 仅用于 modal 回调);无可选链。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages/family/add-member.vue
|
|
|
|
|
+git commit -m "feat: 注册零填写-添加成员手机号已注册时提示邀请加入"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 8: 验证与文档收尾
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `docs/superpowers/PROJECT-OVERVIEW.md`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 后端编译**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile`
|
|
|
|
|
+Expected: BUILD SUCCESS。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 前端静态校验**
|
|
|
|
|
+
|
|
|
|
|
+- `node --check cfc-frontend/utils/profile-field-usage.js` 退出码 0
|
|
|
|
|
+- 对 join-form.vue / login.vue / user-edit.vue / join.vue / add-member.vue 提取 `<script>` 块逐一 `node --check`
|
|
|
|
|
+- 全局 grep 确认无残留:`grep -rn "goUserEdit\|autoParent=true\|hasPendingFamilyInvite" cfc-frontend/pages/ | grep -v "pendingFamilyInviteCode"`(允许 login.vue 中 `pendingFamilyInviteCode` 读取代码存在;`join.vue` 的 storage 写入保留)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 更新 PROJECT-OVERVIEW.md**
|
|
|
|
|
+
|
|
|
|
|
+把 `2026-09-28-registration-zero-fill-design.md` 条目状态 `🟡 设计稿(待确认)` → `✅ 已实施(注册零填写+接收邀请页+映射表+手机号冲突邀请)`;同时更新头部 v2.13 状态行中该条描述为已实施。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add docs/superpowers/PROJECT-OVERVIEW.md
|
|
|
|
|
+git commit -m "docs: 注册零填写实施完成,PROJECT-OVERVIEW同步"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 推送**
|
|
|
|
|
+
|
|
|
|
|
+Run: `git push`
|
|
|
|
|
+Expected: 推送成功。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 自检记录
|
|
|
|
|
+
|
|
|
|
|
+- **规格覆盖度**:5.1(Task 1-2)、5.2(Task 3-4)、5.3(Task 5-6)、5.4(Task 7)、7 验收标准(Task 8)——全部覆盖
|
|
|
|
|
+- **占位符扫描**:无 TBD/TODO;每个任务含完整代码或明确操作
|
|
|
|
|
+- **类型一致性**:`checkPhone(phone)` 返回 `{code, data: boolean}`(AuthController L253 返回 `Result<Boolean>`);`requestJoinByCode(inviteCode, familyRole)`(api.js L1390);`updateUserInfo(payload)` 透传 realName/nickname/avatar;`User.setRealName(String)` 存在
|
|
|
|
|
+- **风险点**:user-edit.vue 保留的 dead 方法(`joinFamilyAction`/`createFamilyAction`/`confirmRole`/`selectRole`)不引用即无副作用;join-form 页面首次访问需 HBuilderX 重新打包(产物在 unpackage/,本计划不打包)
|