From 55d7383db61ba3b4a137de42abe53d920cd846b2 Mon Sep 17 00:00:00 2001 From: xianyi Date: Fri, 28 Nov 2025 16:54:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9F=A5=E6=83=85=E5=90=8C?= =?UTF-8?q?=E6=84=8F=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/UI6/UI6.tsx | 5 +- src/pages/UI7/UI7.tsx | 335 +++++++++++++++++++++++++++++----------- src/pages/UI81/UI81.tsx | 41 +---- src/pages/UI9/UI9.tsx | 3 + 4 files changed, 252 insertions(+), 132 deletions(-) diff --git a/src/pages/UI6/UI6.tsx b/src/pages/UI6/UI6.tsx index 4597004..3226d15 100644 --- a/src/pages/UI6/UI6.tsx +++ b/src/pages/UI6/UI6.tsx @@ -33,6 +33,7 @@ const UI6: React.FC = () => { } const res = await getPackagItemDetail(id_no as string); if (res.Status === 200) { + localStorage.setItem("package_code", res.Data.packagItemInfo.package_code.toString() || ""); // 处理数据:将 project_id 和 project_name 字符串分离为数组 const processedData = res.Data.listPackDetail.map((item: any) => { // 将 project_id 字符串按中文顿号分割为数组 @@ -68,10 +69,10 @@ const UI6: React.FC = () => { - {localStorage.getItem("name")}{localStorage.getItem("gender") === "男" ? "先生" : "女士"}定制套餐 + {localStorage.getItem("name")}{localStorage.getItem("gender") === "男" ? "先生" : "女士"} {PackageInfo.package_name}

- 以下是您预约的 {PackageInfo.appointment_datetime} 体检套餐详情,请核对确认 + 尊敬的客户,以下是您预约的 {PackageInfo.appointment_datetime} 体检套餐详情,请核对确认
diff --git a/src/pages/UI7/UI7.tsx b/src/pages/UI7/UI7.tsx index 4db6ac2..70e2b04 100644 --- a/src/pages/UI7/UI7.tsx +++ b/src/pages/UI7/UI7.tsx @@ -8,59 +8,83 @@ import ConfirmButton from "../../components/ConfirmButton"; import DecorLine from "../../components/DecorLine"; import WaitButton from "../../components/WaitButton"; -import { getTongyishuPdf, submitDaojiandanSign } from "../../api/hisApi"; +import { + getTongyishuPdf, + signIn, + submitDaojiandanSign, + submitTongyishuSign, +} from "../../api/hisApi"; // 独立的 PDF 渲染组件,使用 React.memo 避免不必要的重新渲染 const PdfRenderer = React.memo<{ - pdfFiles: Uint8Array[]; + pdfFile: Uint8Array | null; loading: boolean; error: string; - pageCounts: Record; - onPageCountUpdate: (index: number, numPages: number) => void; + pageCount: number; + onPageCountUpdate: (numPages: number) => void; loadMessage: string; -}>(({ pdfFiles, loading, error, pageCounts, onPageCountUpdate, loadMessage }) => { - return ( - <> - {loading && ( -
- {loadMessage} -
- )} - {error && ( -
- {error} -
- )} - {!loading && - !error && - pdfFiles.map((fileData, index) => ( -
+ documentKey: string; +}>( + ({ + pdfFile, + loading, + error, + pageCount, + onPageCountUpdate, + loadMessage, + documentKey, + }) => { + const pagesToRender = Math.max(pageCount, 1); + return ( + <> + {loading && ( +
+ {loadMessage} +
+ )} + {error && ( +
+ {error} +
+ )} + {!loading && !error && !pdfFile && ( +
+ 暂无可展示的 PDF,等待加载完成 +
+ )} + {!loading && !error && pdfFile && ( +
onPageCountUpdate(index, numPages)} + onLoadSuccess={({ numPages }) => onPageCountUpdate(numPages)} > - {Array.from( - { length: pageCounts[index] || 0 }, - (_, pageIndex) => ( - - ) - )} + {Array.from({ length: pagesToRender }, (_, pageIndex) => ( + + ))}
- ))} - - ); -}); + )} + + ); + } +); PdfRenderer.displayName = "PdfRenderer"; +type PdfMeta = { + pdf_url: string; + pdf_name: string; + combination_code: number | null; +}; + const UI7: React.FC = () => { const navigate = useNavigate(); const hasFetchedRef = useRef(false); @@ -70,33 +94,30 @@ const UI7: React.FC = () => { const [countdown, setCountdown] = useState(5); const [showWaitButton, setShowWaitButton] = useState(true); const [pdfFiles, setPdfFiles] = useState([]); - const [pageCounts, setPageCounts] = - useState>({}); + const [pdfInfoList, setPdfInfoList] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [loadMessage, setLoadMessage] = useState("PDF加载中..."); const [isDrawing, setIsDrawing] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [currentPdfPageCount, setCurrentPdfPageCount] = useState(0); - + const totalRequiredSignatures = pdfInfoList.length + 1; + const isInstructionStep = currentStep === 0; + const currentPdfIndex = isInstructionStep ? -1 : currentStep - 1; + const currentPdfFile = + currentPdfIndex >= 0 && currentPdfIndex < pdfFiles.length + ? pdfFiles[currentPdfIndex] + : null; + const currentPdfMeta = + currentPdfIndex >= 0 && currentPdfIndex < pdfInfoList.length + ? pdfInfoList[currentPdfIndex] + : null; + const currentPdfName = currentPdfMeta?.pdf_name || ""; // 辅助函数:从 URL 获取 PDF 的 Uint8Array const fetchPdfBytes = async (url: string) => { try { - if (window.electronAPI?.fetchPdf) { - window.electronAPI.log("info", `[UI7] 通过 Electron 获取 PDF: ${url}`); - const result = await window.electronAPI.fetchPdf(url); - if (result.success && result.data) { - const cleanBase64 = result.data.replace(/\s/g, ""); - const binaryString = atob(cleanBase64); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; - } - throw new Error(result.error || "fetchPdf 返回失败"); - } - window.electronAPI?.log("info", `[UI7] 通过 fetch 获取 PDF: ${url}`); const response = await fetch(url); if (!response.ok) { @@ -128,19 +149,54 @@ const UI7: React.FC = () => { return; } - const pdfUrls: string[] = res.Data?.list_pdf_url || []; - if (!pdfUrls.length) { + const rawList = res.Data?.list_pdf_url || []; + const normalizedList = rawList + .map((item, idx): PdfMeta => { + if (typeof item === "string") { + return { + pdf_url: item, + pdf_name: `知情同意书${idx + 1}`, + combination_code: null, + }; + } + if (item && typeof item === "object") { + const codeValue = + item.combination_code != null + ? Number(item.combination_code) + : null; + return { + pdf_url: item.pdf_url || "", + pdf_name: item.pdf_name || `知情同意书${idx + 1}`, + combination_code: + codeValue != null && !Number.isNaN(codeValue) + ? codeValue + : null, + }; + } + return { + pdf_url: "", + pdf_name: `知情同意书${idx + 1}`, + combination_code: null, + }; + }) + .filter( + (item): item is PdfMeta => + typeof item.pdf_url === "string" && item.pdf_url.length > 0 + ); + + if (!normalizedList.length) { throw new Error("未获取到任何 PDF 链接"); } setLoading(true); setError(""); setPdfFiles([]); + setPdfInfoList([]); window.electronAPI?.log("info", "[UI7] 开始获取 PDF 文件"); const files: Uint8Array[] = []; - for (let idx = 0; idx < pdfUrls.length; idx++) { - const url = pdfUrls[idx]; + for (let idx = 0; idx < normalizedList.length; idx++) { + const { pdf_url: url } = normalizedList[idx]; window.electronAPI?.log( "info", `[UI7] 下载第 ${idx + 1} 份 PDF: ${url}` @@ -150,6 +206,7 @@ const UI7: React.FC = () => { } setPdfFiles(files); + setPdfInfoList(normalizedList); setLoading(false); setError(""); } catch (err) { @@ -164,6 +221,10 @@ const UI7: React.FC = () => { fetchPdfs(); }, []); + useEffect(() => { + localStorage.removeItem("consentSignatureList"); + }, []); + useEffect(() => { if (countdown > 0) { const timer = setTimeout(() => { @@ -175,6 +236,15 @@ const UI7: React.FC = () => { } }, [countdown]); + useEffect(() => { + setCurrentPdfPageCount(0); + }, [currentStep, currentPdfFile]); + + const resetCountdown = useCallback(() => { + setCountdown(5); + setShowWaitButton(true); + }, []); + useEffect(() => { const initCanvas = () => { const canvas = canvasRef.current; @@ -342,10 +412,13 @@ const UI7: React.FC = () => { try { const blob = await canvasToBlob(canvas); - // 仍在本地缓存一下,方便 UI8 等页面复用 + // 本地缓存成列表,方便 UI8 或调试使用 try { const dataUrl = canvas.toDataURL("image/png"); - localStorage.setItem("consentSignature", dataUrl); + const storedRaw = localStorage.getItem("consentSignatureList"); + const storedList: string[] = storedRaw ? JSON.parse(storedRaw) : []; + storedList[currentStep] = dataUrl; + localStorage.setItem("consentSignatureList", JSON.stringify(storedList)); } catch (cacheErr) { window.electronAPI?.log( "warn", @@ -353,17 +426,48 @@ const UI7: React.FC = () => { ); } + if (currentStep === 0) { + window.electronAPI?.log( + "info", + `[UI7] 提交导检单签名(第 1 次),exam_id=${examId}` + ); + const daojiandanRes = await submitDaojiandanSign(examId, blob); + if (daojiandanRes.Status !== 200) { + throw new Error(daojiandanRes.Message || "提交导检单签名失败"); + } + localStorage.setItem("consentSignature", daojiandanRes.Data.pdf_url || ""); + } else { + const meta = pdfInfoList[currentStep - 1]; + if (!meta || meta.combination_code == null) { + throw new Error("当前知情同意书缺少组合代码,无法提交签名"); + } + window.electronAPI?.log( + "info", + `[UI7] 提交知情同意书签名,exam_id=${examId}, combination_code=${meta.combination_code}` + ); + const tongyishuRes = await submitTongyishuSign( + examId, + meta.combination_code, + blob + ); + if (tongyishuRes.Status !== 200) { + throw new Error(tongyishuRes.Message || "提交知情同意书签名失败"); + } + } + + if (currentStep >= pdfInfoList.length) { + await sign(); + navigate("/UI8"); + return; + } + + clearCanvas(); + setCurrentStep((prev) => prev + 1); + resetCountdown(); window.electronAPI?.log( "info", - `[UI7] 提交导检单签名,exam_id=${examId}` + `[UI7] 完成第 ${currentStep + 1} 次签名,进入下一份 PDF` ); - const res = await submitDaojiandanSign(examId, blob); - if (res.Status !== 200) { - throw new Error(res.Message || "提交签名失败"); - } - window.electronAPI?.log("info", "[UI7] 签名提交成功"); - localStorage.setItem("consentSignature", res.Data.pdf_url || ""); - navigate("/UI8"); } catch (err) { const msg = (err as Error).message || "签名提交失败,请稍后重试"; window.electronAPI?.log("error", `[UI7] ${msg}`); @@ -374,37 +478,88 @@ const UI7: React.FC = () => { }; execute(); - }, [canvasToBlob, isCanvasBlank, isSubmitting, navigate]); + }, [ + canvasToBlob, + clearCanvas, + currentStep, + isCanvasBlank, + isSubmitting, + navigate, + pdfInfoList, + resetCountdown, + totalRequiredSignatures, + ]); - // 稳定 PDF 页数更新回调 - const handlePageCountUpdate = useCallback((index: number, numPages: number) => { - setPageCounts((prev) => { - if (prev[index] === numPages) return prev; - return { ...prev, [index]: numPages }; - }); + const handlePageCountUpdate = useCallback((numPages: number) => { + setCurrentPdfPageCount(numPages); }, []); + const sign = async () => { + const physical_exam_id = localStorage.getItem("selectedExamId"); + const package_code = localStorage.getItem("package_code"); + if (!physical_exam_id || !package_code) { + alert("体检ID或套餐代码不存在"); + return; + } + const res = await signIn(Number(physical_exam_id), Number(package_code)); + if (res.Status === 200) { + if (res.Data.is_success === 0) { + return; + } else { + console.log(res.Data); + + alert(res.Message); + } + } else { + alert(res.Message); + } + }; + return (
- 体检知情同意书确认 + + + {!isInstructionStep && currentPdfName ? `${currentPdfName}` : "检测项目知情同意书确认"} +
-
- -
+ {isInstructionStep ? ( +
+

请阅读注意事项并完成首次签名确认,之后系统将逐份展示知情同意书。

+ {/*

+ 每次签名都会保存,全部完成后会一次性提交签名列表。 +

*/} +
+ ) : ( +
+ +
+ )}
- 请阅读后在下方签名确认 + + {isInstructionStep + ? "请确认已阅读总说明后在下方签名" + : `当前展示第 ${currentStep + 1}/${totalRequiredSignatures}份,请签名确认`} +
{ @@ -18,48 +18,9 @@ const UI81: React.FC = () => { navigate("/UI9"); }; - const [ListData, setListData] = useState([]); - const [PackageInfo, setPackageInfo] = useState({}); useEffect(() => { - getListData(); }, []); - - const getListData = async () => { - const id_no = localStorage.getItem("lastIdCardNo"); - if (!id_no) { - alert("请先输入身份证号"); - return; - } - const res = await getPackagItemDetail(id_no as string); - if (res.Status === 200) { - // 处理数据:将 project_id 和 project_name 字符串分离为数组 - const processedData = res.Data.listPackDetail.map((item: any) => { - // 将 project_id 字符串按中文顿号分割为数组 - const project_ids = item.project_id - ? item.project_id.split("、").map((id: string) => id.trim()).filter((id: string) => id) - : []; - - // 将 project_name 字符串按中文顿号分割为数组 - const project_names = item.project_name - ? item.project_name.split("、").map((name: string) => name.trim()).filter((name: string) => name) - : []; - - return { - ...item, - project_ids, - project_names, - }; - }); - - setListData(processedData); - setPackageInfo(res.Data.packagItemInfo); - } else { - alert(`获取列表数据失败: ${res.Message}`); - } - }; - - return (
diff --git a/src/pages/UI9/UI9.tsx b/src/pages/UI9/UI9.tsx index 56d53f0..b0fa70e 100644 --- a/src/pages/UI9/UI9.tsx +++ b/src/pages/UI9/UI9.tsx @@ -21,6 +21,9 @@ const UI9: React.FC = () => { localStorage.removeItem("lastIdCardNo"); localStorage.removeItem("name"); localStorage.removeItem("gender"); + localStorage.removeItem("package_code"); + localStorage.removeItem("consentSignatureList"); + localStorage.removeItem("consentSignature"); navigate("/"); }, [navigate]);