Files
yuanhe-checkin-electron/electron/main.js
2025-11-27 15:35:36 +08:00

330 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const https = require("https");
const http = require("http");
const fs = require("fs");
const os = require("os");
let mainWindow = null;
const { Worker } = require("worker_threads");
const log = require("electron-log");
// 配置日志输出
log.transports.file.level = "info";
log.transports.file.resolvePath = () =>
path.join(path.dirname(app.getPath("exe")), "logs", "main.log");
log.info("App starting...");
// 监听渲染进程日志
ipcMain.on("log-message", (event, { level, message }) => {
if (log[level]) {
log[level](`[Renderer] ${message}`);
} else {
log.info(`[Renderer] ${message}`);
}
});
let idCardWorker = null;
function createIdCardWorker() {
if (idCardWorker) return;
log.info("Creating IDCard Worker...");
idCardWorker = new Worker(path.join(__dirname, "idcard-worker.js"));
idCardWorker.on("message", (msg) => {
if (!mainWindow) return;
if (msg.type === "data") {
log.info("IDCard data received");
mainWindow.webContents.send("idcard-data", { payload: msg.payload });
} else if (msg.type === "error") {
log.error("IDCard worker error message:", msg.payload);
mainWindow.webContents.send("idcard-error", { payload: msg.payload });
} else if (msg.type === "log") {
log.info("[Worker]", msg.payload);
}
});
idCardWorker.on("error", (err) => {
log.error("Worker thread error:", err);
});
idCardWorker.on("exit", (code) => {
if (code !== 0) log.error(`Worker stopped with exit code ${code}`);
else log.info("Worker stopped gracefully");
idCardWorker = null;
});
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1080,
height: 1920,
fullscreen: true, // 全屏
frame: false, // 无边框
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: false,
contextIsolation: true,
},
});
// 启用触摸事件支持Windows 触摸屏)
mainWindow.webContents.on("did-finish-load", () => {
// 注入触摸事件支持脚本
mainWindow.webContents
.executeJavaScript(
`
// 确保触摸事件可用
if (navigator.maxTouchPoints === 0) {
console.log('[Electron] 未检测到触摸设备,将使用鼠标事件模拟');
} else {
console.log('[Electron] 检测到触摸设备,触摸点数:', navigator.maxTouchPoints);
}
`
)
.catch((err) => log.error("Failed to inject touch support:", err));
});
const isDev = !app.isPackaged;
if (isDev) {
mainWindow.loadURL("http://localhost:5173");
// 打开开发者工具
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, "../dist/index.html"));
}
}
// 处理PDF获取请求绕过CORS
ipcMain.handle("fetch-pdf", async (event, pdfUrl) => {
return new Promise((resolve, reject) => {
const protocol = pdfUrl.startsWith("https") ? https : http;
protocol
.get(pdfUrl, (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", () => {
const buffer = Buffer.concat(chunks);
resolve({
success: true,
data: buffer.toString("base64"),
});
});
redirectResponse.on("error", (error) => {
reject({ success: false, error: error.message });
});
})
.on("error", (error) => {
reject({ success: false, error: error.message });
});
} else {
const chunks = [];
response.on("data", (chunk) => chunks.push(chunk));
response.on("end", () => {
const buffer = Buffer.concat(chunks);
resolve({
success: true,
data: buffer.toString("base64"),
});
});
response.on("error", (error) => {
reject({ success: false, error: error.message });
});
}
})
.on("error", (error) => {
reject({ success: false, error: error.message });
});
});
});
ipcMain.handle("get-printers", async (event) => {
try {
const webContents = event.sender;
if (!webContents) {
return { success: false, error: "No webContents available" };
}
if (typeof webContents.getPrintersAsync === "function") {
const printers = await webContents.getPrintersAsync();
return { success: true, printers };
}
const printers = webContents.getPrinters();
return { success: true, printers };
} catch (error) {
log.error("Failed to get printers:", error);
return { success: false, error: error.message };
}
});
// 处理PDF打印请求
ipcMain.handle("print-pdf", async (event, pdfDataOrUrl, printOptions = {}) => {
let printWindow = null;
let tempFilePath = null;
const targetPrinterName = printOptions?.printerName;
try {
let pdfPath = pdfDataOrUrl;
// 如果是 base64 data URI转换为临时文件
if (pdfDataOrUrl.startsWith("data:application/pdf;base64,")) {
const base64Data = pdfDataOrUrl.replace(
"data:application/pdf;base64,",
""
);
const buffer = Buffer.from(base64Data, "base64");
// 创建临时文件
tempFilePath = path.join(os.tmpdir(), `print_${Date.now()}.pdf`);
fs.writeFileSync(tempFilePath, buffer);
pdfPath = `file://${tempFilePath}`;
log.info("PDF written to temp file:", tempFilePath);
}
// 如果是相对路径Vite 打包后的资源),转换为绝对路径
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);
} else {
// 开发环境Vite 会处理,保持原样
pdfPath = `http://localhost:5173${pdfDataOrUrl}`;
log.info("Using dev server PDF:", pdfPath);
}
}
// 如果已经是 file:// 或 http(s):// 协议,保持原样
else if (
!pdfDataOrUrl.startsWith("file://") &&
!pdfDataOrUrl.startsWith("http")
) {
pdfPath = `file://${pdfDataOrUrl}`;
}
// 创建一个隐藏的窗口用于加载PDF
printWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
webPreferences: {
plugins: true, // 启用插件以支持 PDF 查看
},
});
// 加载PDF
await printWindow.loadURL(pdfPath);
log.info("PDF loaded for printing");
// 等待更长时间确保 PDF 完全渲染
await new Promise((resolve) => setTimeout(resolve, 2000));
// 静默打印(直接调用系统打印对话框)
return new Promise((resolve) => {
const basePrintOptions = {
silent: Boolean(targetPrinterName),
printBackground: true,
margins: {
marginType: "none",
},
};
if (targetPrinterName) {
basePrintOptions.deviceName = targetPrinterName;
}
printWindow.webContents.print(
basePrintOptions,
(success, errorType) => {
// 清理临时文件
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
log.info("Temp file deleted");
} 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 });
}
}
);
});
} catch (error) {
log.error("Print error:", error);
// 清理临时文件
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
} catch (err) {
log.error("Failed to delete temp file:", err);
}
}
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
return { success: false, error: error.message };
}
});
app.whenReady().then(() => {
createWindow();
createIdCardWorker();
// IPC <20><><EFBFBD><EFBFBD>
ipcMain.handle("start_idcard_listen", () => {
if (idCardWorker) {
idCardWorker.postMessage("start");
} else {
createIdCardWorker();
idCardWorker.postMessage("start");
}
return "started";
});
ipcMain.handle("stop_idcard_listen", () => {
if (idCardWorker) {
idCardWorker.postMessage("stop");
}
return "stopped";
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on("window-all-closed", () => {
if (idCardWorker) {
idCardWorker.terminate();
}
if (process.platform !== "darwin") {
app.quit();
}
});