MikiVL cb2c186f7f
Some checks are pending
Build Windows Installer / build-windows (push) Waiting to run
添加 GitHub Actions Windows 构建流程
- 新增 .github/workflows/build-windows.yml,在 windows-latest runner 上构建 NSIS 安装包
- 修复 PYTHON_BIN 在 Windows 下需要 .exe 后缀
- 更新 extraResources 为目录式 glob,同时兼容 macOS (main) 和 Windows (main.exe)
- package.json 新增 win/nsis electron-builder 目标配置

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 23:02:53 +08:00

92 lines
2.9 KiB
JavaScript

const { ipcMain, dialog, app, shell } = require("electron");
const path = require("path");
const fs = require("fs");
const { spawn } = require("child_process");
const { listTemplates, saveTemplate, updateTemplate, deleteTemplate } = require("../db/templateDb");
const APPDATA_DIR = path.join(app.getPath("userData"), "excel-batch-editor", "templates");
fs.mkdirSync(APPDATA_DIR, { recursive: true });
const PYTHON_BIN = app.isPackaged
? path.join(
process.resourcesPath,
"python",
process.platform === "win32" ? "main.exe" : "main"
)
: path.join(__dirname, "../../python/main.py");
function callPython(payload) {
return new Promise((resolve, reject) => {
const isPackaged = app.isPackaged;
const proc = isPackaged
? spawn(PYTHON_BIN)
: spawn(process.env.PYTHON_PATH || "python3", [PYTHON_BIN]);
let stdout = "";
proc.stdout.on("data", (d) => (stdout += d));
proc.stderr.on("data", (d) => console.error("[python]", d.toString()));
proc.on("close", () => {
try {
resolve(JSON.parse(stdout.trim()));
} catch (e) {
reject(new Error("Python 返回了无效 JSON: " + stdout));
}
});
proc.on("error", (e) => reject(new Error("Python 进程启动失败: " + e.message)));
proc.stdin.write(JSON.stringify(payload) + "\n");
proc.stdin.end();
});
}
ipcMain.handle("template:list", () => listTemplates());
ipcMain.handle("template:save", (_, data) => {
const id = saveTemplate(data);
return id;
});
ipcMain.handle("template:update", (_, id, data) => {
updateTemplate(id, data);
return true;
});
ipcMain.handle("template:delete", (_, id) => {
deleteTemplate(id);
return true;
});
ipcMain.handle("template:parse", async (_, filePath) => {
return callPython({ action: "parse_template", file_path: filePath });
});
let _mockDialogPath = null;
if (process.env.NODE_ENV !== "production") {
ipcMain.handle("test:mockDialog", (_, p) => { _mockDialogPath = p; });
}
ipcMain.handle("file:select", async (_, filters = []) => {
if (_mockDialogPath) { const p = _mockDialogPath; _mockDialogPath = null; return p; }
const result = await dialog.showOpenDialog({ filters, properties: ["openFile"] });
return result.canceled ? null : result.filePaths[0];
});
ipcMain.handle("file:selectDir", async () => {
if (_mockDialogPath) { const p = _mockDialogPath; _mockDialogPath = null; return p; }
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
return result.canceled ? null : result.filePaths[0];
});
ipcMain.handle("file:openDir", (_, dirPath) => {
shell.openPath(dirPath);
});
ipcMain.handle("file:copyToAppData", (_, srcPath, templateId) => {
const dir = path.join(APPDATA_DIR, templateId);
fs.mkdirSync(dir, { recursive: true });
const dest = path.join(dir, path.basename(srcPath));
fs.copyFileSync(srcPath, dest);
return dest;
});
module.exports = { callPython };