import React, { useEffect, useRef, useMemo, useState } from 'react'; import { Form, Input, DatePicker, InputNumber, Select, Radio, FormInstance, message, } from 'antd'; import { useIntl, FormattedMessage } from 'react-intl'; import { registerFormFields } from '@/validation/patient/registerSchema'; import NumberWithUnit from '@/components/NumberWithUnit'; import dayjs, { Dayjs } from 'dayjs'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '@/states/store'; import { setFormData } from '@/states/patient/register/formSlice'; import { PregnancyStatus, pregnancyStatusOptions, } from '@/domain/patient/pregnancyStatus'; import { getGenderOptions } from '@/domain/patient/genderOptions'; import { processQRCodeData, transformToFormData } from '@/domain/qrcode/qrCodeDataProcessor'; interface BasicInfoFormProps { style?: React.CSSProperties; form?: FormInstance; // eslint-disable-next-line @typescript-eslint/no-explicit-any onValuesChange?: (changedValues: any, allValues: any) => void; } const BasicInfoForm: React.FC = ({ style, form, onValuesChange, }) => { // 使用 ref 记录更新源,防止循环触发 const updateSourceRef = useRef<'age' | 'dob' | null>(null); const patient_age = Form.useWatch('patient_age', form); const patient_dob = Form.useWatch('patient_dob', form); const productName = useSelector( (state: RootState) => state.product.productName ); const formData = useSelector((state: RootState) => state.form.formData); const dispatch = useDispatch(); const [pregnancyStatusFieldVisible, setPregnancyStatusFieldVisible] = useState(false); const initializedRef = useRef(false); // 扫码枪扫码相关状态 const scanInputRef = useRef(null); const [isComposing, setIsComposing] = useState(false); const bufferRef = useRef(''); const timerRef = useRef(null); // 同步初始表单值到 Redux store,并生成登记号 useEffect(() => { if (!initializedRef.current) { // 生成登记号(如果不存在) let updatedFormData = { ...formData }; if (!updatedFormData.accession_number) { const now = dayjs(); updatedFormData.accession_number = now.format('YYYYMMDDHHmmss'); } const allValues = form?.getFieldsValue(); if (allValues && Object.keys(allValues).length > 0) { const merged = { ...allValues ,...updatedFormData}; dispatch(setFormData(merged)); form?.setFieldsValue(merged) } initializedRef.current = true; } }, [form, dispatch, formData]); const intl = useIntl(); // 动态获取性别选项 const genderOptions = useMemo(() => { return getGenderOptions(productName); }, [productName]); // 根据年龄计算出生日期 const calculateDobFromAge = (age: { number: number; unit: 'D' | 'M' | 'Y'; }): Dayjs => { const now = dayjs(); switch (age.unit) { case 'D': return now.subtract(age.number, 'day'); case 'M': return now.subtract(age.number, 'month'); case 'Y': return now.subtract(age.number, 'year'); default: return now; } }; // 根据出生日期计算年龄 const calculateAgeFromDob = ( dob: Dayjs ): { number: number; unit: 'D' | 'M' | 'Y' } => { const now = dayjs(); const years = now.diff(dob, 'year'); if (years >= 1) { return { number: years, unit: 'Y' }; } const months = now.diff(dob, 'month'); if (months >= 1) { return { number: months, unit: 'M' }; } const days = now.diff(dob, 'day'); return { number: Math.max(0, days), unit: 'D' }; }; // 监听年龄变化 useEffect(() => { // 如果是出生日期联动更新的年龄,跳过 if (updateSourceRef.current === 'dob') { updateSourceRef.current = null; return; } // 如果年龄有效,计算出生日期 if (patient_age && patient_age.number >= 0 && form) { updateSourceRef.current = 'age'; const newDob = calculateDobFromAge(patient_age); form.setFieldsValue({ patient_dob: newDob }); //人工同步更新 Redux store dispatch(setFormData(form.getFieldsValue())) console.log('年龄变化,更新出生日期:', newDob.format('YYYY-MM-DD')); } }, [patient_age, form]); // 监听出生日期变化 useEffect(() => { // 如果是年龄联动更新的出生日期,跳过 if (updateSourceRef.current === 'age') { updateSourceRef.current = null; return; } // 如果出生日期有效,计算年龄 if (patient_dob && dayjs.isDayjs(patient_dob) && form) { updateSourceRef.current = 'dob'; const newAge = calculateAgeFromDob(patient_dob); form.setFieldsValue({ patient_age: newAge }); console.log('出生日期变化,更新年龄:', newAge); } }, [patient_dob, form]); // 处理扫码枪扫码后的数据:解析文本并自动填充到注册表单 const processScannedData = React.useCallback(async (scannedText: string) => { try { // 添加调试日志,输出实际接收到的扫码文本 console.log('[扫码枪调试] 得到的扫码数据是:', JSON.stringify(scannedText)); console.log('[扫码枪调试] 扫码数据长度:', scannedText.length); // 使用二维码数据处理器解析扫码文本(复用现有逻辑) const result = processQRCodeData(scannedText); if (!result.success) { message.error(result.error || '扫码枪数据处理失败'); return; } // 将解析后的数据转换为表单格式 const formData = transformToFormData(result.data!); // 自动填充表单 form?.setFieldsValue(formData); // 同步更新 Redux store dispatch(setFormData(formData)); // 显示成功提示 message.success('扫码枪数据已自动填充到表单'); } catch (error) { console.error('扫码枪数据处理失败:', error); message.error('扫码枪数据处理失败'); } }, [form, dispatch]); // 扫码枪扫码监听器:页面级 keydown 监听,自动切换焦点到隐藏input useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const target = e.target as HTMLElement; const isEditable = target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable); // 如果当前没有可编辑元素获得焦点,认为是扫码枪输入 if (!isEditable) { scanInputRef.current?.focus(); } // 扫码枪通常以 Enter 结束 if (e.key === 'Enter') { if (bufferRef.current) { processScannedData(bufferRef.current); bufferRef.current = ''; if (scanInputRef.current) { scanInputRef.current.value = ''; } } // 阻止Enter键的默认行为(如表单提交) e.preventDefault(); e.stopPropagation(); } else if (e.key === 'Tab') { // 完全控制Tab键行为,防止焦点切换 e.preventDefault(); e.stopPropagation(); // 手动将Tab字符插入到隐藏input中 const input = scanInputRef.current; if (input) { const start = input.selectionStart || 0; const end = input.selectionEnd || 0; const value = input.value; // 在光标位置插入Tab字符 input.value = value.substring(0, start) + '\t' + value.substring(end); // 更新光标位置 input.selectionStart = input.selectionEnd = start + 1; // 手动触发input事件,确保React能检测到变化 input.dispatchEvent(new Event('input', { bubbles: true })); } } // 其他字符不阻止,让它们自然输入到隐藏input中 // input会自动过滤掉无效字符,只保留有效的文本 }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [processScannedData]); /** input 事件:真正可靠地拿中文 */ const handleScanInput = (e: React.ChangeEvent) => { if (isComposing) return; bufferRef.current = e.target.value; // 防止没有 Enter 的扫码枪(用时间判断) if (timerRef.current) { clearTimeout(timerRef.current); } timerRef.current = window.setTimeout(() => { if (bufferRef.current) { processScannedData(bufferRef.current); bufferRef.current = ''; e.target.value = ''; } }, 300); // 300ms 内无新输入,认为扫码结束 }; return (
{/** 宠物专用 */} {productName === 'VETDROS' && ( } name="owner_name" required={registerFormFields.owner_name.required} validateTrigger={registerFormFields.owner_name.trigger} rules={registerFormFields.owner_name.validation} > )} } name="patient_name" required={registerFormFields.patient_name.required} validateTrigger={registerFormFields.patient_name.trigger} rules={registerFormFields.patient_name.validation} > } name="accession_number" required={registerFormFields.accession_number.required} validateTrigger={registerFormFields.accession_number.trigger} rules={registerFormFields.accession_number.validation} > } name="patient_id" required={registerFormFields.patient_id.required} validateTrigger={registerFormFields.patient_id.trigger} rules={registerFormFields.patient_id.validation} > } name="patient_size" required={registerFormFields.patient_size.required} validateTrigger={registerFormFields.patient_size.trigger} rules={registerFormFields.patient_size.validation} initialValue="Medium" > } name="patient_age" required={registerFormFields.patient_age.required} validateTrigger={registerFormFields.patient_age.trigger} rules={registerFormFields.patient_age.validation} initialValue={{ number: 0, unit: 'Y' }} > } name="patient_dob" required={registerFormFields.patient_dob.required} validateTrigger={registerFormFields.patient_dob.trigger} rules={registerFormFields.patient_dob.validation} initialValue={dayjs()} > current && current > dayjs().endOf('day')} /> } name="patient_sex" required={registerFormFields.patient_sex.required} validateTrigger={registerFormFields.patient_sex.trigger} rules={registerFormFields.patient_sex.validation} > {intl.formatMessage({ id: 'register.sexNeutered.altered', defaultMessage: 'ALTERED', })} {intl.formatMessage({ id: 'register.sexNeutered.unaltered', defaultMessage: 'UNALTERED', })} )} {/** 人类专用 */} {productName === 'DROS' && form?.getFieldValue('patient_sex') && ( )} {/** 宠物专用 */} {productName === 'VETDROS' && ( } name="chip_number" required={registerFormFields.chip_number.required} validateTrigger={registerFormFields.chip_number.trigger} rules={registerFormFields.chip_number.validation} > )} {/** 宠物专用 */} {productName === 'VETDROS' && ( } name="variety" required={registerFormFields.variety.required} validateTrigger={registerFormFields.variety.trigger} rules={registerFormFields.variety.validation} > )} {/* } name="patient_type" required={registerFormFields.patient_type.required} validateTrigger={registerFormFields.patient_type.trigger} rules={registerFormFields.patient_type.validation} > */} } name="ref_physician" required={registerFormFields.ref_physician.required} validateTrigger={registerFormFields.ref_physician.trigger} rules={registerFormFields.ref_physician.validation} > } name="operator_id" required={registerFormFields.operator_id.required} validateTrigger={registerFormFields.operator_id.trigger} rules={registerFormFields.operator_id.validation} > } name="weight" required={registerFormFields.weight.required} validateTrigger={registerFormFields.weight.trigger} rules={registerFormFields.weight.validation} > } name="thickness" required={registerFormFields.thickness.required} validateTrigger={registerFormFields.thickness.trigger} rules={registerFormFields.thickness.validation} > } name="length" required={registerFormFields.length.required} validateTrigger={registerFormFields.length.trigger} rules={registerFormFields.length.validation} > } name="comment" required={registerFormFields.comment.required} validateTrigger={registerFormFields.comment.trigger} rules={registerFormFields.comment.validation} > {/* 扫码枪兜底 input(隐藏) */} setIsComposing(true)} onCompositionEnd={(e) => { setIsComposing(false); bufferRef.current = e.currentTarget.value; }} onInput={handleScanInput} autoComplete="off" />
); }; export default BasicInfoForm;