更新知情同意书

This commit is contained in:
xianyi
2025-11-28 16:54:51 +08:00
parent 5d096b62d2
commit 55d7383db6
4 changed files with 252 additions and 132 deletions

View File

@@ -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 = () => {
<DecorLine />
<span className="basic-paragraph">
{localStorage.getItem("name")}{localStorage.getItem("gender") === "男" ? "先生" : "女士"}
{localStorage.getItem("name")}{localStorage.getItem("gender") === "男" ? "先生" : "女士"} <strong>{PackageInfo.package_name}</strong>
<br />
<br />
{PackageInfo.appointment_datetime}
{PackageInfo.appointment_datetime}
</span>
<div className="ui6-table-container">

View File

@@ -8,17 +8,33 @@ 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<number, number>;
onPageCountUpdate: (index: number, numPages: number) => void;
pageCount: number;
onPageCountUpdate: (numPages: number) => void;
loadMessage: string;
}>(({ pdfFiles, loading, error, pageCounts, onPageCountUpdate, loadMessage }) => {
documentKey: string;
}>(
({
pdfFile,
loading,
error,
pageCount,
onPageCountUpdate,
loadMessage,
documentKey,
}) => {
const pagesToRender = Math.max(pageCount, 1);
return (
<>
{loading && (
@@ -31,36 +47,44 @@ const PdfRenderer = React.memo<{
{error}
</div>
)}
{!loading &&
!error &&
pdfFiles.map((fileData, index) => (
<div key={index} style={{ marginBottom: "20px" }}>
{!loading && !error && !pdfFile && (
<div style={{ padding: "20px", textAlign: "center", fontSize: "28px" }}>
PDF
</div>
)}
{!loading && !error && pdfFile && (
<div style={{ marginBottom: "20px" }}>
<Document
file={{ data: fileData }}
key={documentKey}
file={{ data: pdfFile }}
loading=""
onLoadSuccess={({ numPages }) => onPageCountUpdate(index, numPages)}
onLoadSuccess={({ numPages }) => onPageCountUpdate(numPages)}
>
{Array.from(
{ length: pageCounts[index] || 0 },
(_, pageIndex) => (
{Array.from({ length: pagesToRender }, (_, pageIndex) => (
<Page
key={`pdf-${index}-page-${pageIndex + 1}`}
key={`pdf-page-${pageIndex + 1}`}
pageNumber={pageIndex + 1}
renderTextLayer={false}
renderAnnotationLayer={false}
width={900}
/>
)
)}
))}
</Document>
</div>
))}
)}
</>
);
});
}
);
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<Uint8Array[]>([]);
const [pageCounts, setPageCounts] =
useState<Record<number, number>>({});
const [pdfInfoList, setPdfInfoList] = useState<PdfMeta[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
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] 提交导检单签名exam_id=${examId}`
`[UI7] 提交导检单签名(第 1 次)exam_id=${examId}`
);
const res = await submitDaojiandanSign(examId, blob);
if (res.Status !== 200) {
throw new Error(res.Message || "提交签名失败");
const daojiandanRes = await submitDaojiandanSign(examId, blob);
if (daojiandanRes.Status !== 200) {
throw new Error(daojiandanRes.Message || "提交导检单签名失败");
}
window.electronAPI?.log("info", "[UI7] 签名提交成功");
localStorage.setItem("consentSignature", res.Data.pdf_url || "");
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] 完成第 ${currentStep + 1} 次签名,进入下一份 PDF`
);
} 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 (
<div className="basic-root">
<div className="basic-white-block">
<div className="basic-content">
<span className="basic-title"></span>
<span className="basic-title">
{!isInstructionStep && currentPdfName ? `${currentPdfName}` : "检测项目知情同意书确认"}
</span>
<DecorLine />
<div className="ui7-text-wrapper">
{isInstructionStep ? (
<div
style={{
padding: "40px",
fontSize: "28px",
lineHeight: "48px",
color: "#333",
}}
>
<p></p>
{/* <p style={{ marginTop: "12px" }}>
每次签名都会保存,全部完成后会一次性提交签名列表。
</p> */}
</div>
) : (
<div className="ui7-text-content">
<PdfRenderer
pdfFiles={pdfFiles}
pdfFile={currentPdfFile}
loading={loading}
error={error}
pageCounts={pageCounts}
pageCount={currentPdfPageCount}
onPageCountUpdate={handlePageCountUpdate}
loadMessage={loadMessage}
documentKey={`pdf-step-${currentStep}`}
/>
</div>
)}
</div>
<span className="ui7-text_4"></span>
<span className="ui7-text_4">
{isInstructionStep
? "请确认已阅读总说明后在下方签名"
: `当前展示第 ${currentStep + 1}/${totalRequiredSignatures}份,请签名确认`}
</span>
<div className="ui7-signature-wrapper">
<canvas

View File

@@ -4,7 +4,7 @@ import "../../assets/css/basic.css";
import { useNavigate } from "react-router-dom";
import ConfirmButton from "../../components/ConfirmButton";
import DecorLine from "../../components/DecorLine";
import { getPackagItemDetail } from "../../api/hisApi";
import { getPackagItemDetail, signIn } from "../../api/hisApi";
import errorIcon from "../../assets/error.png";
const UI81: React.FC = () => {
@@ -18,48 +18,9 @@ const UI81: React.FC = () => {
navigate("/UI9");
};
const [ListData, setListData] = useState<any[]>([]);
const [PackageInfo, setPackageInfo] = useState<any>({});
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 (
<div className="basic-root">
<div className="basic-white-block">

View File

@@ -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]);