完善签名功能
This commit is contained in:
BIN
public/sign.png
Normal file
BIN
public/sign.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -8,7 +8,7 @@ import type {
|
|||||||
OutputTongyishuFileInfo,
|
OutputTongyishuFileInfo,
|
||||||
PhysicalExamProgressItem,
|
PhysicalExamProgressItem,
|
||||||
} from '../../api';
|
} from '../../api';
|
||||||
import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter, getTongyishuPdf } from '../../api';
|
import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter, getTongyishuPdf, submitTongyishuSign } from '../../api';
|
||||||
import type { SignaturePadHandle } from '../ui';
|
import type { SignaturePadHandle } from '../ui';
|
||||||
import { Button, Input, SignaturePad } from '../ui';
|
import { Button, Input, SignaturePad } from '../ui';
|
||||||
|
|
||||||
@@ -189,11 +189,31 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => {
|
|||||||
const [previewPdf, setPreviewPdf] = useState<OutputTongyishuFileInfo | null>(null);
|
const [previewPdf, setPreviewPdf] = useState<OutputTongyishuFileInfo | null>(null);
|
||||||
const [showSignature, setShowSignature] = useState(false);
|
const [showSignature, setShowSignature] = useState(false);
|
||||||
const signaturePadRef = useRef<SignaturePadHandle | null>(null);
|
const signaturePadRef = useRef<SignaturePadHandle | null>(null);
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
|
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
||||||
|
const [signedCombinationCodes, setSignedCombinationCodes] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const SIGN_STORAGE_KEY = 'yh_signed_consents';
|
||||||
|
|
||||||
const handlePickFile = () => {
|
const handlePickFile = () => {
|
||||||
fileInputRef.current?.click();
|
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) => {
|
const mockOcr = async (file: File) => {
|
||||||
// 简单模拟 OCR:提取文件名中的数字,或返回示例身份证
|
// 简单模拟 OCR:提取文件名中的数字,或返回示例身份证
|
||||||
const match = file.name.match(/\d{6,18}/);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!examId) {
|
if (!examId) {
|
||||||
setConsentList([]);
|
setConsentList([]);
|
||||||
@@ -311,7 +386,18 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => {
|
|||||||
key={item.pdf_url || item.pdf_name}
|
key={item.pdf_url || item.pdf_name}
|
||||||
className='flex items-center justify-between gap-3 p-3 rounded-xl border bg-white shadow-sm'
|
className='flex items-center justify-between gap-3 p-3 rounded-xl border bg-white shadow-sm'
|
||||||
>
|
>
|
||||||
<div className='text-sm text-gray-800 truncate'>{item.pdf_name}</div>
|
<div className='text-sm text-gray-800 truncate flex items-center gap-2 relative pr-20'>
|
||||||
|
<span className='truncate'>{item.pdf_name}</span>
|
||||||
|
{item.combination_code !== undefined &&
|
||||||
|
signedCombinationCodes.includes(Number(item.combination_code)) && (
|
||||||
|
<img
|
||||||
|
src='/sign.png'
|
||||||
|
alt='已签名'
|
||||||
|
className='w-16 h-16 absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none select-none'
|
||||||
|
loading='lazy'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<Button className='py-1.5 px-3' onClick={() => setPreviewPdf(item)}>
|
<Button className='py-1.5 px-3' onClick={() => setPreviewPdf(item)}>
|
||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
@@ -372,6 +458,32 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{submitMessage && (
|
||||||
|
<div className={`text-sm text-center ${submitMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'
|
||||||
|
}`}>
|
||||||
|
{submitMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className='flex items-center justify-end gap-3'>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6'
|
||||||
|
onClick={() => {
|
||||||
|
setShowSignature(false);
|
||||||
|
setSubmitMessage(null);
|
||||||
|
signaturePadRef.current?.clear();
|
||||||
|
}}
|
||||||
|
disabled={submitLoading}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6 bg-blue-600 text-white hover:bg-blue-700'
|
||||||
|
onClick={handleSubmitSign}
|
||||||
|
disabled={submitLoading}
|
||||||
|
>
|
||||||
|
{submitLoading ? '提交中...' : '提交签名'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user