validators.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // utils/validators.ts
  2. // 轻量类型声明,避免对特定 UI 库的硬依赖
  3. export interface FormItemRule {
  4. required?: boolean
  5. message?: string
  6. trigger?: 'blur' | 'change'
  7. pattern?: RegExp
  8. validator?: (
  9. rule: FormItemRule,
  10. value: any,
  11. callback: (error?: Error) => void,
  12. ) => void
  13. }
  14. type I18nT = (key: string, ...args: any[]) => string
  15. export const Patterns = {
  16. // 至少 8-20 位,包含大小写字母、数字、特殊字符中的至少三类
  17. password: /^(?:(?=.*[a-z])(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+=-])|(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+=-])|(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+=-]))[\w!@#$%^&*()+=-]{8,20}$/,
  18. email: /^[\w.+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
  19. idNumber: /^[A-Z0-9]{6,18}$/i,
  20. mobile: /^\d{6,15}$/,
  21. postcode: /^[a-z0-9]{1,15}$/i,
  22. // 允许英文地址常见符号:空格- , . / # '
  23. address: /^[A-Z0-9\s\-.,/#']+$/i,
  24. // 允许中文、字母、数字、空格、连字符、常见中文分隔符与中点
  25. addressCn: /^[\u4E00-\u9FA5A-Z0-9\s\-·,。、《》()()#]+$/i,
  26. }
  27. // ✅ 通用规则封装
  28. export const Validators = {
  29. required: (msg: string, trigger: 'blur' | 'change' = 'blur'): FormItemRule => ({
  30. required: true,
  31. message: msg,
  32. trigger,
  33. }),
  34. pattern: (regex: RegExp, msg: string, trigger: 'blur' | 'change' = 'blur'): FormItemRule => ({
  35. pattern: regex,
  36. message: msg,
  37. trigger,
  38. }),
  39. custom: (
  40. validator: (rule: FormItemRule, value: any, callback: (error?: Error) => void) => void,
  41. trigger: 'blur' | 'change' = 'blur',
  42. ): FormItemRule => ({
  43. validator,
  44. trigger,
  45. }),
  46. }
  47. // ✅ 自定义校验
  48. export function validateAge18(
  49. rule: FormItemRule,
  50. value: string,
  51. callback: (error?: Error) => void,
  52. t: I18nT,
  53. ) {
  54. if (!value)
  55. return callback(new Error(t('card.vaildate.v5')))
  56. const today = new Date()
  57. const birthDate = new Date(value)
  58. let age = today.getFullYear() - birthDate.getFullYear()
  59. const month = today.getMonth() - birthDate.getMonth()
  60. if (month < 0 || (month === 0 && today.getDate() < birthDate.getDate()))
  61. age--
  62. age < 18 ? callback(new Error(t('card.New.n3'))) : callback()
  63. }
  64. export function validateAddress(
  65. rule: FormItemRule,
  66. value: string,
  67. callback: (error?: Error) => void,
  68. t: I18nT,
  69. ) {
  70. const val = String(value ?? '').trim()
  71. if (val.length < 2 || val.length > 40 || !Patterns.address.test(val)) {
  72. callback(new Error(t('card.New.n1')))
  73. }
  74. else {
  75. callback()
  76. }
  77. }
  78. export function validateName(
  79. rule: FormItemRule,
  80. value: string,
  81. callback: (error?: Error) => void,
  82. ) {
  83. const val = String(value ?? '').trim()
  84. const regex = /^[A-Z\s'-]+$/i
  85. if (!val) {
  86. callback(new Error('Name is required'))
  87. return
  88. }
  89. if (!regex.test(val)) {
  90. callback(new Error('Invalid name format'))
  91. return
  92. }
  93. if (val.length < 2 || val.length > 23) {
  94. callback(new Error('Name length must be 2-23 characters'))
  95. return
  96. }
  97. // 连续空格或标点的简单规避
  98. if (/\s{2,}/.test(val)) {
  99. callback(new Error('Name contains consecutive spaces'))
  100. return
  101. }
  102. callback()
  103. }
  104. // 复杂密码校验(独立导出,便于在表单中直接复用)
  105. export function validatePassword(
  106. rule: FormItemRule,
  107. value: string,
  108. callback: (error?: Error) => void,
  109. t?: I18nT,
  110. ) {
  111. const val = String(value ?? '')
  112. if (!val)
  113. return callback(new Error(t ? t('vaildate.password.empty') : 'Password is required'))
  114. if (!Patterns.password.test(val)) {
  115. return callback(
  116. new Error(
  117. t ? t('vaildate.password.format') : 'Password must be 8-20 chars and include 3 of: upper, lower, digit, symbol',
  118. ),
  119. )
  120. }
  121. callback()
  122. }
  123. export function validatePostcode(
  124. rule: FormItemRule,
  125. value: string,
  126. callback: (error?: Error) => void,
  127. t?: I18nT,
  128. ) {
  129. const val = String(value ?? '').trim()
  130. if (!val)
  131. return callback(new Error(t ? t('card.vaildate.v8') : 'Postcode is required'))
  132. if (!Patterns.postcode.test(val)) {
  133. return callback(new Error(t ? t('card.New.n2') : 'Invalid postcode'))
  134. }
  135. callback()
  136. }
  137. export function validateAddressCn(
  138. rule: FormItemRule,
  139. value: string,
  140. callback: (error?: Error) => void,
  141. t?: I18nT,
  142. ) {
  143. const val = String(value ?? '').trim()
  144. if (!val)
  145. return callback(new Error(t ? t('card.vaildate.v27') : 'Address is required'))
  146. if (!Patterns.addressCn.test(val)) {
  147. return callback(new Error(t ? t('card.vaildate.v27') : 'Invalid address characters'))
  148. }
  149. callback()
  150. }