Files
NextEdu/src/modules/onboarding/schema.ts
SpecialX c90748124d 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.
2026-06-22 14:04:55 +08:00

86 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>