| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- import { app, BrowserWindow, ipcMain, dialog } from "electron";
- import { fileURLToPath } from "url";
- import path from "path";
- import { spawn } from "child_process";
- import fs from "fs";
- import Papa from "./libs/papaparse/papaparse.js";
- process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
- const __filename = fileURLToPath(import.meta.url);
- const __dirname = path.dirname(__filename);
- const isDev = process.env.NODE_ENV === "development";
- let mainWindow = null;
- function parsePythonOutput(output) {
- const text = output.trim();
- if (!text) return null;
- try {
- return JSON.parse(text);
- } catch {
- const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
- for (let i = lines.length - 1; i >= 0; i -= 1) {
- try {
- return JSON.parse(lines[i]);
- } catch {
- // try previous line
- }
- }
- return text;
- }
- }
- function normalizeCsvPath(filePath) {
- if (typeof filePath !== "string" || !filePath.trim()) {
- throw new Error(`CSV 路径无效: ${filePath}`);
- }
- let resolvedPath = path.normalize(filePath.trim());
- if (fs.existsSync(resolvedPath)) {
- try {
- resolvedPath = fs.realpathSync.native(resolvedPath);
- } catch {
- // keep normalized path
- }
- }
- return resolvedPath;
- }
- function getServesBaseDir() {
- return isDev
- ? path.join(__dirname, "../serves")
- : path.join(process.resourcesPath, "app.asar.unpacked", "serves");
- }
- function hasEntryModule(dir) {
- if (!fs.existsSync(dir)) return false;
- return fs
- .readdirSync(dir)
- .some(
- (file) =>
- file.startsWith("entry.") &&
- (file.endsWith(".pyd") || file.endsWith(".so"))
- );
- }
- function getPythonRunnerBackend(jiguangDir, isWin) {
- const runnerPy = path.join(jiguangDir, "runner.py");
- if (fs.existsSync(runnerPy) && hasEntryModule(jiguangDir)) {
- return {
- command: isWin ? "python" : "python3.11",
- prefixArgs: [runnerPy],
- cwd: jiguangDir,
- type: "python-runner",
- };
- }
- return null;
- }
- function getNuitkaRunnerBackend(jiguangDir, isWin) {
- const runnerExeName = isWin ? "api_runner.exe" : "api_runner";
- const runnerCandidates = [
- { exe: path.join(jiguangDir, "runner.dist", runnerExeName), cwd: path.join(jiguangDir, "runner.dist") },
- { exe: path.join(jiguangDir, runnerExeName), cwd: jiguangDir },
- ];
- for (const candidate of runnerCandidates) {
- if (fs.existsSync(candidate.exe)) {
- return {
- command: candidate.exe,
- prefixArgs: [],
- cwd: candidate.cwd,
- type: "nuitka-runner",
- };
- }
- }
- return null;
- }
- function getLegacyBackend(distDir, isWin) {
- const legacyExePath = path.join(distDir, isWin ? "api_test.exe" : "api_test");
- if (fs.existsSync(legacyExePath)) {
- return {
- command: legacyExePath,
- prefixArgs: [],
- cwd: distDir,
- type: "legacy-exe",
- };
- }
- return null;
- }
- /**
- * 解析 Python 后端启动方式。
- *
- * 开发环境:python runner.py → api_runner.exe → api_test.exe
- * 生产环境:api_runner.exe → api_test.exe(禁止 python runner.py)
- */
- function resolvePythonBackend() {
- const servesBase = getServesBaseDir();
- const jiguangDir = path.join(servesBase, "JiGuang-program");
- const distDir = path.join(servesBase, "dist");
- const isWin = process.platform === "win32";
- if (isDev) {
- return (
- getPythonRunnerBackend(jiguangDir, isWin) ||
- getNuitkaRunnerBackend(jiguangDir, isWin) ||
- getLegacyBackend(distDir, isWin)
- );
- }
- return getNuitkaRunnerBackend(jiguangDir, isWin) || getLegacyBackend(distDir, isWin);
- }
- function runPythonBackend(apiName, params = {}) {
- const backend = resolvePythonBackend();
- if (!backend) {
- const servesBase = getServesBaseDir();
- return Promise.reject(
- `未找到 Python 后端。请确认以下路径之一存在:\n` +
- ` - ${path.join(servesBase, "JiGuang-program", "runner.dist", "api_runner.exe")}\n` +
- ` - ${path.join(servesBase, "JiGuang-program", "api_runner.exe")}\n` +
- ` - ${path.join(servesBase, "dist", "api_test.exe")}`
- );
- }
- const payload = Buffer.from(JSON.stringify(params)).toString("base64");
- const args = [...backend.prefixArgs, apiName, payload];
- console.log(
- `🐍 启动 Python 后端 [${backend.type}]:`,
- backend.command,
- args.join(" ")
- );
- return new Promise((resolve, reject) => {
- const child = spawn(backend.command, args, {
- cwd: backend.cwd,
- encoding: "utf8",
- windowsHide: true,
- env: {
- ...process.env,
- PYTHONIOENCODING: "utf-8",
- PYTHONUTF8: "1",
- },
- });
- let stdoutData = "";
- let stderrData = "";
- child.stdout.on("data", (data) => {
- const text = data.toString();
- stdoutData += text;
- console.log("🐍 Python stdout:", text);
- });
- child.stderr.on("data", (data) => {
- const text = data.toString();
- stderrData += text;
- console.error("🐍 Python stderr:", text);
- });
- child.on("error", (err) => {
- reject(`启动 Python 后端失败: ${err.message}`);
- });
- child.on("close", (code) => {
- const output = stdoutData.trim();
- if (code !== 0) {
- reject(
- `Python 后端退出码 ${code}\n` +
- (stderrData || output || "无错误输出")
- );
- return;
- }
- const parsed = parsePythonOutput(output);
- if (typeof parsed === "string") {
- console.warn("⚠️ Python 输出不是 JSON:", output);
- }
- resolve(parsed);
- });
- });
- }
- function createWindow() {
- mainWindow = new BrowserWindow({
- width: 1400,
- height: 900,
- minWidth: 800,
- minHeight: 600,
- webPreferences: {
- preload: path.join(__dirname, "./preload.mjs"),
- contextIsolation: true,
- nodeIntegration: false,
- allowRunningInsecureContent: false,
- },
- icon: path.join(__dirname, "../src/assets/images/login/bg.png"), // 开发环境的图标
- });
- //环境判断引入不同的页面
- if (process.env.NODE_ENV === "development") {
- mainWindow.loadURL("http://localhost:5173");
- mainWindow.webContents.openDevTools();
- } else {
- //生产环境将引入打包好的文件
- mainWindow.loadFile(path.join(__dirname, "../dist/index.html"));
- }
- ipcMain.handle("run-python-exe", async (_event, apiName, params = {}) => {
- return runPythonBackend(apiName, params);
- });
- ipcMain.handle("get-install-path", async () => {
- return process.execPath; // 获取可执行文件路径
- });
- // 监听前端请求,读取 CSV 文件
- ipcMain.handle("read-csv", async (event, filePath) => {
- try {
- const resolvedPath = normalizeCsvPath(filePath);
- if (!fs.existsSync(resolvedPath)) {
- console.warn("CSV 文件不存在,返回空列表:", resolvedPath);
- return [];
- }
- const csvData = fs.readFileSync(resolvedPath, "utf-8");
- const parsedData = Papa.parse(csvData, {
- header: true,
- skipEmptyLines: true, // ← 这个也可以帮助跳过完全空的行
- transformHeader: (header) => header.trim(), // ← 这个也可以帮助跳过完全空的行
- });
- // 过滤掉所有字段都为空的行
- const cleanData = parsedData.data.filter((row) =>
- Object.values(row).some((val) => val && val.trim() !== "")
- );
- return cleanData; // 返回解析后的数据
- } catch (error) {
- console.error("读取 CSV 失败:", error);
- return { error: error.message };
- }
- });
- // 监听 get-file-path 事件 监听渲染进程请求文件路径
- ipcMain.handle("get-file-path", async () => {
- try {
- const { filePaths } = await dialog.showOpenDialog({
- properties: ["openFile", "multiSelections"], // ✅ 允许选择多个文件
- });
- if (filePaths.length > 0) {
- console.log("用户选择的文件路径:", filePaths[0]);
- return filePaths[0]; // 返回文件路径
- } else {
- console.log("用户取消了选择");
- return null; // 用户没有选择文件
- }
- } catch (error) {
- console.error("获取文件路径失败:", error);
- return null;
- }
- });
- mainWindow.on("close", (event) => {
- event.preventDefault(); // ✅ 阻止默认关闭行为
- const choice = dialog.showMessageBoxSync(mainWindow, {
- type: "question",
- buttons: ["取消", "退出"],
- defaultId: 0, // 默认选中 "取消"
- title: "确认退出",
- message: "退出程序正在分析中的数据会丢失,确定要退出程序吗?",
- });
- if (choice === 1) {
- // ✅ 直接移除监听器,允许窗口关闭
- mainWindow.removeAllListeners("close");
- mainWindow.close();
- }
- });
- mainWindow.on("closed", () => {
- mainWindow = null; // ✅ 窗口关闭后清空引用
- });
- }
- app.whenReady().then(createWindow);
- app.on("window-all-closed", () => {
- if (process.platform !== "darwin") app.quit();
- });
- app.on("activate", () => {
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
- });
- export { createWindow };
|