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
}