使用接口
This commit is contained in:
@@ -77,20 +77,27 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* PDF 展示容器 */
|
||||
/* PDF 展示容器 - 改为滚动显示模式 */
|
||||
.ui8-pdf-container {
|
||||
height: 1000px;
|
||||
height: 1200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
touch-action: pan-y; /* 允许垂直触摸滑动 */
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE 和 Edge */
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.ui8-pdf-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* PDF 页面包装器 - 不再需要,保留以防兼容性 */
|
||||
.ui8-pdf-page-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -100,8 +107,6 @@
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
max-height: 1200px;
|
||||
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 向上滑动动画(下一页) */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { Document, Page, pdfjs } from "react-pdf";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
|
||||
import "./UI8.css";
|
||||
import "../../assets/css/basic.css";
|
||||
@@ -8,72 +8,137 @@ import BackButton from "../../components/BackButton";
|
||||
import ConfirmButton from "../../components/ConfirmButton";
|
||||
import ui8A from "../../assets/ui8A.png";
|
||||
import ui8B from "../../assets/ui8B.png";
|
||||
import testPdf from "../../assets/testPdf.pdf";
|
||||
import { getDaojiandanPdf } from "../../api/hisApi";
|
||||
|
||||
// 配置 PDF.js worker
|
||||
// 开发环境:Vite 会从根路径提供 public/pdf.worker.min.js
|
||||
// 生产环境:Electron 需要相对于 index.html 的路径
|
||||
const workerPath = new URL("pdf.worker.min.js", document.baseURI).href;
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = workerPath;
|
||||
// 独立的 PDF 渲染组件,使用 React.memo 避免不必要的重新渲染
|
||||
const PdfRenderer = React.memo<{
|
||||
pdfFiles: string[];
|
||||
loading: boolean;
|
||||
error: string;
|
||||
pageCounts: Record<number, number>;
|
||||
onPageCountUpdate: (index: number, numPages: number) => void;
|
||||
onDocumentLoadError: (error: Error) => void;
|
||||
}>(({ pdfFiles, loading, error, pageCounts, onPageCountUpdate, onDocumentLoadError }) => {
|
||||
return (
|
||||
<>
|
||||
{loading && <div className="ui8-loading">PDF加载中...</div>}
|
||||
{error && <div className="ui8-error">{error}</div>}
|
||||
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.log("info", `PDF Worker 路径: ${workerPath}`);
|
||||
window.electronAPI.log("info", `Base URI: ${document.baseURI}`);
|
||||
}
|
||||
{!loading &&
|
||||
!error &&
|
||||
pdfFiles.map((fileData, index) => (
|
||||
<div key={index} style={{ marginBottom: "20px", width: "100%" }}>
|
||||
<Document
|
||||
file={fileData}
|
||||
loading=""
|
||||
onLoadSuccess={({ numPages }) => onPageCountUpdate(index, numPages)}
|
||||
onLoadError={onDocumentLoadError}
|
||||
>
|
||||
{Array.from({ length: pageCounts[index] || 0 }, (_, pageIndex) => (
|
||||
<div key={`pdf-${index}-page-${pageIndex + 1}`} className="ui8-pdf-page-wrapper">
|
||||
<Page
|
||||
pageNumber={pageIndex + 1}
|
||||
renderTextLayer={false}
|
||||
renderAnnotationLayer={false}
|
||||
className="ui8-pdf-page"
|
||||
width={920}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
PdfRenderer.displayName = "PdfRenderer";
|
||||
|
||||
// 使用本地 PDF 文件进行测试
|
||||
const PDF_URL = testPdf;
|
||||
const UI8: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [numPages, setNumPages] = useState<number>(0);
|
||||
const [pageNumber, setPageNumber] = useState<number>(1);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [isPrinting, setIsPrinting] = useState<boolean>(false);
|
||||
const [pdfData, setPdfData] = useState<string | null>(null);
|
||||
const [isAnimating, setIsAnimating] = useState<boolean>(false);
|
||||
const [animationDirection, setAnimationDirection] = useState<"up" | "down" | null>(null);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const touchStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const [pdfFiles, setPdfFiles] = useState<string[]>([]);
|
||||
const [pageCounts, setPageCounts] = useState<Record<number, number>>({});
|
||||
const [originPdfUrls, setOriginPdfUrls] = useState<string[]>([]);
|
||||
|
||||
const getExamId = () => {
|
||||
const storedId = localStorage.getItem("selectedExamId");
|
||||
return storedId || "";
|
||||
};
|
||||
|
||||
const arrayBufferToDataUrl = (arrayBuffer: ArrayBuffer) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
if (typeof reader.result === "string") {
|
||||
resolve(reader.result);
|
||||
} else {
|
||||
reject(new Error("PDF 转换失败"));
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(new Error("PDF 读取失败"));
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchPdfDataUrl = async (url: string) => {
|
||||
try {
|
||||
if (window.electronAPI?.fetchPdf) {
|
||||
window.electronAPI.log("info", `[UI8] 通过 Electron 获取 PDF: ${url}`);
|
||||
const result = await window.electronAPI.fetchPdf(url);
|
||||
if (result.success && result.data) {
|
||||
const cleanBase64 = result.data.replace(/\s/g, "");
|
||||
return `data:application/pdf;base64,${cleanBase64}`;
|
||||
}
|
||||
throw new Error(result.error || "fetchPdf 返回失败");
|
||||
}
|
||||
|
||||
window.electronAPI?.log("info", `[UI8] 通过 fetch 获取 PDF: ${url}`);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`网络请求失败: ${response.status}`);
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return await arrayBufferToDataUrl(arrayBuffer);
|
||||
} catch (err) {
|
||||
throw new Error(`获取 PDF 失败: ${(err as Error).message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载PDF数据
|
||||
useEffect(() => {
|
||||
const loadPdf = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
window.electronAPI?.log("info", `开始加载PDF: ${PDF_URL}`);
|
||||
|
||||
// 本地文件直接使用,无需通过 fetchPdf
|
||||
if (PDF_URL.startsWith("/") || PDF_URL.startsWith("blob:") || PDF_URL.includes("assets")) {
|
||||
window.electronAPI?.log("info", `检测到本地PDF文件,直接加载: ${PDF_URL}`);
|
||||
setPdfData(PDF_URL);
|
||||
setLoading(false);
|
||||
return;
|
||||
setError("");
|
||||
setPdfFiles([]);
|
||||
setPageCounts({});
|
||||
|
||||
const examId = getExamId();
|
||||
window.electronAPI?.log("info", `[UI8] 开始获取导检单 PDF,exam_id=${examId}`);
|
||||
const res = await getDaojiandanPdf(parseInt(examId, 10));
|
||||
if (res.Status !== 200) {
|
||||
throw new Error(res.Message || "获取导检单PDF失败");
|
||||
}
|
||||
|
||||
// 远程 URL 需要通过 Electron 绕过 CORS
|
||||
if (window.electronAPI?.fetchPdf) {
|
||||
window.electronAPI.log("info", `通过Electron下载远程PDF: ${PDF_URL}`);
|
||||
const result = await window.electronAPI.fetchPdf(PDF_URL);
|
||||
if (result.success && result.data) {
|
||||
// 将base64转换为data URL
|
||||
window.electronAPI.log("info", `PDF下载成功,大小: ${result.data.length} bytes (base64)`);
|
||||
setPdfData(`data:application/pdf;base64,${result.data}`);
|
||||
} else {
|
||||
const errorMsg = `PDF下载失败: ${result.error || "未知错误"}`;
|
||||
window.electronAPI.log("error", errorMsg);
|
||||
setError(errorMsg);
|
||||
}
|
||||
} else {
|
||||
// 非Electron环境,直接使用URL
|
||||
window.electronAPI?.log("warn", "非Electron环境,直接使用URL加载");
|
||||
setPdfData(PDF_URL);
|
||||
const pdfUrl = res.Data?.pdf_url;
|
||||
if (!pdfUrl) {
|
||||
throw new Error("未获取到导检单 PDF");
|
||||
}
|
||||
|
||||
setOriginPdfUrls([pdfUrl]);
|
||||
|
||||
window.electronAPI?.log("info", `[UI8] 下载导检单 PDF: ${pdfUrl}`);
|
||||
const dataUrl = await fetchPdfDataUrl(pdfUrl);
|
||||
setPdfFiles([dataUrl]);
|
||||
} catch (err) {
|
||||
const errorMsg = `PDF fetch error: ${err}`;
|
||||
const errorMsg = `导检单PDF获取失败: ${(err as Error).message || err}`;
|
||||
console.error(errorMsg);
|
||||
window.electronAPI?.log("error", errorMsg);
|
||||
setError("PDF加载失败,请检查网络连接");
|
||||
window.electronAPI?.log("error", `[UI8] ${errorMsg}`);
|
||||
setError("PDF 获取失败,请稍后重试");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -82,12 +147,12 @@ const UI8: React.FC = () => {
|
||||
loadPdf();
|
||||
}, []);
|
||||
|
||||
const handleBack = () => {
|
||||
const handleBack = useCallback(() => {
|
||||
navigate(-1);
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
// 打印PDF功能
|
||||
const handleConfirm = async () => {
|
||||
const handleConfirm = useCallback(async () => {
|
||||
if (!window.electronAPI?.printPdf) {
|
||||
const errorMsg = "打印功能不可用,请在 Electron 环境中运行";
|
||||
window.electronAPI?.log("error", errorMsg);
|
||||
@@ -95,7 +160,8 @@ const UI8: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pdfData) {
|
||||
const primaryPdf = pdfFiles[0] || originPdfUrls[0];
|
||||
if (!primaryPdf) {
|
||||
const errorMsg = "PDF 尚未加载完成,请稍候";
|
||||
window.electronAPI?.log("warn", errorMsg);
|
||||
alert(errorMsg);
|
||||
@@ -104,9 +170,11 @@ const UI8: React.FC = () => {
|
||||
|
||||
setIsPrinting(true);
|
||||
try {
|
||||
// 本地文件直接传原始路径,远程文件传 base64 data URI
|
||||
const printData = pdfData.startsWith("data:") ? pdfData : PDF_URL;
|
||||
const dataType = pdfData.startsWith("data:") ? "base64数据" : "本地文件路径";
|
||||
const printData =
|
||||
primaryPdf.startsWith("data:") && pdfFiles[0]
|
||||
? primaryPdf
|
||||
: originPdfUrls[0];
|
||||
const dataType = printData.startsWith("data:") ? "base64数据" : "远程文件路径";
|
||||
window.electronAPI.log("info", `开始打印PDF (${dataType}): ${printData.substring(0, 100)}...`);
|
||||
|
||||
const result = await window.electronAPI.printPdf(printData);
|
||||
@@ -124,202 +192,41 @@ const UI8: React.FC = () => {
|
||||
alert("打印失败,请重试");
|
||||
} finally {
|
||||
setIsPrinting(false);
|
||||
navigate("/UI9");
|
||||
}
|
||||
};
|
||||
}, [originPdfUrls, pdfFiles]);
|
||||
|
||||
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
||||
window.electronAPI?.log("info", `PDF渲染成功,共 ${numPages} 页`);
|
||||
setNumPages(numPages);
|
||||
const handlePageCountUpdate = useCallback((index: number, numPages: number) => {
|
||||
window.electronAPI?.log("info", `[UI8] PDF渲染成功 (index=${index}),共 ${numPages} 页`);
|
||||
setPageCounts((prev) => {
|
||||
if (prev[index] === numPages) return prev;
|
||||
return { ...prev, [index]: numPages };
|
||||
});
|
||||
setLoading(false);
|
||||
setError("");
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDocumentLoadError = (error: Error) => {
|
||||
const onDocumentLoadError = useCallback((error: Error) => {
|
||||
const errorMsg = `PDF渲染失败: ${error.message || error}`;
|
||||
console.error("PDF load error:", error);
|
||||
window.electronAPI?.log("error", errorMsg);
|
||||
window.electronAPI?.log("error", `PDF数据: ${pdfData?.substring(0, 100)}...`);
|
||||
setError("PDF 加载失败,请检查网络连接");
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const goToPrevPage = useCallback(() => {
|
||||
if (isAnimating || pageNumber <= 1) return;
|
||||
setIsAnimating(true);
|
||||
setAnimationDirection("down");
|
||||
setTimeout(() => {
|
||||
setPageNumber((prev) => Math.max(1, prev - 1));
|
||||
setIsAnimating(false);
|
||||
setAnimationDirection(null);
|
||||
}, 300);
|
||||
}, [isAnimating, pageNumber]);
|
||||
|
||||
const goToNextPage = useCallback(() => {
|
||||
if (isAnimating || pageNumber >= numPages) return;
|
||||
setIsAnimating(true);
|
||||
setAnimationDirection("up");
|
||||
setTimeout(() => {
|
||||
setPageNumber((prev) => Math.min(numPages, prev + 1));
|
||||
setIsAnimating(false);
|
||||
setAnimationDirection(null);
|
||||
}, 300);
|
||||
}, [isAnimating, pageNumber, numPages]);
|
||||
|
||||
// 监听触摸和鼠标事件实现上下滑动翻页
|
||||
useEffect(() => {
|
||||
const container = scrollContainerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
|
||||
// 触摸事件处理
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
const touch = e.touches[0];
|
||||
touchStartRef.current = {
|
||||
x: touch.clientX,
|
||||
y: touch.clientY,
|
||||
};
|
||||
window.electronAPI?.log("info", `触摸开始: (${touch.clientX}, ${touch.clientY})`);
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: TouchEvent) => {
|
||||
if (!touchStartRef.current) return;
|
||||
|
||||
const touch = e.changedTouches[0];
|
||||
const deltaX = touch.clientX - touchStartRef.current.x;
|
||||
const deltaY = touch.clientY - touchStartRef.current.y;
|
||||
|
||||
window.electronAPI?.log("info", `触摸结束: delta(${deltaX}, ${deltaY})`);
|
||||
|
||||
// 判断是否为有效滑动(至少50px,且垂直滑动大于水平滑动)
|
||||
const minSwipeDistance = 50;
|
||||
if (Math.abs(deltaY) > minSwipeDistance && Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||
if (deltaY < 0) {
|
||||
// 向上滑动 = 下一页
|
||||
window.electronAPI?.log("info", "向上滑动 -> 下一页");
|
||||
goToNextPage();
|
||||
} else if (deltaY > 0) {
|
||||
// 向下滑动 = 上一页
|
||||
window.electronAPI?.log("info", "向下滑动 -> 上一页");
|
||||
goToPrevPage();
|
||||
}
|
||||
}
|
||||
|
||||
touchStartRef.current = null;
|
||||
};
|
||||
|
||||
const handleTouchCancel = () => {
|
||||
touchStartRef.current = null;
|
||||
};
|
||||
|
||||
// 鼠标事件处理(作为触摸的后备方案)
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
isDragging = true;
|
||||
startY = e.clientY;
|
||||
window.electronAPI?.log("info", `鼠标按下: (${e.clientX}, ${e.clientY})`);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
// 防止页面滚动
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const deltaY = e.clientY - startY;
|
||||
window.electronAPI?.log("info", `鼠标释放: deltaY=${deltaY}`);
|
||||
|
||||
// 判断是否为有效滑动(至少50px)
|
||||
const minSwipeDistance = 50;
|
||||
if (Math.abs(deltaY) > minSwipeDistance) {
|
||||
if (deltaY < 0) {
|
||||
// 向上拖动 = 下一页
|
||||
window.electronAPI?.log("info", "向上拖动 -> 下一页");
|
||||
goToNextPage();
|
||||
} else if (deltaY > 0) {
|
||||
// 向下拖动 = 上一页
|
||||
window.electronAPI?.log("info", "向下拖动 -> 上一页");
|
||||
goToPrevPage();
|
||||
}
|
||||
}
|
||||
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
// 注册触摸事件
|
||||
container.addEventListener("touchstart", handleTouchStart, { passive: true });
|
||||
container.addEventListener("touchend", handleTouchEnd, { passive: true });
|
||||
container.addEventListener("touchcancel", handleTouchCancel, { passive: true });
|
||||
|
||||
// 鼠标离开处理
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
// 注册鼠标事件(作为后备)
|
||||
container.addEventListener("mousedown", handleMouseDown);
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
container.addEventListener("mouseup", handleMouseUp);
|
||||
container.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
return () => {
|
||||
// 清理触摸事件
|
||||
container.removeEventListener("touchstart", handleTouchStart);
|
||||
container.removeEventListener("touchend", handleTouchEnd);
|
||||
container.removeEventListener("touchcancel", handleTouchCancel);
|
||||
// 清理鼠标事件
|
||||
container.removeEventListener("mousedown", handleMouseDown);
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
container.removeEventListener("mouseup", handleMouseUp);
|
||||
container.removeEventListener("mouseleave", handleMouseLeave);
|
||||
};
|
||||
}, [goToNextPage, goToPrevPage]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="basic-root">
|
||||
<div className="basic-white-block">
|
||||
<div className="basic-content">
|
||||
<div className="ui8-pdf-container" ref={scrollContainerRef}>
|
||||
{loading && <div className="ui8-loading">PDF加载中...</div>}
|
||||
{error && <div className="ui8-error">{error}</div>}
|
||||
|
||||
{pdfData && (
|
||||
<Document
|
||||
file={pdfData}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={onDocumentLoadError}
|
||||
loading=""
|
||||
>
|
||||
<div
|
||||
className={`ui8-pdf-page-wrapper ${
|
||||
animationDirection === "up" ? "slide-up" :
|
||||
animationDirection === "down" ? "slide-down" : ""
|
||||
}`}
|
||||
>
|
||||
<Page
|
||||
pageNumber={pageNumber}
|
||||
renderTextLayer={false}
|
||||
renderAnnotationLayer={false}
|
||||
className="ui8-pdf-page"
|
||||
width={920}
|
||||
/>
|
||||
</div>
|
||||
</Document>
|
||||
)}
|
||||
|
||||
{/* {numPages > 0 && (
|
||||
<>
|
||||
|
||||
<div className="ui8-pdf-controls">
|
||||
<span className="ui8-page-info">
|
||||
{pageNumber} / {numPages}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)} */}
|
||||
<div className="ui8-pdf-container">
|
||||
<PdfRenderer
|
||||
pdfFiles={pdfFiles}
|
||||
loading={loading}
|
||||
error={error}
|
||||
pageCounts={pageCounts}
|
||||
onPageCountUpdate={handlePageCountUpdate}
|
||||
onDocumentLoadError={onDocumentLoadError}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ui8-right-section">
|
||||
|
||||
Reference in New Issue
Block a user