registerSchema.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import {
  2. z,
  3. ZodTypeAny,
  4. ZodString,
  5. ZodNumber,
  6. ZodBoolean,
  7. ZodEffects,
  8. } from 'zod';
  9. import { RegisterInfo } from '@/API/patient/workActions';
  10. import { Rule } from 'antd/es/form';
  11. import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
  12. type IgnoredKeys = 'views' | 'study_type' | 'modality' | 'patient_type'; // 这些字段不在表单中填写
  13. const registerInfoSchema: Record<
  14. keyof Omit<RegisterInfo, IgnoredKeys>,
  15. z.ZodTypeAny
  16. > = {
  17. accession_number: z.string().nonempty().regex(/^[a-zA-Z0-9_-]+$/, '只能输入数字、英文字符、下划线和连字符'),
  18. patient_name: z.string().nonempty(),
  19. patient_id: z.string().nonempty().regex(/^[a-zA-Z0-9_-]+$/, '只能输入数字、英文字符、下划线和连字符'),
  20. patient_size: z.string().nonempty(),
  21. patient_age: z.object({
  22. number: z.number(), // 必须是数字
  23. unit: z.string(), // 必须是字符串(根据你的需求,也可以是 z.enum(...) 等)
  24. }),
  25. patient_dob: z.string().nonempty(),
  26. patient_sex: z.string().optional(),
  27. ref_physician: z.string().optional(),
  28. operator_id: z.string().regex(/^[a-zA-Z0-9_-]*$/, '只能输入数字、英文字符、下划线和连字符').optional(),
  29. weight: z.number().optional(),
  30. thickness: z.number().optional(),
  31. length: z.number().optional(),
  32. comment: z.string().optional(),
  33. // views: z.array(z.object({
  34. // view_id: z.string(),
  35. // procedure_id: z.string()
  36. // })).optional()
  37. /**---下面是宠物专用字段--- */
  38. sex_neutered: z.string().optional(),
  39. chip_number: z.string().optional(),
  40. variety: z.string().optional(),
  41. owner_name: z.string().nonempty(),
  42. is_anaesthesia: z.boolean().optional(),
  43. is_sedation: z.boolean().optional(),
  44. /**---下面是人医专用字段--- */
  45. pregnancy_status: z.nativeEnum(PregnancyStatus).optional(),
  46. };
  47. const registerformSchema = z.object(registerInfoSchema);
  48. // 统一管理规则。规则表。描述要验证的字段【字段来源于被验证的数据结构UserInfo】,以及每个字段的"是否必填、必填标记、验证规则、提示信息、验证时机"
  49. export const registerFormFields = {
  50. accession_number: {
  51. label: 'Accession Number',
  52. required: true,
  53. requiredLabel: '',
  54. validation: zodToAntdRules(registerformSchema.shape.accession_number),
  55. message: 'Accession Number is optional',
  56. trigger: ['onChange', 'onBlur'],
  57. },
  58. patient_id: {
  59. label: 'Patient ID',
  60. required: true,
  61. requiredLabel: 'Patient ID is required',
  62. validation: zodToAntdRules(registerformSchema.shape.patient_id),
  63. message: 'Patient ID is required',
  64. trigger: ['onChange', 'onBlur'],
  65. },
  66. patient_name: {
  67. label: 'Patient Name',
  68. required: true,
  69. requiredLabel: 'Patient Name is required',
  70. validation: zodToAntdRules(registerformSchema.shape.patient_name),
  71. message: 'Patient Name is required',
  72. trigger: ['onChange', 'onBlur'],
  73. },
  74. patient_size: {
  75. label: 'Patient Size',
  76. required: true,
  77. requiredLabel: 'Patient Size is required',
  78. validation: zodToAntdRules(registerformSchema.shape.patient_size),
  79. message: 'Patient Size is required',
  80. trigger: ['onChange', 'onBlur'],
  81. },
  82. patient_age: {
  83. label: 'Patient Age',
  84. required: true,
  85. requiredLabel: 'Patient Age is required',
  86. validation: [
  87. ...zodToAntdRules(registerformSchema.shape.patient_age),
  88. {
  89. validator: (_, value) => {
  90. if (!value || typeof value.number !== 'number') {
  91. return Promise.reject(new Error('请输入有效的年龄'));
  92. }
  93. if (value.number < 0) {
  94. return Promise.reject(new Error('年龄不能为负数'));
  95. }
  96. if (value.number > 200) {
  97. return Promise.reject(new Error('年龄超出合理范围(最大200)'));
  98. }
  99. if (!Number.isInteger(value.number)) {
  100. return Promise.reject(new Error('年龄必须为整数'));
  101. }
  102. return Promise.resolve();
  103. },
  104. },
  105. ],
  106. message: 'Patient Age is required',
  107. trigger: ['onChange', 'onBlur'],
  108. },
  109. patient_dob: {
  110. label: 'Patient DOB',
  111. required: true,
  112. requiredLabel: 'Patient DOB is required',
  113. validation: zodToAntdRules(registerformSchema.shape.patient_dob),
  114. message: 'Patient DOB is required',
  115. trigger: ['onChange', 'onBlur'],
  116. },
  117. patient_sex: {
  118. label: 'Patient Sex',
  119. required: false,
  120. requiredLabel: 'Patient Sex is required',
  121. validation: zodToAntdRules(registerformSchema.shape.patient_sex),
  122. message: 'Patient Sex is required',
  123. trigger: ['onChange', 'onBlur'],
  124. },
  125. // sex_neutered: {
  126. // label: 'Sex Neutered',
  127. // required: false,
  128. // requiredLabel: '',
  129. // validation: zodToAntdRules(registerformSchema.shape.sex_neutered),
  130. // message: 'Sex Neutered is optional',
  131. // trigger: ['onChange', 'onBlur'],
  132. // },
  133. pregnancy_status: {
  134. label: 'Pregnancy Status',
  135. required: false,
  136. requiredLabel: '',
  137. validation: zodToAntdRules(registerformSchema.shape.pregnancy_status),
  138. message: 'Pregnancy Status is optional',
  139. trigger: ['onChange', 'onBlur'],
  140. },
  141. // chip_number: {
  142. // label: 'Chip Number',
  143. // required: false,
  144. // requiredLabel: '',
  145. // validation: zodToAntdRules(registerformSchema.shape.chip_number),
  146. // message: 'Chip Number is optional',
  147. // trigger: ['onChange', 'onBlur'],
  148. // },
  149. // variety: {
  150. // label: 'Variety',
  151. // required: false,
  152. // requiredLabel: '',
  153. // validation: zodToAntdRules(registerformSchema.shape.variety),
  154. // message: 'Variety is optional',
  155. // trigger: ['onChange', 'onBlur'],
  156. // },
  157. // patient_type: {
  158. // label: 'Patient Type',
  159. // required: true,
  160. // requiredLabel: '',
  161. // validation: zodToAntdRules(registerformSchema.shape.patient_type),
  162. // message: 'Patient Type is optional',
  163. // trigger: ['onChange', 'onBlur'],
  164. // },
  165. ref_physician: {
  166. label: 'Referring Physician',
  167. required: false,
  168. requiredLabel: '',
  169. validation: zodToAntdRules(registerformSchema.shape.ref_physician),
  170. message: 'Referring Physician is optional',
  171. trigger: ['onChange', 'onBlur'],
  172. },
  173. operator_id: {
  174. label: 'Operator ID',
  175. required: false,
  176. requiredLabel: '',
  177. validation: zodToAntdRules(registerformSchema.shape.operator_id),
  178. message: 'Operator ID is optional',
  179. trigger: ['onChange', 'onBlur'],
  180. },
  181. weight: {
  182. label: 'Weight',
  183. required: false,
  184. requiredLabel: '',
  185. validation: zodToAntdRules(registerformSchema.shape.weight),
  186. message: 'Weight is optional',
  187. trigger: ['onChange', 'onBlur'],
  188. },
  189. thickness: {
  190. label: 'Thickness',
  191. required: false,
  192. requiredLabel: '',
  193. validation: zodToAntdRules(registerformSchema.shape.thickness),
  194. message: 'Thickness is optional',
  195. trigger: ['onChange', 'onBlur'],
  196. },
  197. length: {
  198. label: 'Length',
  199. required: false,
  200. requiredLabel: '',
  201. validation: zodToAntdRules(registerformSchema.shape.length),
  202. message: 'Length is optional',
  203. trigger: ['onChange', 'onBlur'],
  204. },
  205. comment: {
  206. label: 'Comment',
  207. required: false,
  208. requiredLabel: '',
  209. validation: zodToAntdRules(registerformSchema.shape.comment),
  210. message: 'Comment is optional',
  211. trigger: ['onChange', 'onBlur'],
  212. },
  213. //动物专用字段
  214. owner_name: {
  215. label: 'Owner Name',
  216. required: true,
  217. requiredLabel: '',
  218. validation: zodToAntdRules(registerformSchema.shape.owner_name),
  219. message: 'Owner Name is required',
  220. trigger: ['onChange', 'onBlur'],
  221. },
  222. sex_neutered: {
  223. label: 'neutered',
  224. required: false,
  225. requiredLabel: '',
  226. validation: zodToAntdRules(registerformSchema.shape.sex_neutered),
  227. message: 'neutered is optional',
  228. trigger: ['onChange', 'onBlur'],
  229. },
  230. chip_number: {
  231. label: 'Chip Number',
  232. required: false,
  233. requiredLabel: '',
  234. validation: zodToAntdRules(registerformSchema.shape.chip_number),
  235. message: 'Chip Number is optional',
  236. trigger: ['onChange', 'onBlur'],
  237. },
  238. variety: {
  239. label: 'Variety',
  240. required: false,
  241. requiredLabel: '',
  242. validation: zodToAntdRules(registerformSchema.shape.variety),
  243. message: 'Variety is optional',
  244. trigger: ['onChange', 'onBlur'],
  245. },
  246. is_anaesthesia: {
  247. label: 'Anaesthesia',
  248. required: false,
  249. requiredLabel: '',
  250. validation: zodToAntdRules(registerformSchema.shape.is_anaesthesia),
  251. message: 'Anaesthesia is optional',
  252. trigger: ['onChange', 'onBlur'],
  253. },
  254. is_sedation: {
  255. label: 'Sedation',
  256. required: false,
  257. requiredLabel: '',
  258. validation: zodToAntdRules(registerformSchema.shape.is_sedation),
  259. message: 'Sedation is optional',
  260. trigger: ['onChange', 'onBlur'],
  261. },
  262. } satisfies Record<
  263. keyof Omit<RegisterInfo, IgnoredKeys>,
  264. {
  265. label: string;
  266. required: boolean;
  267. requiredLabel: string;
  268. validation: Rule[];
  269. message: string;
  270. trigger: ('onChange' | 'onBlur' | 'onSubmit')[];
  271. }
  272. >;
  273. export default registerformSchema;
  274. type AntdRule = Rule & {};
  275. export function zodToAntdRules(field: ZodTypeAny): AntdRule[] {
  276. const rules: AntdRule[] = [];
  277. // 解开 effects,比如 .refine() 或 .transform()
  278. while (field instanceof ZodEffects) {
  279. field = field._def.schema;
  280. }
  281. if (field instanceof ZodString) {
  282. for (const check of field._def.checks) {
  283. switch (check.kind) {
  284. case 'min':
  285. if (check.value === 1) {
  286. rules.push({ required: true, message: check.message || '必填项' });
  287. } else {
  288. rules.push({ min: check.value, message: check.message ?? '' });
  289. }
  290. break;
  291. case 'max':
  292. rules.push({ max: check.value, message: check.message ?? '' });
  293. break;
  294. case 'email':
  295. rules.push({ type: 'email', message: check.message ?? '' });
  296. break;
  297. case 'regex':
  298. rules.push({ pattern: check.regex, message: check.message ?? '' });
  299. break;
  300. case 'length':
  301. rules.push({ len: check.value, message: check.message ?? '' });
  302. break;
  303. }
  304. }
  305. }
  306. if (field instanceof ZodNumber) {
  307. for (const check of field._def.checks) {
  308. switch (check.kind) {
  309. case 'min':
  310. rules.push({ min: check.value, message: check.message ?? '' });
  311. break;
  312. case 'max':
  313. rules.push({ max: check.value, message: check.message ?? '' });
  314. break;
  315. case 'int':
  316. rules.push({
  317. type: 'integer',
  318. message: check.message || '必须为整数',
  319. });
  320. break;
  321. }
  322. }
  323. }
  324. if (field instanceof ZodBoolean) {
  325. rules.push({ type: 'boolean', message: '必须为布尔值' });
  326. }
  327. return rules;
  328. }