diff --git a/src/api/his.ts b/src/api/his.ts index e0d294c..b448410 100644 --- a/src/api/his.ts +++ b/src/api/his.ts @@ -110,9 +110,17 @@ export const getCustomerDetail = ( export const signInMedicalExamCenter = ( data: InputMedicalExamCenterSignIn ): Promise => { + const formData = new FormData(); + formData.append('id_no_pic', data.id_no_pic); + return request.post( `${MEDICAL_EXAM_BASE_PATH}/sign-in`, - data + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } ).then(res => res.data); }; diff --git a/src/api/types.ts b/src/api/types.ts index cf54970..e4bb2cb 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -255,8 +255,8 @@ export type CustomerDetailResponse = CommonActionResult; * 体检中心签到入参 */ export interface InputMedicalExamCenterSignIn { - /** 身份证号 */ - id_no: string; + /** 身份证件照片(格式:jpg) */ + id_no_pic: File | Blob; } /** diff --git a/src/components/exam/ExamModal.tsx b/src/components/exam/ExamModal.tsx index a8489e4..12c6a04 100644 --- a/src/components/exam/ExamModal.tsx +++ b/src/components/exam/ExamModal.tsx @@ -178,8 +178,9 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps) }; const ExamSignPanel = ({ examId }: { examId?: number }) => { - const [idNo, setIdNo] = useState(''); - const [ocrLoading, setOcrLoading] = useState(false); + const [idCardFile, setIdCardFile] = useState(null); + const [previewImage, setPreviewImage] = useState(null); + const [showImagePreview, setShowImagePreview] = useState(false); const [signLoading, setSignLoading] = useState(false); const [message, setMessage] = useState(null); const fileInputRef = useRef(null); @@ -214,40 +215,78 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { } }, []); - const mockOcr = async (file: File) => { - // 简单模拟 OCR:提取文件名中的数字,或返回示例身份证 - const match = file.name.match(/\d{6,18}/); - await new Promise((r) => setTimeout(r, 600)); - return match?.[0] || '440101199001010010'; + const convertToJpg = async (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (event) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('无法创建画布上下文')); + return; + } + ctx.drawImage(img, 0, 0); + canvas.toBlob( + (blob) => { + if (!blob) { + reject(new Error('图片转换失败')); + return; + } + const jpgFile = new File([blob], 'id_card.jpg', { type: 'image/jpeg' }); + resolve(jpgFile); + }, + 'image/jpeg', + 0.92 // 质量 92% + ); + }; + img.onerror = () => reject(new Error('图片加载失败')); + img.src = event.target?.result as string; + }; + reader.onerror = () => reject(new Error('文件读取失败')); + reader.readAsDataURL(file); + }); }; const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; - setOcrLoading(true); + setMessage(null); + try { - const ocrId = await mockOcr(file); - setIdNo(ocrId); + // 转换为 JPG 格式 + const jpgFile = await convertToJpg(file); + + // 保存文件用于签到 + setIdCardFile(jpgFile); + + // 生成预览图 + const reader = new FileReader(); + reader.onload = (event) => { + setPreviewImage(event.target?.result as string); + }; + reader.readAsDataURL(jpgFile); } catch (err) { - console.error(err); - setMessage('OCR 识别失败,请重试或手动输入'); - } finally { - setOcrLoading(false); - e.target.value = ''; + console.error('图片转换失败', err); + setMessage('图片处理失败,请重试'); } + + e.target.value = ''; }; const handleSign = async () => { - const trimmed = idNo.trim(); - if (!trimmed) { - setMessage('请输入身份证号'); + if (!idCardFile) { + setMessage('请先上传身份证照片'); return; } setSignLoading(true); setMessage(null); try { - const res = await signInMedicalExamCenter({ id_no: trimmed }); + const res = await signInMedicalExamCenter({ id_no_pic: idCardFile }); const ok = res.Status === 200 && res.Data?.is_success === 0; setMessage(ok ? '签到成功' : res.Message || '签到失败'); } catch (err) { @@ -339,38 +378,72 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { return (
-
身份证扫描与签到
+
身份证拍照与签到
- 支持扫描身份证识别证号,确认后再点击签到。 + 拍照身份证后点击签到按钮完成签到。
- - setIdNo(e.target.value)} - className='flex-1' - /> + {previewImage && ( +
setShowImagePreview(true)} + > + 身份证预览 +
+ )}
- {message &&
{message}
} -
如识别不准,可手动修改后再签到。
+ {message && ( +
+ {message} +
+ )}
+ {showImagePreview && previewImage && ( +
setShowImagePreview(false)} + > +
+ 身份证预览 + +
+
+ )}
体检知情同意书
点击后弹出知情同意书全文及签名区域,签署完成后回到签到页面。