File size: 7,247 Bytes
793e953 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | /**
* 通用表单验证工具
*/
/** 验证结果:通过返回 null,失败返回错误消息 */
export type ValidationResult = string | null
/** 字段级错误集合 */
export type FieldErrors = Record<string, string>
/** 中国手机号格式 */
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
}
|