330 lines
9.3 KiB
JavaScript
330 lines
9.3 KiB
JavaScript
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 ����
|
|
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();
|
|
}
|
|
});
|