新表 family_invite+注册页 /register+家长端家庭管理卡片; 新家长与现有家长权限一致,本次不做成员移除。 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
4.7 KiB
4.7 KiB
家庭管理:邀请第二位家长加入 - 设计文档
日期:2026-08-19 状态:已批准
背景
家长端目前只有 seed 脚本创建的首位家长(手机号+密码登录),家庭内无法自助添加其他家长(如配偶)。 用户希望:能添加家庭其他家长,第二位家长也能查看孩子的学习情况。
关键现状:
users.familyId已支持一家庭多家长;/parent所有查询按 familyId 隔离, 第二位家长只要拿到同一 familyId 即自动看到全部孩子数据,无需权限改造。- 首位家长账号由
scripts/seed.ts创建,生产无注册页。 - 孩子加入:家长生成 6 位激活码(
activation_code表,一次性+15分钟) → 孩子设备输入 → 2 年设备会话。 families表已有name字段(seed 写入),尚未展示。
需求(已与用户确认)
- 家长端「家庭管理」可生成邀请码,第二位家长凭码注册加入家庭。
- 新加入家长权限与现有家长完全一样(概览/课程/积分/设备/孩子管理全可见)。
- 本次不做成员移除(用户选定)。
- 邀请码:6 位数字、一次性、15 分钟有效(与孩子激活码一致)。
方案选择
- 方案 A(采用):独立邀请码表
family_invite+ 新注册页/register- 仿
activation_code既有模式,注册页表单(称呼+手机号+密码+邀请码),对方自设密码。
- 仿
- 方案 B(弃):家长代建账号直接填手机号+初始密码 → 对方无法自选密码,需线下传递。
- 方案 C(弃):复用
activation_code表 → 该表按 childUserId 绑定,语义冲突。
设计
1. 数据模型(schema.ts + 迁移 0009_family-invite.sql)
export const familyInvites = mysqlTable("family_invite", {
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
familyId: bigint("family_id", { mode: "number" })
.notNull()
.references(() => families.id, { onDelete: "cascade" }),
codeHash: varchar("code_hash", { length: 64 }).notNull(),
createdBy: bigint("created_by", { mode: "number" })
.notNull()
.references(() => users.id),
createdAt: datetime("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
expiresAt: datetime("expires_at").notNull(),
usedAt: datetime("used_at"),
});
2. 注册页:src/app/register/page.tsx + actions.ts(仿 join,server action + useActionState)
- 表单:称呼、手机号(11 位)、密码(≥6 位)、邀请码(6 位数字)。
registerWithInvite(prev, formData)逻辑:- zod 校验表单;邀请码 sha256 查询
family_invite(未用且未过期),失败统一报「邀请码无效或已过期,请让家长重新生成」; - 校验手机号未被占用(
users.phone唯一),占用报「该手机号已注册」; - 事务:标记邀请码已用 → 插入
user(role=parent, familyId=邀请码家庭, phone, passwordHash)→createSession(30 天)→redirect("/parent")。
- zod 校验表单;邀请码 sha256 查询
- 密码 bcrypt 哈希(与 login 一致)。
3. 登录页入口
src/app/login/page.tsx底部「去输入激活码」旁加「没有账号?用邀请码加入家庭」链接 →/register。
4. 家长端「家庭管理」卡片
src/components或src/app/parent/family-manage.tsx(client 组件,仿 child-manage):- 展示:家庭名 + 成员列表(家长显示称呼+「家长」徽标,孩子显示名字+「孩子」徽标);
- 「生成邀请码」按钮 → 调
createFamilyInviteaction → 内联显示 6 位码 +「15 分钟有效,一次性」; - 允许同时存在多个未用邀请码,不做互斥。
- dashboard.tsx「功能」tab 中与孩子管理并列插入该卡片;
/parent/page.tsx查询家庭成员数据传入:parents:users.role=parent AND familyId=parent.familyIdkids: 已有查询复用familyName:families表按 familyId 查询
- action
createFamilyInvite(parent/actions.ts):取会话家长 → 生成 6 位随机数字码(与 createActivationCode 同法,15 分钟过期) → 存 hash → 返回码明文给调用方展示。
5. 不改动
- 孩子激活流程、
login逻辑、activation_code表、devices-tab。
测试清单(手工,项目无自动化测试框架)
- 家长端功能 tab → 家庭管理卡片:显示家庭名与全部成员。
- 生成邀请码 → 显示 6 位码;15 分钟后(或码用过后)注册报「邀请码无效或已过期」。
/register成功注册 → 自动登录进入/parent,看到全部孩子与数据(概览/错题/周测)。- 第二家长登录后:设备管理能看到两家长会话;课程/积分改动互相可见。
- 手机号已注册(任意家庭) → 报「该手机号已注册」。
- 孩子设备激活流程不受影响;登录页新链接可达
/register。 - 部署 jump.hn12301.net 后线上复验。