From 973f5338af50f41b2785ae54f81bd5aa72348d35 Mon Sep 17 00:00:00 2001 From: xianyi Date: Tue, 6 Jan 2026 16:42:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/exam/ExamAddonPanel.tsx | 27 ++- src/components/exam/ExamSignPanel.tsx | 322 +++++++++++++------------ src/utils/examActions.ts | 46 +++- 3 files changed, 224 insertions(+), 171 deletions(-) diff --git a/src/components/exam/ExamAddonPanel.tsx b/src/components/exam/ExamAddonPanel.tsx index 2104831..f7e9694 100644 --- a/src/components/exam/ExamAddonPanel.tsx +++ b/src/components/exam/ExamAddonPanel.tsx @@ -356,17 +356,22 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { }; }, []); - // 获取加项PDF - const fetchAddItemBillPdf = async (examId: number) => { + // 获取加项PDF(按本次支付的组合代码生成对应的加项单) + const fetchAddItemBillPdf = async (examId: number, combinationItemCodes: string) => { try { // 调用接口获取加项PDF const res = await getAddItemBillPdf({ exam_id: examId, + CombinationCode: combinationItemCodes, }); - if (res.Status === 200 && res.Data?.pdf_url) { + if (res.Status === 200 && res.Data?.pdf_url && res.Data?.pdf_sort !== undefined && res.Data?.pdf_sort !== null) { // 保存加项PDF信息到localStorage(未签名状态) setAddItemBillPdf(examId, { + pdf_sort: res.Data.pdf_sort, + combinationCode: combinationItemCodes, + payment_status: res.Data.payment_status ?? null, + payment_status_name: res.Data.payment_status_name ?? null, pdf_name: res.Data.pdf_name || '加项单', pdf_url: res.Data.pdf_url, is_signed: false, @@ -392,7 +397,8 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { discount_rate: number; }>, pay_type: number, - company_id: number + company_id: number, + combinationItemCodes: string ) => { if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); @@ -424,8 +430,8 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { setQrcodeUrl(null); // 清空已选项目 setSelectedIds(new Set()); - // 获取加项PDF - fetchAddItemBillPdf(physical_exam_id).then((success) => { + // 获取本次支付对应的加项PDF + fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { if (success) { setPaymentMessage('支付成功,加项单已生成'); } else { @@ -497,7 +503,7 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { return; } - // 获取组合项目代码(用于生成二维码接口,多个加项逗号分隔) + // 获取组合项目代码(用于生成二维码接口 & 生成加项单,多个加项逗号分隔) const combinationItemCodes = listAddItemCombination .map((item) => item.combination_item_code) .join(','); @@ -534,7 +540,8 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { patient_id, listAddItemCombination, 12, // 微信支付 - 0 // 自费模式,company_id 传 0 + 0, // 自费模式,company_id 传 0 + combinationItemCodes ); } else { setPaymentMessage(res.Message || '生成支付二维码失败'); @@ -564,8 +571,8 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { if (result === 'success' || result === '1' || result === 'SUCCESS') { setPaymentMessage('挂账成功,正在生成加项单...'); setSelectedIds(new Set()); - // 获取加项PDF - fetchAddItemBillPdf(physical_exam_id).then((success) => { + // 获取本次挂账对应的加项PDF + fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { if (success) { setPaymentMessage('挂账成功,加项单已生成'); } else { diff --git a/src/components/exam/ExamSignPanel.tsx b/src/components/exam/ExamSignPanel.tsx index 9bd9c97..e8ab4f7 100644 --- a/src/components/exam/ExamSignPanel.tsx +++ b/src/components/exam/ExamSignPanel.tsx @@ -13,6 +13,7 @@ import { setAddItemBillPdf, getAddItemBillPdf as getAddItemBillPdfFromStorage, type TongyishuPdfInfo, + type AddItemBillPdfInfo, } from '../../utils/examActions'; import type { SignaturePadHandle } from '../ui'; import { Button, SignaturePad } from '../ui'; @@ -73,10 +74,9 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { const [daojiandanPdfReady, setDaojiandanPdfReady] = useState(false); const [daojiandanPdfBlobUrl, setDaojiandanPdfBlobUrl] = useState(null); const daojiandanCanvasContainerRef = useRef(null); - // 加项单相关状态 - const [addItemBillUrl, setAddItemBillUrl] = useState(null); - const [addItemBillName, setAddItemBillName] = useState('加项单'); - const [isAddItemBillSigned, setIsAddItemBillSigned] = useState(false); // 加项单是否已签名 + // 加项单相关状态(支持多个加项单) + const [addItemBillList, setAddItemBillList] = useState([]); + const [currentAddItemBill, setCurrentAddItemBill] = useState(null); const [showAddItemBillPreview, setShowAddItemBillPreview] = useState(false); const [showAddItemBillSignature, setShowAddItemBillSignature] = useState(false); const addItemBillSignaturePadRef = useRef(null); @@ -362,31 +362,17 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { fetchDaojiandan(); } - // 检查加项单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); + // 检查加项单PDF(可能有多个) + const storedAddItemBillList = getAddItemBillPdfFromStorage(examId); + if (storedAddItemBillList && storedAddItemBillList.length > 0) { + setAddItemBillList(storedAddItemBillList); + // 默认选中第一个未签名的加项单,如果都已签名则选中第一个 + const unsigned = storedAddItemBillList.find((item) => item.is_signed !== true); + setCurrentAddItemBill(unsigned || storedAddItemBillList[0]); } 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(); + // 没有加项单:不再主动调用接口获取,因为新接口需要 CombinationCode(仅支付时知道) + setAddItemBillList([]); + setCurrentAddItemBill(null); } }, [examId]); @@ -578,9 +564,9 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { renderAllPages(); }, [daojiandanPdfData]); - // 加载加项单 PDF 数据 + // 加载加项单 PDF 数据(当前选中的加项单) useEffect(() => { - if (!showAddItemBillPreview || !addItemBillUrl) { + if (!showAddItemBillPreview || !currentAddItemBill?.pdf_url) { setAddItemBillPdfData(null); setAddItemBillPdfBlobUrl(null); setAddItemBillPdfReady(false); @@ -592,7 +578,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { setAddItemBillPdfLoading(true); setAddItemBillPdfData(null); - fetch(addItemBillUrl) + fetch(currentAddItemBill.pdf_url) .then((resp) => { if (!resp.ok) throw new Error('获取PDF文件失败'); return resp.blob(); @@ -616,7 +602,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { URL.revokeObjectURL(objectUrl); } }; - }, [showAddItemBillPreview, addItemBillUrl]); + }, [showAddItemBillPreview, currentAddItemBill?.pdf_url]); // 渲染加项单 PDF useEffect(() => { @@ -736,29 +722,19 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { }; }, [showDaojiandanPreview, daojiandanUrl]); - // 加项单预览:使用 pdfjs 渲染到 canvas + // 加项单预览:使用 pdfjs 渲染到 canvas(依赖加载好的 addItemBillPdfData) useEffect(() => { - if (!showAddItemBillPreview || !addItemBillUrl || !addItemBillCanvasContainerRef.current) return; + if (!showAddItemBillPreview || !addItemBillPdfData || !addItemBillCanvasContainerRef.current) return; const container = addItemBillCanvasContainerRef.current; - let cancelled = false; + container.innerHTML = ''; 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 pdf = await pdfjsLib.getDocument({ data: addItemBillPdfData }).promise; const scale = 3.0; for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { - if (cancelled) return; - const page = await pdf.getPage(pageNum); const viewport = page.getViewport({ scale }); @@ -785,20 +761,15 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { } } catch (err) { console.error('加项单PDF 渲染失败', err); - } finally { - if (!cancelled) { - setAddItemBillPdfLoading(false); - } } }; renderAddItemBill(); return () => { - cancelled = true; container.innerHTML = ''; }; - }, [showAddItemBillPreview, addItemBillUrl]); + }, [showAddItemBillPreview, addItemBillPdfData]); const handlePrint = () => { if (!pdfBlobUrl || !pdfReady || !canvasContainerRef.current || !previewPdf) return; @@ -1104,13 +1075,18 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { } }; - // 加项单签名提交 + // 加项单签名提交(针对当前选中的加项单) const handleSubmitAddItemBillSign = async () => { if (!examId) { setAddItemBillSubmitMessage('缺少必要信息,无法提交签名'); return; } + if (!currentAddItemBill) { + setAddItemBillSubmitMessage('没有可签名的加项单'); + return; + } + const dataUrl = addItemBillSignaturePadRef.current?.toDataURL('image/png'); if (!dataUrl) { setAddItemBillSubmitMessage('请先完成签名'); @@ -1124,6 +1100,8 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { const res = await submitAddItemBillSign({ exam_id: examId, + pdf_sort: currentAddItemBill.pdf_sort, + combinationCode: currentAddItemBill.combinationCode, sign_file: blob, }); @@ -1131,17 +1109,40 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { 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, + // 更新当前加项单为已签名,并更新本地列表与 localStorage + setAddItemBillList((prev) => { + const next = prev.map((item) => { + if (item.pdf_sort === currentAddItemBill.pdf_sort) { + const updated: AddItemBillPdfInfo = { + ...item, + pdf_name: pdfNameValue, + pdf_url: pdfUrlValue, + payment_status: res.Data?.payment_status ?? item.payment_status ?? null, + payment_status_name: res.Data?.payment_status_name ?? item.payment_status_name ?? null, + is_signed: true, + }; + // 同步写入 localStorage + setAddItemBillPdf(examId, updated); + return updated; + } + return item; + }); + return next; }); + setCurrentAddItemBill((prev) => + prev && prev.pdf_sort === currentAddItemBill.pdf_sort + ? { + ...prev, + pdf_name: pdfNameValue, + pdf_url: pdfUrlValue, + payment_status: res.Data?.payment_status ?? prev.payment_status ?? null, + payment_status_name: res.Data?.payment_status_name ?? prev.payment_status_name ?? null, + is_signed: true, + } + : prev + ); + setTimeout(() => { setShowAddItemBillSignature(false); setAddItemBillSubmitMessage(null); @@ -1160,11 +1161,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { }; // 加项单直接打印 - const handleAddItemBillDirectPrint = async () => { - if (!addItemBillUrl) return; + const handleAddItemBillDirectPrint = async (target: AddItemBillPdfInfo | null) => { + if (!target?.pdf_url) return; try { - const response = await fetch(addItemBillUrl); + const response = await fetch(target.pdf_url); if (!response.ok) throw new Error('获取PDF文件失败'); const blob = await response.blob(); const arrayBuffer = await blob.arrayBuffer(); @@ -1200,7 +1201,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => { - 加项单打印 + ${target.pdf_name || '加项单打印'}