diff --git a/public/sign.png b/public/sign.png new file mode 100644 index 0000000..13afac6 Binary files /dev/null and b/public/sign.png differ diff --git a/src/components/exam/ExamModal.tsx b/src/components/exam/ExamModal.tsx index 256c34e..a8489e4 100644 --- a/src/components/exam/ExamModal.tsx +++ b/src/components/exam/ExamModal.tsx @@ -8,7 +8,7 @@ import type { OutputTongyishuFileInfo, PhysicalExamProgressItem, } from '../../api'; -import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter, getTongyishuPdf } from '../../api'; +import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter, getTongyishuPdf, submitTongyishuSign } from '../../api'; import type { SignaturePadHandle } from '../ui'; import { Button, Input, SignaturePad } from '../ui'; @@ -189,11 +189,31 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { const [previewPdf, setPreviewPdf] = useState(null); const [showSignature, setShowSignature] = useState(false); const signaturePadRef = useRef(null); + const [submitLoading, setSubmitLoading] = useState(false); + const [submitMessage, setSubmitMessage] = useState(null); + const [signedCombinationCodes, setSignedCombinationCodes] = useState([]); + + const SIGN_STORAGE_KEY = 'yh_signed_consents'; const handlePickFile = () => { fileInputRef.current?.click(); }; + useEffect(() => { + if (typeof window === 'undefined') return; + const raw = localStorage.getItem(SIGN_STORAGE_KEY); + if (raw) { + try { + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) { + setSignedCombinationCodes(parsed.filter((x) => typeof x === 'number')); + } + } catch (err) { + console.warn('签名记录解析失败', err); + } + } + }, []); + const mockOcr = async (file: File) => { // 简单模拟 OCR:提取文件名中的数字,或返回示例身份证 const match = file.name.match(/\d{6,18}/); @@ -238,6 +258,61 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { } }; + const handleSubmitSign = async () => { + if (!examId || !previewPdf?.combination_code) { + setSubmitMessage('缺少必要信息,无法提交签名'); + return; + } + + const dataUrl = signaturePadRef.current?.toDataURL('image/png'); + if (!dataUrl) { + setSubmitMessage('请先完成签名'); + return; + } + + setSubmitLoading(true); + setSubmitMessage(null); + try { + // 将 base64 转换为 Blob + const blob = await fetch(dataUrl).then(r => r.blob()); + + // 提交签名 + const res = await submitTongyishuSign({ + exam_id: examId, + combination_code: previewPdf.combination_code, + sign_file: blob, + }); + + if (res.Status === 200) { + setSubmitMessage('签名提交成功'); + // 记录已签名的组合代码 + setSignedCombinationCodes((prev) => { + const code = Number(previewPdf.combination_code); + if (!Number.isFinite(code)) return prev || []; + const next = Array.from(new Set([...(prev || []), code])); + if (typeof window !== 'undefined') { + localStorage.setItem(SIGN_STORAGE_KEY, JSON.stringify(next)); + } + return next; + }); + // 2秒后关闭弹窗 + setTimeout(() => { + setShowSignature(false); + setPreviewPdf(null); + setSubmitMessage(null); + signaturePadRef.current?.clear(); + }, 2000); + } else { + setSubmitMessage(res.Message || '签名提交失败'); + } + } catch (err) { + console.error('提交签名失败', err); + setSubmitMessage('签名提交失败,请稍后重试'); + } finally { + setSubmitLoading(false); + } + }; + useEffect(() => { if (!examId) { setConsentList([]); @@ -311,7 +386,18 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { key={item.pdf_url || item.pdf_name} className='flex items-center justify-between gap-3 p-3 rounded-xl border bg-white shadow-sm' > -
{item.pdf_name}
+
+ {item.pdf_name} + {item.combination_code !== undefined && + signedCombinationCodes.includes(Number(item.combination_code)) && ( + 已签名 + )} +
@@ -372,6 +458,32 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => { + {submitMessage && ( +
+ {submitMessage} +
+ )} +
+ + +
)}