Files
ipad/src/components/exam/ExamPrintPanel.tsx
2025-12-16 10:07:11 +08:00

212 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from 'react';
import { submitDaojiandanSign } from '../../api';
import type { ExamClient } from '../../data/mockData';
import { setExamActionRecord } from '../../utils/examActions';
import type { SignaturePadHandle } from '../ui';
import { Button, SignaturePad } from '../ui';
export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [pdfReady, setPdfReady] = useState(false);
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
const [signatureSubmitted, setSignatureSubmitted] = useState(false);
const [submitLoading, setSubmitLoading] = useState(false);
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
const signaturePadRef = useRef<SignaturePadHandle | null>(null);
const printRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
const handleSubmitSign = async () => {
const examId = Number(client.id);
if (!examId) {
setSubmitMessage('无效的体检ID');
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());
// 提交签名生成导检单PDF
const res = await submitDaojiandanSign({
exam_id: examId,
sign_file: blob,
});
if (res.Status === 200 && res.Data?.pdf_url) {
setSubmitMessage('签名提交成功,正在加载导检单...');
setSignatureSubmitted(true);
setPdfUrl(res.Data.pdf_url);
// 记录打印导检单是否签名操作
setExamActionRecord(examId, 'printSign', true);
} else {
setSubmitMessage(res.Message || '签名提交失败');
}
} catch (err) {
console.error('提交签名失败', err);
setSubmitMessage('签名提交失败,请稍后重试');
} finally {
setSubmitLoading(false);
}
};
useEffect(() => {
if (!pdfUrl) return;
let objectUrl: string | null = null;
setPdfReady(false);
setLoading(true);
fetch(pdfUrl)
.then((resp) => {
if (!resp.ok) throw new Error('获取PDF文件失败');
return resp.blob();
})
.then((blob) => {
objectUrl = URL.createObjectURL(blob);
setPdfBlobUrl(objectUrl);
})
.catch((err) => {
console.error('PDF 拉取失败', err);
setError('PDF 加载失败,请稍后重试');
})
.finally(() => {
setLoading(false);
});
return () => {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
};
}, [pdfUrl]);
const handlePrint = () => {
if (!pdfBlobUrl || !pdfReady) return;
const printWindow = window.open(pdfBlobUrl, '_blank');
if (printWindow) {
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
return (
<div className='flex justify-center'>
<div className='w-full max-w-[95%] bg-white rounded-2xl border shadow-sm px-6 py-4 text-xs text-gray-800'>
<div className='flex items-center justify-between border-b pb-3 mb-3'>
<div>
<div className='text-sm font-semibold'> · </div>
<div className='text-[11px] text-gray-500 mt-1'>
{signatureSubmitted
? '此为预览页面,实际打印效果以院内打印机为准。'
: '请先完成签名签名后将生成导检单PDF'}
</div>
</div>
<div className='flex items-center gap-3'>
<div className='text-right text-[11px] text-gray-500'>
<div>{client.id}</div>
<div>{new Date().toLocaleDateString('zh-CN')}</div>
</div>
{signatureSubmitted && (
<Button
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handlePrint}
disabled={loading || !pdfReady || !pdfUrl}
>
</Button>
)}
</div>
</div>
{!signatureSubmitted ? (
<div className='flex flex-col gap-4'>
<div className='text-sm font-medium text-gray-900'></div>
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
<SignaturePad
ref={signaturePadRef}
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={() => signaturePadRef.current?.clear()}
disabled={submitLoading}
>
</Button>
</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 bg-blue-600 text-white hover:bg-blue-700'
onClick={handleSubmitSign}
disabled={submitLoading}
>
{submitLoading ? '提交中...' : '提交签名'}
</Button>
</div>
</div>
) : loading ? (
<div className='flex items-center justify-center py-12 text-gray-500'>
<div>PDF...</div>
</div>
) : error ? (
<div className='flex flex-col items-center justify-center py-12 text-gray-500'>
<div className='mb-2'>{error}</div>
<Button
className='px-4 py-1.5 text-xs'
onClick={() => {
setError(null);
setPdfUrl(null);
setSignatureSubmitted(false);
signaturePadRef.current?.clear();
}}
>
</Button>
</div>
) : pdfUrl ? (
<div className='w-full'>
<div ref={printRef} className='print-content'>
<div className='flex justify-center border rounded-lg p-4 bg-gray-50 overflow-auto max-h-[600px]'>
<iframe
src={pdfBlobUrl || ''}
className='w-full h-[600px] border rounded-lg'
title='导检单PDF预览'
onLoad={() => setPdfReady(true)}
ref={iframeRef}
/>
</div>
</div>
</div>
) : null}
</div>
</div>
);
};