修复打印和exam_id问题

This commit is contained in:
xianyi
2025-12-03 14:33:23 +08:00
parent 408221fc66
commit 60ac086d7d
6 changed files with 420 additions and 185 deletions

View File

@@ -4,6 +4,7 @@ const https = require("https");
const http = require("http");
const fs = require("fs");
const os = require("os");
const ptp = require("pdf-to-printer");
let mainWindow = null;
const { Worker } = require("worker_threads");
@@ -169,12 +170,11 @@ ipcMain.handle("get-printers", async (event) => {
// 处理PDF打印请求
ipcMain.handle("print-pdf", async (event, pdfDataOrUrl, printOptions = {}) => {
let printWindow = null;
let tempFilePath = null;
const targetPrinterName = printOptions?.printerName;
try {
let pdfPath = pdfDataOrUrl;
let pdfFilePath = null;
// 如果是 base64 data URI转换为临时文件
if (pdfDataOrUrl.startsWith("data:application/pdf;base64,")) {
@@ -182,95 +182,130 @@ ipcMain.handle("print-pdf", async (event, pdfDataOrUrl, printOptions = {}) => {
"data:application/pdf;base64,",
""
);
const buffer = Buffer.from(base64Data, "base64");
// 清理base64字符串中的空白字符
const cleanBase64 = base64Data.replace(/\s/g, "");
try {
const buffer = Buffer.from(cleanBase64, "base64");
// 验证PDF文件头%PDF
if (buffer.length < 4 || buffer.toString("ascii", 0, 4) !== "%PDF") {
throw new Error("无效的PDF数据文件头不正确");
}
// 创建临时文件
tempFilePath = path.join(os.tmpdir(), `print_${Date.now()}.pdf`);
tempFilePath = path.join(os.tmpdir(), `print_${Date.now()}_${Math.random().toString(36).substring(7)}.pdf`);
fs.writeFileSync(tempFilePath, buffer);
pdfPath = `file://${tempFilePath}`;
log.info("PDF written to temp file:", tempFilePath);
pdfFilePath = tempFilePath;
const fileSize = fs.statSync(tempFilePath).size;
log.info(`PDF written to temp file: ${tempFilePath}, size: ${fileSize} bytes`);
if (fileSize < 1024) {
throw new Error(`PDF文件大小异常${fileSize} bytes可能数据不完整`);
}
// 如果是相对路径Vite 打包后的资源),转换为绝对路径
} catch (err) {
log.error("Base64解码或文件写入失败:", err);
throw new Error(`PDF数据处理失败: ${err.message}`);
}
}
// 如果是HTTP/HTTPS URL先下载
else if (pdfDataOrUrl.startsWith("http://") || pdfDataOrUrl.startsWith("https://")) {
log.info("Downloading PDF from URL:", pdfDataOrUrl);
const protocol = pdfDataOrUrl.startsWith("https") ? https : http;
const buffer = await new Promise((resolve, reject) => {
protocol.get(pdfDataOrUrl, (response) => {
// 处理重定向
if (response.statusCode === 301 || response.statusCode === 302) {
const redirectUrl = response.headers.location;
protocol.get(redirectUrl, (redirectResponse) => {
const chunks = [];
redirectResponse.on("data", (chunk) => chunks.push(chunk));
redirectResponse.on("end", () => {
resolve(Buffer.concat(chunks));
});
redirectResponse.on("error", reject);
}).on("error", reject);
} else {
const chunks = [];
response.on("data", (chunk) => chunks.push(chunk));
response.on("end", () => {
resolve(Buffer.concat(chunks));
});
response.on("error", reject);
}
}).on("error", reject);
});
// 验证PDF文件头
if (buffer.length < 4 || buffer.toString("ascii", 0, 4) !== "%PDF") {
throw new Error("下载的文件不是有效的PDF");
}
tempFilePath = path.join(os.tmpdir(), `print_${Date.now()}_${Math.random().toString(36).substring(7)}.pdf`);
fs.writeFileSync(tempFilePath, buffer);
pdfFilePath = tempFilePath;
const fileSize = fs.statSync(tempFilePath).size;
log.info(`PDF downloaded to temp file: ${tempFilePath}, size: ${fileSize} bytes`);
}
// 如果是本地文件路径
else if (pdfDataOrUrl.startsWith("file://")) {
pdfFilePath = pdfDataOrUrl.replace("file://", "");
}
// 如果是相对路径
else if (pdfDataOrUrl.startsWith("/assets/")) {
const isDev = !app.isPackaged;
if (!isDev) {
// 生产环境:从 dist 目录加载
const absolutePath = path.join(__dirname, "..", "dist", pdfDataOrUrl);
pdfPath = `file://${absolutePath}`;
log.info("Using bundled PDF:", absolutePath);
pdfFilePath = path.join(__dirname, "..", "dist", pdfDataOrUrl);
} else {
// 开发环境Vite 会处理,保持原样
pdfPath = `http://localhost:5173${pdfDataOrUrl}`;
log.info("Using dev server PDF:", pdfPath);
throw new Error("开发环境不支持相对路径PDF打印请使用完整URL或base64");
}
}
// 如果已经是 file:// 或 http(s):// 协议,保持原样
else if (
!pdfDataOrUrl.startsWith("file://") &&
!pdfDataOrUrl.startsWith("http")
) {
pdfPath = `file://${pdfDataOrUrl}`;
// 直接作为文件路径
else {
pdfFilePath = pdfDataOrUrl;
}
// 创建一个隐藏的窗口用于加载PDF
printWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
webPreferences: {
plugins: true, // 启用插件以支持 PDF 查看
},
});
// 验证文件存在
if (!pdfFilePath || !fs.existsSync(pdfFilePath)) {
throw new Error(`PDF文件不存在: ${pdfFilePath}`);
}
// 加载PDF
await printWindow.loadURL(pdfPath);
log.info("PDF loaded for printing");
// 验证文件大小
const stats = fs.statSync(pdfFilePath);
if (stats.size < 1024) {
throw new Error(`PDF文件大小异常${stats.size} bytes可能文件损坏`);
}
// 等待更长时间确保 PDF 完全渲染
await new Promise((resolve) => setTimeout(resolve, 2000));
log.info(`准备打印PDF: ${pdfFilePath}, 打印机: ${targetPrinterName || "默认打印机"}`);
// 静默打印(直接调用系统打印对话框)
return new Promise((resolve) => {
const basePrintOptions = {
silent: Boolean(targetPrinterName),
printBackground: true,
margins: {
marginType: "none",
},
// 使用 pdf-to-printer 打印
const printOptions_ptp = {
printer: targetPrinterName || undefined,
silent: true, // 静默打印
};
if (targetPrinterName) {
basePrintOptions.deviceName = targetPrinterName;
}
await ptp.print(pdfFilePath, printOptions_ptp);
log.info("PDF打印请求已发送成功");
printWindow.webContents.print(
basePrintOptions,
(success, errorType) => {
// 清理临时文件
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
// 延迟删除,确保打印任务已提交
setTimeout(() => {
fs.unlinkSync(tempFilePath);
log.info("Temp file deleted");
}, 5000);
} catch (err) {
log.error("Failed to delete temp file:", err);
}
}
// 关闭窗口
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
if (!success) {
log.error("Print failed:", errorType);
resolve({ success: false, error: errorType });
} else {
log.info("Print completed");
resolve({ success: true });
}
}
);
});
return { success: true };
} catch (error) {
log.error("Print error:", error);
@@ -283,10 +318,7 @@ ipcMain.handle("print-pdf", async (event, pdfDataOrUrl, printOptions = {}) => {
}
}
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
return { success: false, error: error.message };
return { success: false, error: error.message || String(error) };
}
});

350
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "Electron application compatible with Windows 7",
"main": "electron/main.js",
"scripts": {
"dev": "concurrently \"vite\" \"wait-on tcp:5173 && electron .\"",
"dev": "concurrently \"vite --host\" \"wait-on tcp:5173 && electron .\"",
"build": "vite build",
"pack": "npm run build && electron-builder --dir",
"pack:ia32": "npm run build && electron-builder --dir --ia32",
@@ -20,6 +20,7 @@
"electron-log": "^5.4.3",
"iconv-lite": "^0.7.0",
"koffi": "^2.14.1",
"pdf-to-printer": "^5.6.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-pdf": "5.7.2",

View File

@@ -67,6 +67,7 @@ export interface PackagItemInfo {
package_code: string;
package_name: string;
registration_time: string;
physical_exam_id: number;
}
// 套餐详情

View File

@@ -34,6 +34,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 || "");
localStorage.setItem("selectedExamId", res.Data.packagItemInfo.physical_exam_id.toString() || "0");
// 处理数据:将 project_id 和 project_name 字符串分离为数组
const processedData = res.Data.listPackDetail.map((item: any) => {
// 将 project_id 字符串按中文顿号分割为数组

View File

@@ -291,49 +291,73 @@ const UI8: React.FC = () => {
return;
}
const primaryPdf = pdfFiles[0] || originPdfUrls[0];
if (!primaryPdf) {
if (printers.length > 0 && !selectedPrinter) {
alert("请先选择打印机");
return;
}
if (pdfFiles.length === 0 && originPdfUrls.length === 0) {
const errorMsg = "PDF 尚未加载完成,请稍候";
window.electronAPI?.log("warn", errorMsg);
alert(errorMsg);
return;
}
if (printers.length > 0 && !selectedPrinter) {
alert("请先选择打印机");
return;
}
setIsPrinting(true);
try {
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)}...`);
// 优先使用已加载的 base64 数据,确保数据完整
let printData: string;
let dataType: string;
if (pdfFiles[0] && pdfFiles[0].startsWith("data:application/pdf;base64,")) {
printData = pdfFiles[0];
dataType = "base64数据";
// 验证 base64 数据长度至少应该有几KB
const base64Part = printData.replace("data:application/pdf;base64,", "");
const estimatedSize = (base64Part.length * 3) / 4;
window.electronAPI.log("info", `使用base64数据打印估算大小: ${Math.round(estimatedSize)} bytes`);
if (estimatedSize < 1024) {
window.electronAPI.log("warn", "base64数据可能不完整尝试使用原始URL");
// 如果base64数据太小回退到URL
if (originPdfUrls[0]) {
printData = originPdfUrls[0];
dataType = "远程文件路径";
}
}
} else if (originPdfUrls[0]) {
printData = originPdfUrls[0];
dataType = "远程文件路径";
} else {
throw new Error("没有可用的PDF数据");
}
window.electronAPI.log("info", `开始打印PDF (${dataType}): ${dataType === "base64数据" ? `base64长度${printData.length}` : printData.substring(0, 100)}...`);
const printResult = await window.electronAPI.printPdf(
printData,
selectedPrinter ? { printerName: selectedPrinter } : undefined
);
if (!printResult.success) {
const errorMsg = `打印失败: ${printResult.error || "未知错误"}`;
window.electronAPI.log("error", errorMsg);
alert(errorMsg);
} else {
window.electronAPI.log("info", "PDF打印请求已发送");
return;
}
window.electronAPI.log("info", "PDF打印请求已发送成功");
navigate("/UI81");
} catch (error) {
const errorMsg = `Print error: ${error}`;
const errorMsg = `打印错误: ${(error as Error).message || error}`;
console.error(errorMsg);
window.electronAPI.log("error", errorMsg);
alert("打印失败,请重试");
} finally {
setIsPrinting(false);
navigate("/UI81");
}
}, [originPdfUrls, pdfFiles, printers.length, selectedPrinter]);
}, [originPdfUrls, pdfFiles, printers.length, selectedPrinter, navigate]);
const handlePageCountUpdate = useCallback((index: number, numPages: number) => {
window.electronAPI?.log("info", `[UI8] PDF渲染成功 (index=${index}),共 ${numPages}`);