// ============================================================ // 校验函数 — 纯函数,与 UI 层解耦 // 每个校验器返回 string(错误信息)或 null(通过) // ============================================================ type ValidateResult = string | null; /** 用户名:3~64 位,仅允许字母、数字、-、_、. */ export function validateUsername(value: string): ValidateResult { if (!value || !value.trim()) return '请输入用户名'; if (value.length < 3) return '用户名至少 3 位'; if (value.length > 64) return '用户名最多 64 位'; if (!/^[a-zA-Z0-9\-_.]+$/.test(value)) return '仅允许字母、数字、-、_、.'; return null; } /** 密码:6~128 位 */ export function validatePassword(value: string): ValidateResult { if (!value) return '请输入密码'; if (value.length < 6) return '密码至少 6 位'; if (value.length > 128) return '密码最多 128 位'; return null; } /** 确认密码:与密码一致 */ export function validateConfirmPassword(value: string, password: string): ValidateResult { if (!value) return '请再次输入密码'; if (value !== password) return '两次输入的密码不一致'; return null; } /** 手机号:5~32 位数字(可含 + 前缀) */ export function validatePhone(value: string): ValidateResult { if (!value) return '请输入手机号'; if (value.length < 5) return '手机号至少 5 位'; if (value.length > 32) return '手机号最多 32 位'; if (!/^\+?[0-9]+$/.test(value)) return '手机号格式不正确'; return null; } /** 短信验证码:4~8 位数字 */ export function validateSmsCode(value: string): ValidateResult { if (!value) return '请输入验证码'; if (value.length < 4) return '验证码至少 4 位'; if (value.length > 8) return '验证码最多 8 位'; if (!/^[0-9]+$/.test(value)) return '验证码仅允许数字'; return null; } /** 设备 ID:8~128 位 */ export function validateDeviceId(value: string): ValidateResult { if (!value) return '请输入设备标识'; if (value.length < 8) return '设备标识至少 8 位'; if (value.length > 128) return '设备标识最多 128 位'; return null; } /** 可选字段:非空时不超过 maxLen */ export function optionalMaxLen(maxLen: number, label: string) { return (value: string): ValidateResult => { if (!value) return null; if (value.length > maxLen) return `${label}最多 ${maxLen} 位`; return null; }; } /** 邀请码:可选,最多 32 位字母数字 */ export function validateReferralCode(value: string): ValidateResult { if (!value) return null; if (value.length > 32) return '邀请码最多 32 位'; if (!/^[a-zA-Z0-9]+$/.test(value)) return '仅允许字母和数字'; return null; } /** * 对一条字段配置执行所有规则校验 * @returns 错误信息数组(空数组表示通过) */ export function validateField(value: string, rules: import('../model/types').FieldRule[]): string[] { const errors: string[] = []; for (const rule of rules) { // required if (rule.required && (!value || !value.trim())) { errors.push(rule.message || '此字段为必填'); continue; } if (!value) continue; // 非必填且为空 → 跳过后续校验 // minLen if (rule.minLen && value.length < rule.minLen) { errors.push(rule.message || `至少 ${rule.minLen} 位`); } // maxLen if (rule.maxLen && value.length > rule.maxLen) { errors.push(rule.message || `最多 ${rule.maxLen} 位`); } // pattern if (rule.pattern && !rule.pattern.test(value)) { errors.push(rule.message || '格式不正确'); } } return errors; } /** * 逐字段运行校验器,有错误则设置并返回 true。 * * 用法(在 handleSubmit 中): * if (runFieldChecks(setErrors, * { field: 'phone', error: validatePhone(phone) }, * { field: 'pwd', error: validatePassword(pwd) }, * { field: 'confirm', error: validateConfirmPassword(confirm, pwd) }, * )) return; */ export function runFieldChecks( setErrors: (errors: Record) => void, ...checks: Array<{ field: string; error: string | null }> ): boolean { const newErrors: Record = {}; for (const { field, error } of checks) { if (error) newErrors[field] = error; } if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return true; } setErrors({}); return false; } /** * 校验整个表单 * @returns 错误映射 { fieldName: errorMsg[] },空对象表示全部通过 */ export function validateForm>( values: T, fieldConfigs: import('../model/types').FieldConfig[], crossValidators?: Record string | null>, ): Record { const errors: Record = {}; for (const config of fieldConfigs) { const value = values[config.name] || ''; const fieldErrors = validateField(value, config.rules); if (fieldErrors.length > 0) { errors[config.name] = fieldErrors; } } // 跨字段校验(如确认密码) if (crossValidators) { for (const [name, fn] of Object.entries(crossValidators)) { const value = values[name] || ''; const err = fn(value, values); if (err) { errors[name] = [...(errors[name] || []), err]; } } } return errors; }