import { useEffect, useRef, useState } from 'react'; import * as pdfjsLib from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'; import { getDaojiandanPdf as getDaojiandanPdfApi, submitDaojiandanSign, editDaojiandanPrintStatus } from '../../api'; import type { ExamClient } from '../../data/mockData'; import { setExamActionRecord, setDaojiandanPdf, getDaojiandanPdf } from '../../utils/examActions'; import type { SignaturePadHandle } from '../ui'; import { Button, SignaturePad } from '../ui'; // Polyfill for Promise.withResolvers if (typeof (Promise as any).withResolvers === 'undefined') { (Promise as any).withResolvers = function () { let resolve!: (value: T | PromiseLike) => void; let reject!: (reason?: any) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; } // 配置 PDF.js worker pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; export const ExamPrintPanel = ({ client }: { client: ExamClient }) => { const [pdfUrl, setPdfUrl] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [pdfReady, setPdfReady] = useState(false); const [pdfBlobUrl, setPdfBlobUrl] = useState(null); const [signatureSubmitted, setSignatureSubmitted] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); const [submitMessage, setSubmitMessage] = useState(null); const [showPreview, setShowPreview] = useState(false); const [fetchLoading, setFetchLoading] = useState(false); const [pdfData, setPdfData] = useState(null); const signaturePadRef = useRef(null); const printRef = useRef(null); const canvasContainerRef = useRef(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); const pdfUrlValue = res.Data.pdf_url; const pdfNameValue = res.Data.pdf_name || '导检单'; setPdfUrl(pdfUrlValue); // 保存导检单PDF信息到localStorage setDaojiandanPdf(examId, { pdf_name: pdfNameValue, pdf_url: pdfUrlValue, }); // 记录打印导检单是否签名操作 setExamActionRecord(examId, 'printSign', true); // 更新导检单打印状态 try { await editDaojiandanPrintStatus({ exam_id: examId }); } catch (err) { console.error('更新导检单打印状态失败', err); // 不阻塞主流程,仅记录错误 } } else { setSubmitMessage(res.Message || '签名提交失败'); } } catch (err) { console.error('提交签名失败', err); setSubmitMessage('签名提交失败,请稍后重试'); } finally { setSubmitLoading(false); } }; // 组件加载时检查localStorage,如果有则直接展示 useEffect(() => { const examId = Number(client.id); if (!examId) return; const storedPdf = getDaojiandanPdf(examId); if (storedPdf && storedPdf.pdf_url) { setPdfUrl(storedPdf.pdf_url); setShowPreview(true); } }, [client.id]); // 获取导检单PDF(优先使用localStorage,没有则调用接口) const handleFetchPdf = async () => { const examId = Number(client.id); if (!examId) { setError('无效的体检ID'); return; } setFetchLoading(true); setError(null); try { // 先尝试从localStorage获取 const storedPdf = getDaojiandanPdf(examId); if (storedPdf && storedPdf.pdf_url) { setPdfUrl(storedPdf.pdf_url); setShowPreview(true); setFetchLoading(false); return; } // 如果没有存储的,则调用接口获取 const res = await getDaojiandanPdfApi({ exam_id: examId }); if (res.Status === 200 && res.Data?.pdf_url) { const pdfUrlValue = res.Data.pdf_url; const pdfNameValue = res.Data.pdf_name || '导检单'; setPdfUrl(pdfUrlValue); // 保存到localStorage setDaojiandanPdf(examId, { pdf_name: pdfNameValue, pdf_url: pdfUrlValue, }); setShowPreview(true); } else { setError(res.Message || '获取导检单失败'); } } catch (err) { console.error('获取导检单失败', err); setError('获取导检单失败,请稍后重试'); } finally { setFetchLoading(false); } }; // 第一步:加载 PDF 数据 useEffect(() => { if (!pdfUrl) return; let objectUrl: string | null = null; setPdfReady(false); setLoading(true); setPdfData(null); fetch(pdfUrl) .then((resp) => { if (!resp.ok) throw new Error('获取PDF文件失败'); return resp.blob(); }) .then((blob) => { objectUrl = URL.createObjectURL(blob); setPdfBlobUrl(objectUrl); return blob.arrayBuffer(); }) .then((arrayBuffer) => { setPdfData(arrayBuffer); setLoading(false); }) .catch((err) => { console.error('PDF 拉取失败', err); setError('PDF 加载失败,请稍后重试'); setLoading(false); }); return () => { if (objectUrl) { URL.revokeObjectURL(objectUrl); } }; }, [pdfUrl]); // 第二步:渲染 PDF useEffect(() => { if (!pdfData || !canvasContainerRef.current) return; console.log('开始渲染 PDF'); setPdfReady(false); const renderAllPages = async () => { try { const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; console.log('PDF 加载成功,共 ' + pdf.numPages + ' 页'); if (!canvasContainerRef.current) { console.log('canvasContainerRef 仍为 null'); return; } // 清空容器 canvasContainerRef.current.innerHTML = ''; const scale = 1.2; for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); const viewport = page.getViewport({ scale }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) { console.log('无法获取 canvas context'); continue; } canvas.height = viewport.height; canvas.width = viewport.width; canvas.style.display = 'block'; canvas.style.marginBottom = '10px'; canvas.className = 'mx-auto border rounded-lg shadow-sm'; canvasContainerRef.current.appendChild(canvas); const renderContext = { canvasContext: context, viewport: viewport, } as any; await page.render(renderContext).promise; console.log('渲染完成第 ' + pageNum + ' 页'); } setPdfReady(true); console.log('所有页面渲染完成'); } catch (err) { console.error('PDF 渲染失败', err); setError('PDF 渲染失败,请稍后重试: ' + err); } }; renderAllPages(); }, [pdfData]); const handlePrint = () => { if (!pdfBlobUrl || !pdfReady || !canvasContainerRef.current) return; // 创建打印窗口 const printWindow = window.open('', '_blank'); if (!printWindow) return; // 获取所有的 canvas const canvases = canvasContainerRef.current.querySelectorAll('canvas'); // 构建打印页面 printWindow.document.write(` 导检单打印 `); // 将每个 canvas 转换为图片并添加到打印窗口 canvases.forEach((canvas) => { const imgData = (canvas as HTMLCanvasElement).toDataURL('image/png'); printWindow.document.write(``); }); printWindow.document.write(` `); printWindow.document.close(); // 等待图片加载完成后执行打印 printWindow.onload = () => { printWindow.focus(); setTimeout(() => { printWindow.print(); }, 1000); }; }; return (
圆和医疗体检中心 · 导检单预览
{signatureSubmitted || showPreview ? '此为预览页面,实际打印效果以院内打印机为准。' : '请先完成签名,签名后将生成导检单PDF'}
体检号:{client.id}
日期:{new Date().toLocaleDateString('zh-CN')}
{!showPreview && !signatureSubmitted && ( )} {showPreview && !signatureSubmitted && ( <> )} {signatureSubmitted && ( )}
{!signatureSubmitted && !showPreview ? (
请在下方区域签名
请在上方区域签名
{submitMessage && (
{submitMessage}
)}
) : fetchLoading ? (
正在加载导检单...
) : loading ? (
正在加载PDF...
) : error ? (
{error}
{signatureSubmitted ? ( ) : ( )}
) : pdfUrl ? (
) : null}
); };