feat: introduce i18n system and class invitation codes
Add complete i18n infrastructure using next-intl (cookie-driven, without i18n routing) with zh-CN/en dictionary files, locale switcher, and NextIntlClientProvider in root layout. Add class invitation code system with new class_invitation_codes table, data-access layer (generate/validate/consume/revoke), server actions with permission checks, rate limiting, and audit logging. Add class-invitation-manager UI component. Refactor onboarding stepper to use i18n translations and accept new invitation code format (6-char alphanumeric) with backward compatibility for legacy 6-digit codes.
This commit is contained in:
85
src/modules/onboarding/schema.ts
Normal file
85
src/modules/onboarding/schema.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { z } from "zod"
|
||||
|
||||
/**
|
||||
* Onboarding 输入校验 schema。
|
||||
*
|
||||
* 设计原则(参考 K12 教务铁律):
|
||||
* - 角色字段不在用户可提交的 schema 中,角色由管理员预分配,服务端从 usersToRoles 读取。
|
||||
* - 班级代码仅作为"确认/补绑"用途,服务端调用 modules/classes data-access 做强校验。
|
||||
*
|
||||
* v3 修复(对标 PowerSchool Access ID + Access Password):
|
||||
* - P0-2 家长绑定验证增强:从"邮箱+生日"(生日仅 365 种可能)升级为"邮箱+生日+手机号后4位"三因子,
|
||||
* 组合空间提升至 365 × 10000 = 3.65M 种,显著降低枚举攻击风险。
|
||||
* - P1-4 家长多子女:children 数组替代单个 childEmail/childBindingCode,支持一次绑定多个子女。
|
||||
*/
|
||||
const childSchema = z.object({
|
||||
childEmail: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "请填写子女邮箱")
|
||||
.email("子女邮箱格式错误"),
|
||||
childBirthDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "请填写子女生日")
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, "子女生日格式错误(YYYY-MM-DD)"),
|
||||
childPhoneSuffix: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "请填写子女手机号后 4 位")
|
||||
.regex(/^\d{4}$/, "子女手机号后 4 位格式错误"),
|
||||
childRelation: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(50, "关系字段长度不能超过 50 个字符")
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
})
|
||||
|
||||
export const OnboardingSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "请填写姓名")
|
||||
.max(50, "姓名长度不能超过 50 个字符"),
|
||||
phone: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "请填写电话")
|
||||
.regex(/^1\d{10}$/, "请输入有效的手机号(11 位,以 1 开头)"),
|
||||
address: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(200, "住址长度不能超过 200 个字符")
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
// 学生/教师补绑班级的邀请码列表(可选,每项为 6 位字母数字,v3 新格式)
|
||||
// v3:从 6 位数字升级为 6 位字母数字(剔除歧义字符 0/O/1/I/L),兼容旧 6 位数字码
|
||||
classCodes: z
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(
|
||||
/^[A-Z2-9]{6}$|^\d{6}$/,
|
||||
"班级邀请码格式错误(6 位字母数字或 6 位数字)"
|
||||
)
|
||||
)
|
||||
.max(10, "单次最多绑定 10 个班级")
|
||||
.optional()
|
||||
.default([]),
|
||||
// 教师任课科目(可选,v3 修复 P0-3:服务端循环为每个科目绑定)
|
||||
teacherSubjects: z
|
||||
.array(z.string().trim().min(1))
|
||||
.max(10)
|
||||
.optional()
|
||||
.default([]),
|
||||
// 家长绑定子女列表(P1-4 多子女支持)
|
||||
children: z
|
||||
.array(childSchema)
|
||||
.max(10, "单次最多绑定 10 个子女")
|
||||
.optional()
|
||||
.default([]),
|
||||
})
|
||||
|
||||
export type OnboardingInput = z.infer<typeof OnboardingSchema>
|
||||
Reference in New Issue
Block a user