保存加项PDF与导检单签名判断

This commit is contained in:
xianyi
2026-01-05 17:16:32 +08:00
parent b41deedd00
commit f8fdebb955
4 changed files with 227 additions and 46 deletions

View File

@@ -3,13 +3,14 @@ import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
import type { OutputTongyishuFileInfo } from '../../api';
import { getTongyishuPdf, signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus, getDaojiandanPdf as getDaojiandanPdfApi } from '../../api';
import { getTongyishuPdf, signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus, getDaojiandanPdf as getDaojiandanPdfApi, getAddItemBillPdf as getAddItemBillPdfApi, submitAddItemBillSign } from '../../api';
import {
setExamActionRecord,
setTongyishuPdfList,
getTongyishuPdfList,
setDaojiandanPdf,
getDaojiandanPdf as getDaojiandanPdfFromStorage,
setAddItemBillPdf,
getAddItemBillPdf as getAddItemBillPdfFromStorage,
type TongyishuPdfInfo,
} from '../../utils/examActions';
@@ -70,9 +71,14 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
// 加项单相关状态
const [addItemBillUrl, setAddItemBillUrl] = useState<string | null>(null);
const [addItemBillName, setAddItemBillName] = useState<string>('加项单');
const [isAddItemBillSigned, setIsAddItemBillSigned] = useState(false); // 加项单是否已签名
const [showAddItemBillPreview, setShowAddItemBillPreview] = useState(false);
const [showAddItemBillSignature, setShowAddItemBillSignature] = useState(false);
const addItemBillSignaturePadRef = useRef<SignaturePadHandle | null>(null);
const [addItemBillSubmitLoading, setAddItemBillSubmitLoading] = useState(false);
const [addItemBillSubmitMessage, setAddItemBillSubmitMessage] = useState<string | null>(null);
const busy = signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading;
const busy = signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading || addItemBillSubmitLoading;
useEffect(() => {
onBusyChange?.(busy);
@@ -303,11 +309,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
useEffect(() => {
if (!examId) return;
// 先检查 localStorage 中的导检单(已签名的)
// 先检查 localStorage 中的导检单
const storedPdf = getDaojiandanPdfFromStorage(examId);
if (storedPdf && storedPdf.pdf_url) {
setDaojiandanUrl(storedPdf.pdf_url);
setIsDaojiandanSigned(true); // localStorage 中的是已签名
// 使用 localStorage 中保存的 is_signed 字段判断是否已签名
setIsDaojiandanSigned(storedPdf.is_signed === true);
} else {
// 如果 localStorage 中没有导检单,调用接口获取未签名的导检单用于查看和签名
const fetchDaojiandan = async () => {
@@ -326,11 +333,31 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
fetchDaojiandan();
}
// 检查加项单PDF
// 检查加项单PDF(逻辑和导检单一样)
const storedAddItemBill = getAddItemBillPdfFromStorage(examId);
if (storedAddItemBill && storedAddItemBill.pdf_url) {
setAddItemBillUrl(storedAddItemBill.pdf_url);
setAddItemBillName(storedAddItemBill.pdf_name || '加项单');
// 使用 localStorage 中保存的 is_signed 字段判断是否已签名
setIsAddItemBillSigned(storedAddItemBill.is_signed === true);
} else {
// 如果 localStorage 中没有加项单,调用接口获取未签名的加项单用于查看和签名
const fetchAddItemBill = async () => {
try {
const res = await getAddItemBillPdfApi({ exam_id: examId });
if (res.Status === 200 && res.Data?.pdf_url) {
const pdfUrlValue = res.Data.pdf_url;
const pdfNameValue = res.Data.pdf_name || '加项单';
// 不保存到 localStorage只用于显示和签名
setAddItemBillUrl(pdfUrlValue);
setAddItemBillName(pdfNameValue);
setIsAddItemBillSigned(false); // 接口获取的是未签名的
}
} catch (err) {
console.error('获取加项单失败', err);
}
};
fetchAddItemBill();
}
}, [examId]);
@@ -620,10 +647,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
setDaojiandanUrl(pdfUrlValue);
setIsDaojiandanSigned(true); // 签名成功后标记为已签名
// 保存导检单PDF信息到localStorage
// 保存导检单PDF信息到localStorage(标记为已签名)
setDaojiandanPdf(examId, {
pdf_name: pdfNameValue,
pdf_url: pdfUrlValue,
is_signed: true,
});
// 记录打印导检单是否签名操作
@@ -653,6 +681,61 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
}
};
// 加项单签名提交
const handleSubmitAddItemBillSign = async () => {
if (!examId) {
setAddItemBillSubmitMessage('缺少必要信息,无法提交签名');
return;
}
const dataUrl = addItemBillSignaturePadRef.current?.toDataURL('image/png');
if (!dataUrl) {
setAddItemBillSubmitMessage('请先完成签名');
return;
}
setAddItemBillSubmitLoading(true);
setAddItemBillSubmitMessage(null);
try {
const blob = await fetch(dataUrl).then((r) => r.blob());
const res = await submitAddItemBillSign({
exam_id: examId,
sign_file: blob,
});
if (res.Status === 200 && res.Data?.pdf_url) {
setAddItemBillSubmitMessage('签名提交成功');
const pdfUrlValue = res.Data.pdf_url;
const pdfNameValue = res.Data.pdf_name || '加项单';
setAddItemBillUrl(pdfUrlValue);
setAddItemBillName(pdfNameValue);
setIsAddItemBillSigned(true); // 签名成功后标记为已签名
// 保存加项单PDF信息到localStorage标记为已签名
setAddItemBillPdf(examId, {
pdf_name: pdfNameValue,
pdf_url: pdfUrlValue,
is_signed: true,
});
setTimeout(() => {
setShowAddItemBillSignature(false);
setAddItemBillSubmitMessage(null);
addItemBillSignaturePadRef.current?.clear();
setShowAddItemBillPreview(true);
}, 2000);
} else {
setAddItemBillSubmitMessage(res.Message || '签名提交失败');
}
} catch (err) {
console.error('提交签名失败', err);
setAddItemBillSubmitMessage('签名提交失败,请稍后重试');
} finally {
setAddItemBillSubmitLoading(false);
}
};
// 加项单直接打印
const handleAddItemBillDirectPrint = async () => {
if (!addItemBillUrl) return;
@@ -1265,34 +1348,65 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
<div className='flex items-center justify-between gap-3 p-2 rounded-xl border bg-white shadow-sm'>
<div className='text-sm text-gray-800 truncate flex items-center gap-2 relative pr-20'>
<span className='truncate'>{addItemBillName.length > 12 ? addItemBillName.slice(0, 12) + "..." : addItemBillName}</span>
<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'
/>
{isAddItemBillSigned && (
<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>
<div className='flex items-center gap-2'>
<Button
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
handleAddItemBillDirectPrint();
}}
disabled={busy}
>
</Button>
<Button
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
setShowAddItemBillPreview(true);
}}
disabled={busy}
>
</Button>
{isAddItemBillSigned ? (
// 已签名:显示打印和查看按钮
<>
<Button
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
handleAddItemBillDirectPrint();
}}
disabled={busy}
>
</Button>
<Button
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
setShowAddItemBillPreview(true);
}}
disabled={busy}
>
</Button>
</>
) : (
// 未签名:显示查看和签名按钮
<>
<Button
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
setShowAddItemBillPreview(true);
}}
disabled={busy}
>
</Button>
<Button
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
setShowAddItemBillSignature(true);
}}
disabled={busy}
>
</Button>
</>
)}
</div>
</div>
)}
@@ -1471,19 +1585,85 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
</div>
)
}
{
showAddItemBillSignature && (
<div className='fixed inset-0 z-[70] bg-black/80 flex items-center justify-center px-6'>
<div className='bg-white rounded-2xl w-full max-w-3xl shadow-2xl p-4 flex flex-col gap-4'>
<div className='flex items-center justify-between'>
<div className='text-base font-semibold text-gray-900'></div>
<div className='flex items-center gap-2'>
<Button className='py-1 px-3' onClick={() => setShowAddItemBillSignature(false)} disabled={busy}>
</Button>
</div>
</div>
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
<SignaturePad
ref={addItemBillSignaturePadRef}
className='ui7-signature-canvas w-full h-72 bg-white touch-none'
/>
<div className='flex items-center justify-between px-3 py-2 bg-gray-50 border-t'>
<div className='text-xs text-gray-500'></div>
<Button className='ui7-clear-button py-1 px-3' onClick={() => addItemBillSignaturePadRef.current?.clear()} disabled={addItemBillSubmitLoading}>
</Button>
</div>
</div>
{addItemBillSubmitMessage && (
<div className={`text-sm text-center ${addItemBillSubmitMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{addItemBillSubmitMessage}</div>
)}
<div className='flex items-center justify-end gap-3'>
<Button
className='py-2 px-6'
onClick={() => {
setShowAddItemBillSignature(false);
setAddItemBillSubmitMessage(null);
addItemBillSignaturePadRef.current?.clear();
}}
disabled={addItemBillSubmitLoading}
>
</Button>
<Button
className='py-2 px-6 bg-blue-600 text-white hover:bg-blue-700'
onClick={handleSubmitAddItemBillSign}
disabled={addItemBillSubmitLoading}
>
{addItemBillSubmitLoading ? '提交中...' : '提交签名'}
</Button>
</div>
</div>
</div>
)
}
{
showAddItemBillPreview && addItemBillUrl && (
<div className='fixed inset-0 z-[60] bg-black/75 flex flex-col'>
<div className='flex items-center justify-between p-4 text-white bg-gray-900/80'>
<div className='text-sm font-medium truncate pr-3'>{addItemBillName}</div>
<div className='flex items-center gap-2'>
<Button
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handleAddItemBillDirectPrint}
disabled={busy}
>
</Button>
{isAddItemBillSigned && (
<Button
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handleAddItemBillDirectPrint}
disabled={busy}
>
</Button>
)}
{!isAddItemBillSigned && (
<Button
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
setShowAddItemBillPreview(false);
setShowAddItemBillSignature(true);
}}
disabled={busy}
>
</Button>
)}
<Button className='py-1 px-3' onClick={() => !busy && setShowAddItemBillPreview(false)} disabled={busy}>
</Button>