+ 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}份,请签名确认`}
+