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
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())docs/superpowers/ 文件增改必须同步 PROJECT-OVERVIEW.mdfeat: 注册零填写-xxxpages/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 |
修改 | 状态同步 |
Files:
cfc-frontend/pages/login/login.vue:174-181Interfaces:
uni.getStorageSync('pendingFamilyInviteCode')(join.vue 写入)Produces: 登录成功后的跳转决策(直接进首页 或 跳 join-form)
[ ] Step 1: 修改 completeLogin() 跳转逻辑
把 L174-181 的以下代码:
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()
}
替换为:
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()
}
node --check 无法直接检查 .vue,改为人工核对:确认 L174-181 区域替换正确、hasPendingFamilyInvite 变量不再被引用、无遗留语法错误(uni.showToast/self.loading 保持原位置)。
[ ] Step 3: 提交
git add cfc-frontend/pages/login/login.vue
git commit -m "feat: 注册零填写-登录成功直接进首页,家庭邀请改跳join-form"
Files:
cfc-frontend/pages/user-edit/user-edit.vue:418-463(onLoad)、:754-768(saveUserInfo 家庭邀请块)、:48-57(家庭身份模板区)、:400-403(hasPendingFamilyInvite computed)Interfaces:
Produces: 纯编辑模式页面(个人中心入口)
[ ] Step 1: 替换 onLoad 为新用户流程废弃版
把 L418-463 整个 onLoad(options) {...} 替换为:
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 测试分支全部删除。
删除以下整段:
<!-- 家庭身份(接受家庭邀请时置顶显示,方便第一时间选择家庭身份) -->
<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>
hasPendingFamilyInvite computed删除 L400-403:
// 是否有待处理的家庭邀请码
hasPendingFamilyInvite: function() {
return !!uni.getStorageSync('pendingFamilyInviteCode')
},
saveUserInfo() 家庭邀请分支L753-778 当前结构(含 if (this.isNewUserFlow && this.hasPendingFamilyInvite) {...} else {...})。由于 isNewUserFlow 恒为 false,将整个 if/else 替换为 else 分支内容:
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 = ...、requestJoinByCodetry/catch 块、uni.removeStorageSync('pendingFamilyInviteCode')。
人工核对:模板中不再引用 hasPendingFamilyInvite;saveUserInfo 无 requestJoinByCode / pendingFamilyInviteCode 引用;isNewUserFlow 仍存在 data 定义(恒 false,其他 v-if 引用无害)。检查 onFamilyRoleChange / familyRoleOptions / familyRoleIndex 若模板无引用则保留方法体不删(无害死代码)。
[ ] Step 6: 提交
git add cfc-frontend/pages/user-edit/user-edit.vue
git commit -m "feat: 注册零填写-user-edit停用新用户流程仅保留编辑用途"
Files:
cfc-frontend/pages/invite/join-form.vuecfc-frontend/pages.json(pages/invite 分包注册)Interfaces:
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 条目后追加:
{
"path": "join-form",
"style": {
"navigationBarTitleText": "加入家庭"
}
}
创建 cfc-frontend/pages/invite/join-form.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>
node --check:从文件中提取 <script> 块存为临时 .js 后运行 node --check(或人工核对模板无 ?.、无 :key 表达式、无 CSS Grid)。
[ ] Step 4: 提交
git add cfc-frontend/pages.json cfc-frontend/pages/invite/join-form.vue
git commit -m "feat: 注册零填写-新增接受家庭邀请页join-form(真实姓名+家庭身份必填)"
Files:
cfc-frontend/pages/invite/join.vue:280-296Interfaces:
this.familyInviteCodeProduces: 已登录不在目标家庭 → 跳 /pages/invite/join-form
[ ] Step 1: 修改 goUserEdit 为 goJoinForm
把 L280-296 的注释与 goUserEdit() 方法替换:
// 已登录但不在目标家庭:跳加入家庭表单页。
// 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()调用。
grep 确认 join.vue 中不再引用 goUserEdit:grep -n "goUserEdit" cfc-frontend/pages/invite/join.vue 应无输出。
[ ] Step 3: 提交
git add cfc-frontend/pages/invite/join.vue
git commit -m "feat: 注册零填写-接受邀请已登录跳join-form替代user-edit"
Files:
cfc-frontend/utils/profile-field-usage.js(全文件替换)Interfaces:
Produces: 完整字段→场景映射(供 profile-backfill.js 场景校验 + 后续各功能接入)
[ ] Step 1: 全文件替换为补全版
把整个 profile-field-usage.js 内容替换为(新增 realName 及补齐场景):
/**
* 个人信息使用关联表
* 记录个人信息的各个字段在哪些功能场景中被使用。
* 用途:
* 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。
node --check cfc-frontend/utils/profile-field-usage.js 退出码 0。
[ ] Step 3: 提交
git add cfc-frontend/utils/profile-field-usage.js
git commit -m "feat: 注册零填写-profile-field-usage映射表补全(realName+各字段场景)"
Files:
cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java(USER_BACKFILL_WHITELIST 定义 + applyUserBackfillField 方法)Interfaces:
backfillProfile(Long userId, ProfileBackfillDTO dto)Produces: user 目标可回写 realName(member 目标无 realName 列,白名单不变)
[ ] Step 1: 白名单加 realName
找到:
private static final String[] USER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour", "dietPreferences", "address"};
替换为:
private static final String[] USER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour", "dietPreferences", "address", "realName"};
applyUserBackfillField 加 realName 分支在 applyUserBackfillField(User user, String field, String value) 的 switch 中,case "address": 之后追加:
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 列)。
Run: cd cfc-backend && mvn clean compile
Expected: BUILD SUCCESS,退出码 0。
[ ] Step 4: 提交
git add cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
git commit -m "feat: 注册零填写-backfill白名单增加realName回写"
Files:
cfc-frontend/pages/family/add-member.vue:77-81(手机号 hint 文案)、:204-241(submit 校验)、:255-308(doSubmit)Interfaces:
checkPhone(@/utils/api.js 已封装 POST /api/auth/check-phone)Produces: 手机号已注册 → 弹确认「邀请加入」;取消则继续添加
[ ] Step 1: import checkPhone
把 L110:
import { addFamilyMember, updateFamilyMember, recordConsent } from '../../utils/api.js'
替换为:
import { addFamilyMember, updateFamilyMember, recordConsent, checkPhone } from '../../utils/api.js'
把 L80:
<text class="hint">若手机号已注册,将自动关联该账号</text>
替换为:
<text class="hint">若手机号已注册,将提示是否邀请该用户加入家庭</text>
在 submit() 的 L222-225(手机号格式校验)之后、L227(儿童弹窗检查)之前插入:
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(L204async submit()),await可直接使用。检查仅在新增(!this.isEdit)时执行,编辑成员跳过。
人工核对:submit() 中新增块的位置在手机号格式校验之后;self 变量不与外层冲突(submit 内无 var self 声明,新增块内 var self = this 仅用于 modal 回调);无可选链。
[ ] Step 5: 提交
git add cfc-frontend/pages/family/add-member.vue
git commit -m "feat: 注册零填写-添加成员手机号已注册时提示邀请加入"
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: 提交
git add docs/superpowers/PROJECT-OVERVIEW.md
git commit -m "docs: 注册零填写实施完成,PROJECT-OVERVIEW同步"
[ ] Step 5: 推送
Run: git push
Expected: 推送成功。
checkPhone(phone) 返回 {code, data: boolean}(AuthController L253 返回 Result<Boolean>);requestJoinByCode(inviteCode, familyRole)(api.js L1390);updateUserInfo(payload) 透传 realName/nickname/avatar;User.setRealName(String) 存在joinFamilyAction/createFamilyAction/confirmRole/selectRole)不引用即无副作用;join-form 页面首次访问需 HBuilderX 重新打包(产物在 unpackage/,本计划不打包)