日期: 2026-05-26 状态: 待审批
家长手动创建的孩子不能登录小程序,只能通过家长端切换角色来操作。但有些场景下孩子需要自主登录(如独立完成任务、查看积分)。需要:
invite_card (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
type VARCHAR(20) -- 'family' | 'guide' | 'child'
ref_id BIGINT -- childId / familyId
code VARCHAR(10) -- 6位随机码
creator_id BIGINT -- 创建人
expires_at DATETIME -- 24h过期
used TINYINT(1) -- 是否已使用
...
)
| 接口 | 说明 |
|---|---|
POST /api/invite-card/generate |
生成邀请码,已有 type/ref_id 参数 |
POST /api/invite-card/verify |
验证邀请码,返回 type/refId/creatorPhone/childInfo |
POST /api/invite-card/accept |
接受邀请,已有 child 类型处理逻辑 |
POST /api/auth/phone-login |
手机验证码登录,返回 token + 用户信息 |
Child.userId — 如果为 null,表示该孩子是手动创建,无关联 User 账号Child.userId — 如果非 null,表示关联到 User 表,孩子可以自主登录Child.phone — 可选,手动创建时可填写,用于后续邀请匹配Child.familyId — 所属家庭家长端孩子管理页
├── 「添加孩子」按钮 → 手动创建 Child(无 User 关联)
└── 「邀请孩子」卡片入口 → 生成邀请卡 → 微信分享
↓
被邀请者打开小程序卡片
↓
login.vue 检测 invite_code
↓
手机号 + 验证码 登录
↓
登录成功后,检测 invite_type=child
↓
调 /api/invite-card/accept
手机号匹配 Child.phone
↓
┌─ 匹配到 → 绑定 Child 到当前 User
└─ 未匹配 → 新建 Child + 绑定 User
↓
设置 currentRole = child
跳转到 child-index
Child.userId = null,无 User 关联,JWT 登录拦截器会拦截accept 时用手机号查该家庭下的 Child:SELECT * FROM child WHERE family_id = ? AND phone = ?child.userId != null 则提示「该孩子已绑定账号」role = child 的 JWT当前逻辑:
// 现有:仅通过 refId 查找 Child,然后匹配手机号
Child child = childService.getById(refId);
if (!child.getPhone().equals(phone)) throw error;
改为:
// 增强:先尝试通过 phone + familyId 匹配已存在的 Child
Child existingChild = childService.lambdaQuery()
.eq(Child::getPhone, phone)
.eq(Child::getFamilyId, familyId)
.one();
if (existingChild != null) {
// 匹配到手动创建的孩子 → 绑定到当前 User
existingChild.setUserId(currentUserId);
childService.updateById(existingChild);
return existingChild;
}
// 未匹配 → 新建 Child(用 User 信息创建)→ 绑定
生成 child 类型邀请卡时:
child.userId == null(未绑定账号的孩子才可邀请)在 phoneLogin() 成功后,检测 session 中是否有 invite_code + type=child:
重构孩子管理页顶部按钮区域:
┌─────────────────────────────┐
│ ┌──────────┐ ┌──────────┐ │
│ │ + 添加孩子 │ │ 📤 邀请孩子 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────┘
「邀请孩子」按钮:
1. 点击 → 调 /api/invite-card/generate { type: "child", refId: childId }
2. 成功 → 存储 inviteCode 到本地
3. 调用 uni.shareAppMessage() 弹出微信分享菜单
4. 分享卡片 path = /pages/login/login?invite_code=xxx&invite_type=child
onLoad(options) {
if (options.invite_code) {
uni.setStorageSync('inviteCode', options.invite_code);
uni.setStorageSync('inviteType', options.invite_type || 'family');
}
}
登录成功后:
const inviteType = uni.getStorageSync('inviteType');
if (inviteType === 'child') {
const inviteCode = uni.getStorageSync('inviteCode');
// 调 accept 接口完成孩子绑定
await acceptInvite({ code: inviteCode, phone: this.phone });
// 返回的 role 应为 child
uni.setStorageSync('currentRole', 'child');
uni.setStorageSync('isSwitchedChild', false);
uni.reLaunch({ url: '/pages/index/index' });
}
export const generateInviteCard = (data) => request('/api/invite-card/generate', data);
export const acceptInvite = (data) => request('/api/invite-card/accept', data);
export const verifyInviteCode = (data) => request('/api/invite-card/verify', data);
无。复用现有 invite_card 表 + child 表。仅需确保 child.phone 字段可用于手机号匹配。