register.form.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import React, { useEffect, useRef, useMemo } from 'react';
  2. import {
  3. Form,
  4. Input,
  5. DatePicker,
  6. InputNumber,
  7. Select,
  8. Radio,
  9. FormInstance,
  10. } from 'antd';
  11. import { useIntl, FormattedMessage } from 'react-intl';
  12. import { registerFormFields } from '@/validation/patient/registerSchema';
  13. import NumberWithUnit from '@/components/NumberWithUnit';
  14. import dayjs, { Dayjs } from 'dayjs';
  15. import { useSelector } from 'react-redux';
  16. import { RootState } from '@/states/store';
  17. import { PregnancyStatus, pregnancyStatusOptions } from '@/domain/patient/pregnancyStatus';
  18. import { getGenderOptions } from '@/domain/patient/genderOptions';
  19. interface BasicInfoFormProps {
  20. style?: React.CSSProperties;
  21. form?: FormInstance;
  22. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  23. onValuesChange?: (changedValues: any, allValues: any) => void;
  24. }
  25. const BasicInfoForm: React.FC<BasicInfoFormProps> = ({
  26. style,
  27. form,
  28. onValuesChange,
  29. }) => {
  30. // 使用 ref 记录更新源,防止循环触发
  31. const updateSourceRef = useRef<'age' | 'dob' | null>(null);
  32. const patient_age = Form.useWatch('patient_age', form);
  33. const patient_dob = Form.useWatch('patient_dob', form);
  34. const productName = useSelector(
  35. (state: RootState) => state.product.productName
  36. );
  37. const intl = useIntl();
  38. // 动态获取性别选项
  39. const genderOptions = useMemo(() => {
  40. return getGenderOptions(productName);
  41. }, [productName]);
  42. // 根据年龄计算出生日期
  43. const calculateDobFromAge = (age: {
  44. number: number;
  45. unit: 'D' | 'M' | 'Y';
  46. }): Dayjs => {
  47. const now = dayjs();
  48. switch (age.unit) {
  49. case 'D':
  50. return now.subtract(age.number, 'day');
  51. case 'M':
  52. return now.subtract(age.number, 'month');
  53. case 'Y':
  54. return now.subtract(age.number, 'year');
  55. default:
  56. return now;
  57. }
  58. };
  59. // 根据出生日期计算年龄
  60. const calculateAgeFromDob = (
  61. dob: Dayjs
  62. ): { number: number; unit: 'D' | 'M' | 'Y' } => {
  63. const now = dayjs();
  64. const years = now.diff(dob, 'year');
  65. if (years >= 1) {
  66. return { number: years, unit: 'Y' };
  67. }
  68. const months = now.diff(dob, 'month');
  69. if (months >= 1) {
  70. return { number: months, unit: 'M' };
  71. }
  72. const days = now.diff(dob, 'day');
  73. return { number: Math.max(0, days), unit: 'D' };
  74. };
  75. // 监听年龄变化
  76. useEffect(() => {
  77. // 如果是出生日期联动更新的年龄,跳过
  78. if (updateSourceRef.current === 'dob') {
  79. updateSourceRef.current = null;
  80. return;
  81. }
  82. // 如果年龄有效,计算出生日期
  83. if (patient_age && patient_age.number >= 0 && form) {
  84. updateSourceRef.current = 'age';
  85. const newDob = calculateDobFromAge(patient_age);
  86. form.setFieldsValue({ patient_dob: newDob });
  87. console.log('年龄变化,更新出生日期:', newDob.format('YYYY-MM-DD'));
  88. }
  89. }, [patient_age, form]);
  90. // 监听出生日期变化
  91. useEffect(() => {
  92. // 如果是年龄联动更新的出生日期,跳过
  93. if (updateSourceRef.current === 'age') {
  94. updateSourceRef.current = null;
  95. return;
  96. }
  97. // 如果出生日期有效,计算年龄
  98. if (patient_dob && dayjs.isDayjs(patient_dob) && form) {
  99. updateSourceRef.current = 'dob';
  100. const newAge = calculateAgeFromDob(patient_dob);
  101. form.setFieldsValue({ patient_age: newAge });
  102. console.log('出生日期变化,更新年龄:', newAge);
  103. }
  104. }, [patient_dob, form]);
  105. return (
  106. <Form
  107. form={form}
  108. layout="vertical"
  109. style={style}
  110. className="px-2"
  111. initialValues={
  112. {
  113. // patient_dob: dayjs(),
  114. }
  115. }
  116. onValuesChange={onValuesChange}
  117. >
  118. {/** 宠物专用 */}
  119. {productName === 'VETDROS' && (
  120. <Form.Item
  121. label={
  122. <FormattedMessage
  123. id="register.owner_name"
  124. defaultMessage="register.owner_name"
  125. />
  126. }
  127. name="owner_name"
  128. required={registerFormFields.owner_name.required}
  129. validateTrigger={registerFormFields.owner_name.trigger}
  130. rules={registerFormFields.owner_name.validation}
  131. >
  132. <Input
  133. placeholder={intl.formatMessage({
  134. id: 'register.owner_name.placeholder',
  135. defaultMessage: 'register.owner_name.placeholder',
  136. })}
  137. />
  138. </Form.Item>
  139. )}
  140. <Form.Item
  141. label={
  142. <FormattedMessage
  143. id={productName === 'VETDROS' ? 'animal.register.patientName' : 'register.patientName'}
  144. defaultMessage={productName === 'VETDROS' ? 'animal.register.patientName' : 'register.patientName'}
  145. />
  146. }
  147. name="patient_name"
  148. required={registerFormFields.patient_name.required}
  149. validateTrigger={registerFormFields.patient_name.trigger}
  150. rules={registerFormFields.patient_name.validation}
  151. >
  152. <Input
  153. placeholder={intl.formatMessage({
  154. id: productName === 'VETDROS' ? 'animal.register.patientName.placeholder' : 'register.patientName.placeholder',
  155. defaultMessage: productName === 'VETDROS' ? 'animal.register.patientName.placeholder' : 'register.patientName.placeholder',
  156. })}
  157. />
  158. </Form.Item>
  159. <Form.Item
  160. label={
  161. <FormattedMessage
  162. id="register.accessionNumber"
  163. defaultMessage="register.accessionNumber"
  164. />
  165. }
  166. name="accession_number"
  167. required={registerFormFields.accession_number.required}
  168. validateTrigger={registerFormFields.accession_number.trigger}
  169. rules={registerFormFields.accession_number.validation}
  170. >
  171. <Input
  172. placeholder={intl.formatMessage({
  173. id: 'register.accessionNumber.placeholder',
  174. defaultMessage: 'register.accessionNumber.placeholder',
  175. })}
  176. />
  177. </Form.Item>
  178. <Form.Item
  179. label={
  180. <FormattedMessage
  181. id={productName === 'VETDROS' ? 'animal.register.patientId' : 'register.patientId'}
  182. defaultMessage={productName === 'VETDROS' ? 'animal.register.patientId' : 'register.patientId'}
  183. />
  184. }
  185. name="patient_id"
  186. required={registerFormFields.patient_id.required}
  187. validateTrigger={registerFormFields.patient_id.trigger}
  188. rules={registerFormFields.patient_id.validation}
  189. >
  190. <Input
  191. placeholder={intl.formatMessage({
  192. id: productName === 'VETDROS' ? 'animal.register.patientId.placeholder' : 'register.patientId.placeholder',
  193. defaultMessage: productName === 'VETDROS' ? 'animal.register.patientId.placeholder' : 'register.patientId.placeholder',
  194. })}
  195. />
  196. </Form.Item>
  197. <Form.Item
  198. label={
  199. <FormattedMessage
  200. id={productName === 'VETDROS' ? 'animal.register.patientSize' : 'register.patientSize'}
  201. defaultMessage={productName === 'VETDROS' ? 'animal.register.patientSize' : 'register.patientSize'}
  202. />
  203. }
  204. name="patient_size"
  205. required={registerFormFields.patient_size.required}
  206. validateTrigger={registerFormFields.patient_size.trigger}
  207. rules={registerFormFields.patient_size.validation}
  208. initialValue="Medium"
  209. >
  210. <Select
  211. placeholder={intl.formatMessage({
  212. id: productName === 'VETDROS' ? 'animal.register.patientSize.placeholder' : 'register.patientSize.placeholder',
  213. defaultMessage: productName === 'VETDROS' ? 'animal.register.patientSize.placeholder' : 'register.patientSize.placeholder',
  214. })}
  215. defaultValue="Medium"
  216. >
  217. <Select.Option value="Large">
  218. {intl.formatMessage({ id: 'Large', defaultMessage: 'Large' })}
  219. </Select.Option>
  220. <Select.Option value="Medium">
  221. {intl.formatMessage({ id: 'Medium', defaultMessage: 'Medium' })}
  222. </Select.Option>
  223. <Select.Option value="Small">
  224. {intl.formatMessage({ id: 'Small', defaultMessage: 'Small' })}
  225. </Select.Option>
  226. </Select>
  227. </Form.Item>
  228. <Form.Item
  229. label={
  230. <FormattedMessage id="register.age" defaultMessage="register.age" />
  231. }
  232. name="patient_age"
  233. required={registerFormFields.patient_age.required}
  234. validateTrigger={registerFormFields.patient_age.trigger}
  235. rules={registerFormFields.patient_age.validation}
  236. initialValue={{ number: 0, unit: 'Y' }}
  237. >
  238. <NumberWithUnit
  239. align="baseline"
  240. defaultUnit="Y"
  241. defaultNumber={0}
  242. unitClassName="w-full"
  243. numberClassName="w-full"
  244. className="w-full"
  245. options={[
  246. { label: '天', value: 'D' },
  247. { label: '月', value: 'M' },
  248. { label: '年', value: 'Y' },
  249. ]}
  250. />
  251. </Form.Item>
  252. <Form.Item
  253. label={
  254. <FormattedMessage
  255. id="register.dateOfBirth"
  256. defaultMessage="register.dateOfBirth"
  257. />
  258. }
  259. name="patient_dob"
  260. required={registerFormFields.patient_dob.required}
  261. validateTrigger={registerFormFields.patient_dob.trigger}
  262. rules={registerFormFields.patient_dob.validation}
  263. >
  264. <DatePicker
  265. format="YYYY-MM-DD"
  266. style={{ width: '100%' }}
  267. disabledDate={(current) => current && current > dayjs().endOf('day')}
  268. />
  269. </Form.Item>
  270. <Form.Item
  271. label={
  272. <FormattedMessage
  273. id="register.gender"
  274. defaultMessage="register.gender"
  275. />
  276. }
  277. name="patient_sex"
  278. required={registerFormFields.patient_sex.required}
  279. validateTrigger={registerFormFields.patient_sex.trigger}
  280. rules={registerFormFields.patient_sex.validation}
  281. >
  282. <Select options={genderOptions} />
  283. </Form.Item>
  284. {/** 宠物专用 */}
  285. {productName === 'VETDROS' && (
  286. <Form.Item
  287. label={
  288. <FormattedMessage
  289. id="register.sexNeutered"
  290. defaultMessage="register.sexNeutered"
  291. />
  292. }
  293. name="sex_neutered"
  294. required={registerFormFields.sex_neutered.required}
  295. validateTrigger={registerFormFields.sex_neutered.trigger}
  296. rules={registerFormFields.sex_neutered.validation}
  297. >
  298. <Select
  299. defaultValue={'UNALTERED'}
  300. placeholder={intl.formatMessage({
  301. id: 'register.sexNeutered.placeholder',
  302. defaultMessage: 'register.sexNeutered.placeholder',
  303. })}
  304. >
  305. <Select.Option value="ALTERED">
  306. {intl.formatMessage({ id: 'register.sexNeutered.altered', defaultMessage: 'ALTERED' })}
  307. </Select.Option>
  308. <Select.Option value="UNALTERED">
  309. {intl.formatMessage({ id: 'register.sexNeutered.unaltered', defaultMessage: 'UNALTERED' })}
  310. </Select.Option>
  311. </Select>
  312. </Form.Item>
  313. )}
  314. {/** 人类专用 */}
  315. {productName === 'DROS' && (
  316. <Form.Item
  317. label={
  318. <FormattedMessage
  319. id="register.pregnancyStatus"
  320. defaultMessage="register.pregnancyStatus"
  321. />
  322. }
  323. name="pregnancy_status"
  324. required={registerFormFields.pregnancy_status.required}
  325. validateTrigger={registerFormFields.pregnancy_status.trigger}
  326. rules={registerFormFields.pregnancy_status.validation}
  327. initialValue={PregnancyStatus.UNKNOWN}
  328. >
  329. <Radio.Group
  330. options={pregnancyStatusOptions}
  331. optionType="button"
  332. buttonStyle="solid"
  333. />
  334. </Form.Item>
  335. )}
  336. {/** 宠物专用 */}
  337. {productName === 'VETDROS' && (
  338. <Form.Item
  339. label={
  340. <FormattedMessage
  341. id="register.chipNumber"
  342. defaultMessage="register.chipNumber"
  343. />
  344. }
  345. name="chip_number"
  346. required={registerFormFields.chip_number.required}
  347. validateTrigger={registerFormFields.chip_number.trigger}
  348. rules={registerFormFields.chip_number.validation}
  349. >
  350. <Input
  351. placeholder={intl.formatMessage({
  352. id: 'register.chipNumber.placeholder',
  353. defaultMessage: 'register.chipNumber.placeholder',
  354. })}
  355. />
  356. </Form.Item>
  357. )}
  358. {/** 宠物专用 */}
  359. {productName === 'VETDROS' && (
  360. <Form.Item
  361. label={
  362. <FormattedMessage
  363. id="register.variety"
  364. defaultMessage="register.variety"
  365. />
  366. }
  367. name="variety"
  368. required={registerFormFields.variety.required}
  369. validateTrigger={registerFormFields.variety.trigger}
  370. rules={registerFormFields.variety.validation}
  371. >
  372. <Input
  373. placeholder={intl.formatMessage({
  374. id: 'register.variety.placeholder',
  375. defaultMessage: 'register.variety.placeholder',
  376. })}
  377. />
  378. </Form.Item>
  379. )}
  380. {/* <Form.Item
  381. label={
  382. <FormattedMessage
  383. id="register.patientType"
  384. defaultMessage="register.patientType"
  385. />
  386. }
  387. name="patient_type"
  388. required={registerFormFields.patient_type.required}
  389. validateTrigger={registerFormFields.patient_type.trigger}
  390. rules={registerFormFields.patient_type.validation}
  391. >
  392. <Input
  393. placeholder={intl.formatMessage({
  394. id: 'register.patientType.placeholder',
  395. defaultMessage: 'register.patientType.placeholder',
  396. })}
  397. />
  398. </Form.Item> */}
  399. <Form.Item
  400. label={
  401. <FormattedMessage
  402. id="register.referringPhysician"
  403. defaultMessage="register.referringPhysician"
  404. />
  405. }
  406. name="ref_physician"
  407. required={registerFormFields.ref_physician.required}
  408. validateTrigger={registerFormFields.ref_physician.trigger}
  409. rules={registerFormFields.ref_physician.validation}
  410. >
  411. <Input
  412. placeholder={intl.formatMessage({
  413. id: 'register.referringPhysician.placeholder',
  414. defaultMessage: 'register.referringPhysician.placeholder',
  415. })}
  416. />
  417. </Form.Item>
  418. <Form.Item
  419. label={
  420. <FormattedMessage
  421. id="register.operatorId"
  422. defaultMessage="register.operatorId"
  423. />
  424. }
  425. name="operator_id"
  426. required={registerFormFields.operator_id.required}
  427. validateTrigger={registerFormFields.operator_id.trigger}
  428. rules={registerFormFields.operator_id.validation}
  429. >
  430. <Input
  431. placeholder={intl.formatMessage({
  432. id: 'register.operatorId.placeholder',
  433. defaultMessage: 'register.operatorId.placeholder',
  434. })}
  435. />
  436. </Form.Item>
  437. <Form.Item
  438. label={
  439. <FormattedMessage
  440. id="register.weight"
  441. defaultMessage="register.weight"
  442. />
  443. }
  444. name="weight"
  445. required={registerFormFields.weight.required}
  446. validateTrigger={registerFormFields.weight.trigger}
  447. rules={registerFormFields.weight.validation}
  448. >
  449. <InputNumber min={0} addonAfter="kg" style={{ width: '100%' }} />
  450. </Form.Item>
  451. <Form.Item
  452. label={
  453. <FormattedMessage
  454. id="register.thickness"
  455. defaultMessage="register.thickness"
  456. />
  457. }
  458. name="thickness"
  459. required={registerFormFields.thickness.required}
  460. validateTrigger={registerFormFields.thickness.trigger}
  461. rules={registerFormFields.thickness.validation}
  462. >
  463. <InputNumber min={0} addonAfter="cm" style={{ width: '100%' }} />
  464. </Form.Item>
  465. <Form.Item
  466. label={
  467. <FormattedMessage
  468. id="register.height"
  469. defaultMessage="register.height"
  470. />
  471. }
  472. name="length"
  473. required={registerFormFields.length.required}
  474. validateTrigger={registerFormFields.length.trigger}
  475. rules={registerFormFields.length.validation}
  476. >
  477. <InputNumber min={0} addonAfter="cm" style={{ width: '100%' }} />
  478. </Form.Item>
  479. <Form.Item
  480. label={
  481. <FormattedMessage
  482. id="register.comment"
  483. defaultMessage="register.comment"
  484. />
  485. }
  486. name="comment"
  487. required={registerFormFields.comment.required}
  488. validateTrigger={registerFormFields.comment.trigger}
  489. rules={registerFormFields.comment.validation}
  490. >
  491. <Input
  492. placeholder={intl.formatMessage({
  493. id: 'register.comment.placeholder',
  494. defaultMessage: 'register.comment.placeholder',
  495. })}
  496. />
  497. </Form.Item>
  498. </Form>
  499. );
  500. };
  501. export default BasicInfoForm;