From 6a6e7dd83dc183546d2fb2437df075f7f000b1ad Mon Sep 17 00:00:00 2001 From: xianyi Date: Tue, 6 Jan 2026 10:00:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E6=A3=80=E5=8D=95=E5=92=8C=E5=8A=A0?= =?UTF-8?q?=E9=A1=B9=E5=8D=95PDF=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/exam/ExamSignPanel.tsx | 358 +++++++++++++++++++++++++- 1 file changed, 344 insertions(+), 14 deletions(-) diff --git a/src/components/exam/ExamSignPanel.tsx b/src/components/exam/ExamSignPanel.tsx index 65541ba..e7c3f2a 100644 --- a/src/components/exam/ExamSignPanel.tsx +++ b/src/components/exam/ExamSignPanel.tsx @@ -68,6 +68,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { const [daojiandanSubmitLoading, setDaojiandanSubmitLoading] = useState(false); const [daojiandanSubmitMessage, setDaojiandanSubmitMessage] = useState(null); const [showDaojiandanPreview, setShowDaojiandanPreview] = useState(false); + const [daojiandanPdfData, setDaojiandanPdfData] = useState(null); + const [daojiandanPdfLoading, setDaojiandanPdfLoading] = useState(false); + const [daojiandanPdfReady, setDaojiandanPdfReady] = useState(false); + const [daojiandanPdfBlobUrl, setDaojiandanPdfBlobUrl] = useState(null); + const daojiandanCanvasContainerRef = useRef(null); // 加项单相关状态 const [addItemBillUrl, setAddItemBillUrl] = useState(null); const [addItemBillName, setAddItemBillName] = useState('加项单'); @@ -77,6 +82,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { const addItemBillSignaturePadRef = useRef(null); const [addItemBillSubmitLoading, setAddItemBillSubmitLoading] = useState(false); const [addItemBillSubmitMessage, setAddItemBillSubmitMessage] = useState(null); + const [addItemBillPdfData, setAddItemBillPdfData] = useState(null); + const [addItemBillPdfLoading, setAddItemBillPdfLoading] = useState(false); + const [addItemBillPdfReady, setAddItemBillPdfReady] = useState(false); + const [addItemBillPdfBlobUrl, setAddItemBillPdfBlobUrl] = useState(null); + const addItemBillCanvasContainerRef = useRef(null); const busy = signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading || addItemBillSubmitLoading; @@ -453,6 +463,314 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { renderAllPages(); }, [pdfData]); + // 加载导检单 PDF 数据 + useEffect(() => { + if (!showDaojiandanPreview || !daojiandanUrl) { + setDaojiandanPdfData(null); + setDaojiandanPdfBlobUrl(null); + setDaojiandanPdfReady(false); + return; + } + + let objectUrl: string | null = null; + setDaojiandanPdfReady(false); + setDaojiandanPdfLoading(true); + setDaojiandanPdfData(null); + + fetch(daojiandanUrl) + .then((resp) => { + if (!resp.ok) throw new Error('获取PDF文件失败'); + return resp.blob(); + }) + .then((blob) => { + objectUrl = URL.createObjectURL(blob); + setDaojiandanPdfBlobUrl(objectUrl); + return blob.arrayBuffer(); + }) + .then((arrayBuffer) => { + setDaojiandanPdfData(arrayBuffer); + setDaojiandanPdfLoading(false); + }) + .catch((err) => { + console.error('导检单PDF 拉取失败', err); + setDaojiandanPdfLoading(false); + }); + + return () => { + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + }; + }, [showDaojiandanPreview, daojiandanUrl]); + + // 渲染导检单 PDF + useEffect(() => { + if (!daojiandanPdfData || !daojiandanCanvasContainerRef.current) return; + + setDaojiandanPdfReady(false); + + const renderAllPages = async () => { + try { + const pdf = await pdfjsLib.getDocument({ data: daojiandanPdfData }).promise; + + if (!daojiandanCanvasContainerRef.current) return; + + // 清空容器 + daojiandanCanvasContainerRef.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) 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'; + + daojiandanCanvasContainerRef.current.appendChild(canvas); + + const renderContext = { + canvasContext: context, + viewport: viewport, + } as any; + + await page.render(renderContext).promise; + } + + setDaojiandanPdfReady(true); + } catch (err) { + console.error('导检单PDF 渲染失败', err); + setDaojiandanPdfLoading(false); + } + }; + + renderAllPages(); + }, [daojiandanPdfData]); + + // 加载加项单 PDF 数据 + useEffect(() => { + if (!showAddItemBillPreview || !addItemBillUrl) { + setAddItemBillPdfData(null); + setAddItemBillPdfBlobUrl(null); + setAddItemBillPdfReady(false); + return; + } + + let objectUrl: string | null = null; + setAddItemBillPdfReady(false); + setAddItemBillPdfLoading(true); + setAddItemBillPdfData(null); + + fetch(addItemBillUrl) + .then((resp) => { + if (!resp.ok) throw new Error('获取PDF文件失败'); + return resp.blob(); + }) + .then((blob) => { + objectUrl = URL.createObjectURL(blob); + setAddItemBillPdfBlobUrl(objectUrl); + return blob.arrayBuffer(); + }) + .then((arrayBuffer) => { + setAddItemBillPdfData(arrayBuffer); + setAddItemBillPdfLoading(false); + }) + .catch((err) => { + console.error('加项单PDF 拉取失败', err); + setAddItemBillPdfLoading(false); + }); + + return () => { + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + }; + }, [showAddItemBillPreview, addItemBillUrl]); + + // 渲染加项单 PDF + useEffect(() => { + if (!addItemBillPdfData || !addItemBillCanvasContainerRef.current) return; + + setAddItemBillPdfReady(false); + + const renderAllPages = async () => { + try { + const pdf = await pdfjsLib.getDocument({ data: addItemBillPdfData }).promise; + + if (!addItemBillCanvasContainerRef.current) return; + + // 清空容器 + addItemBillCanvasContainerRef.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) 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'; + + addItemBillCanvasContainerRef.current.appendChild(canvas); + + const renderContext = { + canvasContext: context, + viewport: viewport, + } as any; + + await page.render(renderContext).promise; + } + + setAddItemBillPdfReady(true); + } catch (err) { + console.error('加项单PDF 渲染失败', err); + setAddItemBillPdfLoading(false); + } + }; + + renderAllPages(); + }, [addItemBillPdfData]); + + // 导检单预览:使用 pdfjs 渲染到 canvas + useEffect(() => { + if (!showDaojiandanPreview || !daojiandanUrl || !daojiandanCanvasContainerRef.current) return; + + const container = daojiandanCanvasContainerRef.current; + let cancelled = false; + + const renderDaojiandan = async () => { + try { + setDaojiandanPdfLoading(true); + container.innerHTML = ''; + + const resp = await fetch(daojiandanUrl); + if (!resp.ok) throw new Error('获取PDF文件失败'); + const blob = await resp.blob(); + const arrayBuffer = await blob.arrayBuffer(); + + const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; + const scale = 1.2; + + for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { + if (cancelled) return; + + const page = await pdf.getPage(pageNum); + const viewport = page.getViewport({ scale }); + + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!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'; + + container.appendChild(canvas); + + const renderContext = { + canvasContext: context, + viewport: viewport, + } as any; + + await page.render(renderContext).promise; + } + } catch (err) { + console.error('导检单PDF 渲染失败', err); + } finally { + if (!cancelled) { + setDaojiandanPdfLoading(false); + } + } + }; + + renderDaojiandan(); + + return () => { + cancelled = true; + container.innerHTML = ''; + }; + }, [showDaojiandanPreview, daojiandanUrl]); + + // 加项单预览:使用 pdfjs 渲染到 canvas + useEffect(() => { + if (!showAddItemBillPreview || !addItemBillUrl || !addItemBillCanvasContainerRef.current) return; + + const container = addItemBillCanvasContainerRef.current; + let cancelled = false; + + const renderAddItemBill = async () => { + try { + setAddItemBillPdfLoading(true); + container.innerHTML = ''; + + const resp = await fetch(addItemBillUrl); + if (!resp.ok) throw new Error('获取PDF文件失败'); + const blob = await resp.blob(); + const arrayBuffer = await blob.arrayBuffer(); + + const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; + const scale = 1.2; + + for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { + if (cancelled) return; + + const page = await pdf.getPage(pageNum); + const viewport = page.getViewport({ scale }); + + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!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'; + + container.appendChild(canvas); + + const renderContext = { + canvasContext: context, + viewport: viewport, + } as any; + + await page.render(renderContext).promise; + } + } catch (err) { + console.error('加项单PDF 渲染失败', err); + } finally { + if (!cancelled) { + setAddItemBillPdfLoading(false); + } + } + }; + + renderAddItemBill(); + + return () => { + cancelled = true; + container.innerHTML = ''; + }; + }, [showAddItemBillPreview, addItemBillUrl]); + const handlePrint = () => { if (!pdfBlobUrl || !pdfReady || !canvasContainerRef.current) return; @@ -1574,13 +1892,19 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
-
-