打印导检单

This commit is contained in:
xianyi
2025-12-11 16:35:19 +08:00
parent 364f85fb9c
commit ad696d2fcb

View File

@@ -7,7 +7,7 @@ import type {
CustomerInfo,
PhysicalExamProgressItem,
} from '../../api';
import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter } from '../../api';
import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter, getTongyishuPdf } from '../../api';
import { Button, Input } from '../ui';
interface ExamModalProps {
@@ -84,7 +84,7 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
onTouchStart={handleTouchStart}
>
<div
className='w-[960px] max-w-[95vw] bg-white rounded-2xl shadow-xl overflow-hidden text-sm'
className='w-[960px] max-w-[95vw] max-h-[95vh] bg-white rounded-2xl shadow-xl overflow-hidden text-sm'
style={{ touchAction: 'none' }}
onDoubleClick={handleDoubleClick}
onTouchStart={handleTouchStart}
@@ -743,89 +743,166 @@ const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => (
</div>
);
const ExamPrintPanel = ({ client }: { client: ExamClient }) => (
const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pdfReady, setPdfReady] = useState(false);
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
const printRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
// 获取 PDF
useEffect(() => {
const physical_exam_id = Number(client.id);
if (!physical_exam_id) {
setError('无效的体检ID');
setLoading(false);
return;
}
setPdfReady(false);
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
// 取第一个PDF URL
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
setPdfReady(false);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}, [client.id]);
// 将 PDF 拉取为同源 blob避免跨域打印限制
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;
// 打开新窗口打印(同源 blob避免跨域和空白页问题
const printWindow = window.open(pdfBlobUrl, '_blank');
if (printWindow) {
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
return (
<div className='flex justify-center'>
<div className='w-[520px] max-w-[95%] bg-white rounded-2xl border shadow-sm px-6 py-4 text-xs text-gray-800'>
<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'></div>
</div>
<div className='flex items-center gap-3'>
<div className='text-right text-[11px] text-gray-500'>
<div>{client.id}</div>
<div>2025-11-18</div>
<div>{new Date().toLocaleDateString('zh-CN')}</div>
</div>
<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>
<div className='grid grid-cols-2 gap-y-1 gap-x-6 mb-3'>
<div>
<span className='font-medium'>{client.name}</span>
{loading ? (
<div className='flex items-center justify-center py-12 text-gray-500'>
<div>PDF...</div>
</div>
<div>
/
<span className='font-medium'>
{client.gender} / {client.age}
</span>
) : 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={() => {
setLoading(true);
setError(null);
const physical_exam_id = Number(client.id);
if (physical_exam_id) {
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}
}}
>
</Button>
</div>
<div>
<span className='font-medium'>{client.packageName}</span>
</div>
<div>
<span className='font-medium'>{client.customerType}</span>
</div>
</div>
<div className='mb-2 font-medium'></div>
<table className='w-full border text-[11px] mb-3'>
<thead>
<tr className='bg-gray-50'>
<th className='border px-2 py-1 text-left'></th>
<th className='border px-2 py-1 text-left'></th>
<th className='border px-2 py-1 text-left'></th>
<th className='border px-2 py-1 text-left'></th>
</tr>
</thead>
<tbody>
{client.checkedItems.map((item, idx) => (
<tr key={`c-${idx}`}>
<td className='border px-2 py-1'>{idx + 1}</td>
<td className='border px-2 py-1'>{item}</td>
<td className='border px-2 py-1'></td>
<td className='border px-2 py-1'></td>
</tr>
))}
{client.pendingItems.map((item, idx) => (
<tr key={`p-${idx}`}>
<td className='border px-2 py-1'>{client.checkedItems.length + idx + 1}</td>
<td className='border px-2 py-1'>{item}</td>
<td className='border px-2 py-1'></td>
<td className='border px-2 py-1'></td>
</tr>
))}
</tbody>
</table>
<div className='grid grid-cols-2 gap-4 text-[11px] text-gray-600'>
<div>
<div className='mb-1 font-medium text-gray-800'></div>
<ul className='list-disc ml-4 space-y-0.5'>
<li></li>
<li></li>
<li>尿</li>
</ul>
</div>
<div className='flex flex-col items-end justify-between'>
<div className='text-right'>
<div>________________</div>
<div className='mt-2'>2025-11-18 09:30</div>
</div>
<div className='mt-4 w-24 h-24 border border-dashed flex items-center justify-center text-[10px] text-gray-400'>
/
) : pdfUrl ? (
<div className='w-full'>
{/* PDF预览区域 */}
<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>
);
);
};