/** * 通用表单验证工具 */ /** 验证结果:通过返回 null,失败返回错误消息 */ export type ValidationResult = string | null /** 字段级错误集合 */ export type FieldErrors = Record /** 中国手机号格式 */ const PHONE_REGEX = /^1[3-9]\d{9}$/ /** 邮箱格式 */ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ /** 动物名称:2-20字符,中英文数字 */ const ANIMAL_NAME_REGEX = /^[\u4e00-\u9fa5a-zA-Z0-9]{2,20}$/ /** 主人姓名:2-20字符,中文(允许中间有 · ) */ const OWNER_NAME_REGEX = /^[\u4e00-\u9fa5·]{2,20}$/ /** 中文年龄格式:数字 + 岁/月/周/天/年 */ const AGE_REGEX = /^(\d+(\.\d+)?)\s*(岁|月|周|天|年)$/ // ============================================================ // 基础验证 // ============================================================ /** 验证手机号 */ export function validatePhone(value: string): ValidationResult { if (!value || !value.trim()) return '请输入手机号' if (!PHONE_REGEX.test(value.trim())) return '手机号格式不正确(11位数字)' return null } /** 验证必填字段 */ export function validateRequired(value: string, label: string): ValidationResult { if (!value || !value.trim()) return `请输入${label}` return null } /** 验证正数(体重等) */ export function validatePositiveNumber(value: string, label: string): ValidationResult { if (!value || !value.trim()) return null // 可选字段 const num = Number(value) if (isNaN(num) || num <= 0) return `${label}必须为正数` return null } /** 验证日期不早于今天(用于下次回访日期等未来日期) */ export function validateFutureDate(value: string, label: string): ValidationResult { if (!value) return null // 可选字段 const date = new Date(value) const today = new Date() today.setHours(0, 0, 0, 0) if (date < today) return `${label}不能早于今天` return null } /** 验证结束日期不早于开始日期 */ export function validateDateRange(startDate: string, endDate: string, startLabel: string, endLabel: string): ValidationResult { if (!startDate || !endDate) return null if (new Date(endDate) < new Date(startDate)) return `${endLabel}不能早于${startLabel}` return null } // ============================================================ // 新增验证函数 // ============================================================ /** 验证邮箱格式 */ export function validateEmail(value: string): ValidationResult { if (!value || !value.trim()) return null // 可选字段 if (!EMAIL_REGEX.test(value.trim())) return '邮箱格式不正确' return null } /** * 验证年龄 * 支持格式: * - 纯数字(视为"岁"):3 → 3岁 * - 带单位:3岁 / 6个月 / 2周 / 14天 / 1年 * 范围:0-50岁(月/周/天会自动换算校验) */ export function validateAge(value: string): ValidationResult { if (!value || !value.trim()) return null // 可选字段 const trimmed = value.trim() // 尝试匹配中文年龄格式 const ageMatch = trimmed.match(AGE_REGEX) if (ageMatch) { const num = parseFloat(ageMatch[1]) const unit = ageMatch[3] switch (unit) { case '岁': case '年': if (num < 0 || num > 50) return '年龄应在 0-50 岁之间' break case '月': if (num < 0 || num > 600) return '年龄应在 0-50 岁之间(600个月)' break case '周': if (num < 0 || num > 2600) return '年龄应在 0-50 岁之间(2600周)' break case '天': if (num < 0 || num > 18250) return '年龄应在 0-50 岁之间(18250天)' break } return null } // 纯数字视为"岁" const pureNum = Number(trimmed) if (!isNaN(pureNum) && pureNum >= 0 && pureNum <= 50) return null return '年龄格式不正确(如:3岁 / 6个月 / 2周)' } /** 验证体重(正数,0.01-200kg) */ export function validateWeight(value: string): ValidationResult { if (!value || !value.trim()) return null // 可选字段 const num = Number(value) if (isNaN(num)) return '体重必须为数字' if (num <= 0) return '体重必须为正数' if (num < 0.01) return '体重最小为 0.01 kg' if (num > 200) return '体重不能超过 200 kg' return null } /** * 验证密码强度 * - 至少 6 位 * - 建议包含字母和数字(仅警告,不阻断) */ export function validatePassword(value: string): ValidationResult { if (!value) return '请输入密码' if (value.length < 6) return '密码至少 6 位' // 建议包含字母和数字(弱密码仅提示,不阻断提交) const hasLetter = /[a-zA-Z]/.test(value) const hasDigit = /\d/.test(value) if (!hasLetter || !hasDigit) { return '建议包含字母和数字以增强安全性' } return null } /** 验证密码强度(严格模式,用于注册等场景) * - 至少 6 位 * - 必须包含字母和数字 */ export function validatePasswordStrict(value: string): ValidationResult { if (!value) return '请输入密码' if (value.length < 6) return '密码至少 6 位' const hasLetter = /[a-zA-Z]/.test(value) const hasDigit = /\d/.test(value) if (!hasLetter || !hasDigit) return '密码必须包含字母和数字' return null } /** 注册/登录密码校验:仅校验最小长度,不阻断弱密码 */ export function validatePasswordBasic(value: string): ValidationResult { if (!value) return '请输入密码' if (value.length < 6) return '密码至少 6 位' return null } /** 验证动物名称(2-20字符,中英文数字) */ export function validateAnimalName(value: string): ValidationResult { if (!value || !value.trim()) return '请输入动物名称' const trimmed = value.trim() if (trimmed.length < 2) return '动物名称至少 2 个字符' if (trimmed.length > 20) return '动物名称最多 20 个字符' if (!ANIMAL_NAME_REGEX.test(trimmed)) return '动物名称仅支持中英文和数字' return null } /** 验证主人姓名(2-20字符,中文,允许中间有 ·) */ export function validateOwnerName(value: string): ValidationResult { if (!value || !value.trim()) return '请输入宠主姓名' const trimmed = value.trim() if (trimmed.length < 2) return '宠主姓名至少 2 个字符' if (trimmed.length > 20) return '宠主姓名最多 20 个字符' if (!OWNER_NAME_REGEX.test(trimmed)) return '宠主姓名仅支持中文' return null } /** 验证最大长度 */ export function validateMaxLength(value: string, max: number, label: string): ValidationResult { if (!value) return null if (value.length > max) return `${label}不能超过 ${max} 个字符` return null } // ============================================================ // 批量验证 // ============================================================ /** 批量验证字段,返回第一个错误或 null */ export function validateFields(validations: Array<{ field: string; result: ValidationResult }>): FieldErrors | null { const errors: FieldErrors = {} for (const { field, result } of validations) { if (result) errors[field] = result } return Object.keys(errors).length > 0 ? errors : null }