import { nativeImage, BrowserWindow, app, ipcMain, Menu, dialog, Tray } from "electron"; import { join } from "node:path"; const __dirname = import.meta.dirname; const __root = join(__dirname, "../../"); const isDev = process.env.NODE_ENV === "development"; process.env.NODE_ENV === "production"; const winURL = isDev ? process.env.VITE_DEV_SERVER_URL : join(__root, "dist/index.html"); const windowOptions = { id: null, // 窗口标题 title: "Electron-MacOS", // 窗口路由地址 url: "", data: null, // 是否是主窗口 isMajor: false, // 是否支持多开窗口 isMultiple: false, maximize: false }; const windowBaseOptions = { icon: join(__root, "resources/shortcut.ico"), // 是否自动隐藏菜单栏(按下Alt键显示) autoHideMenuBar: true, // 窗口标题栏样式 titleBarStyle: "hide", // 窗口背景色 backgroundColor: "#fff", width: 1e3, height: 640, minWidth: "", minHeight: "", // 窗口x坐标 x: "", // 窗口y坐标 y: "", resizable: true, // 是否可最小化 minimizable: true, maximizable: true, // 是否可关闭 closable: true, parent: null, modal: false, alwaysOnTop: false, frame: false, transparent: false, // 创建时是否显示窗口 show: false, webPreferences: { preload: join(__root, "electron/preload.js"), // 页面运行其他脚本之前预先加载指定的脚本 devTools: true // 是否开启DevTools调试工具 } }; function mergeObjects(target, source) { const array = {}; for (let prop in target) { if (source.hasOwnProperty(prop)) { if (typeof source[prop] === "object") { array[prop] = Object.assign({}, target[prop], source[prop]); } else { array[prop] = source[prop]; } } } return Object.assign({}, target, array); } class WindowManager { constructor() { this.winMain = null; this.winDict = {}; this.tray = null; this.trayTimer = null; this.trayIconPath = join(__root, "resources/tray.ico"); this.trayIcon = nativeImage.createFromPath(this.trayIconPath); this.trayEmptyPath = join(__root, "resources/tray-empty.ico"); this.trayEmpty = nativeImage.createFromPath(this.trayEmptyPath); } create(options) { const windowConfig = mergeObjects(windowOptions, options); const windowBaseConfig = mergeObjects(windowBaseOptions, options); for (let i in this.winDict) { let win = this.getWinById(i); if (win && this.winDict[i].url === windowConfig.url && !this.winDict[i].isMultiple && !this.winDict[i].isMajor) { win.restore(); win.focus(); return; } } if (windowBaseConfig.parent) { windowBaseConfig.parent = this.getWinById(windowBaseConfig.parent); } let winObj = new BrowserWindow(windowBaseConfig); if (windowConfig.isMajor) { for (let i in this.winDict) { let win = this.getWinById(i); if (win) { win.close(); delete this.winDict[i]; } } this.winMain = winObj; } if (windowConfig.maximize && windowBaseConfig.maximizable && windowBaseConfig.resizable) { winObj.maximize(); } let url; if (!windowConfig.url) { if (process.env.VITE_DEV_SERVER_URL) { url = process.env.VITE_DEV_SERVER_URL; } else { url = winURL; } } else { url = `${winURL}#${windowConfig.url}`; } winObj.loadURL(url); windowConfig.id = winObj.id; this.winDict[winObj.id] = windowConfig; winObj.once("ready-to-show", () => { winObj.show(); }); winObj.webContents.on("did-finish-load", () => { this.sendById(winObj.id, "win-loaded", windowConfig); }); winObj.on("maximize", () => { this.sendById(winObj.id, "win-maximized", true); }); winObj.on("unmaximize", () => { this.sendById(winObj.id, "win-maximized", false); }); winObj.on("close", () => winObj.setOpacity(0)); winObj.on("closed", () => { console.log("watching window closed..."); delete this.winDict[winObj.id]; }); } getWinById(id) { if (!id) return; return BrowserWindow.fromId(Number(id)); } getAllWin() { return BrowserWindow.getAllWindows(); } sendById(id, channel, args) { let win = this.getWinById(id); if (!win) return; win.webContents.send(channel, args); } sendByMainWin(channel, args) { this.winMain.webContents.send(channel, args); } // 关闭全部窗口 closeAllWin() { try { for (let i in this.winDict) { let win = this.getWinById(i); if (win) { win.close(); delete this.winDict[i]; } else { app.quit(); } } } catch (err) { console.error(err); } } /** * 根据窗口id关闭窗口 * @param {number} id 窗口id,不传id则关闭所有窗口 */ close(id) { if (id) { let win = this.getWinById(id); try { win.close(); delete this.winDict[id]; } catch (error) { throw new Error(`[捕获窗口关闭异常]${error}`); } } else { this.closeAllWin(); } } actions(action, id) { let win = this.getWinById(id); if (id) { try { win[action](); } catch (error) { throw new Error(`[捕获窗口事件异常]${error}`); } } else { for (let i in this.winDict) { if (this.winDict[i]) { let win2 = this.getWinById(i); try { win2[action](); } catch (error) { throw new Error(`[捕获窗口事件异常]${error}`); } } } } } ipcManager() { console.log("watching ipc event..."); ipcMain.on("win-create", (event, args) => this.create(args)); ipcMain.on("win-set", (event, args) => { const { id, action } = args; switch (action) { case "show": case "hide": case "minimize": case "maximize": case "restore": case "reload": this.actions(action, id); break; case "max2min": let win = this.getWinById(id); if (!win) return; if (win.isMaximized()) { win.unmaximize(); } else { win.maximize(); } break; case "close": this.close(id); break; default: throw new Error(`[窗口设置异常]${args}`); } }); ipcMain.on("win-setTitle", (event, title) => { let webContents = event.sender; let win = BrowserWindow.fromWebContents(webContents); win.setTitle(title); win.webContents.send("win-setTitle", title); }); ipcMain.on("win-ipcdata", (event, args) => { for (let i in this.winDict) { this.sendById(i, "win-ipcdata", args); } }); ipcMain.on("show-context-menu", (e) => { const menu = Menu.buildFromTemplate([ { label: "设置备注和标签", click: () => e.sender.send("context-menu-command", "label") }, { label: "权限管理", click: () => null }, { type: "separator" }, { label: "删除", click: () => e.sender.send("context-menu-command", "delete") } ]); menu.popup(); }); ipcMain.on("win-traydestory", (event, args) => this.trayDestroy()); ipcMain.handle("win-isResizable", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win) return win.isResizable(); }); ipcMain.handle("win-isMaximizable", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win) return win.isMaximizable(); }); ipcMain.handle("win-isMaximized", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win) return win.isMaximized(); }); ipcMain.handle("win-isAlwaysOnTop", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win) return win.isAlwaysOnTop(); }); ipcMain.handle("win-setAlwaysOnTop", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win.isAlwaysOnTop()) { win.setAlwaysOnTop(false); return false; } else { win.setAlwaysOnTop(true); return true; } }); ipcMain.handle("win-min", (e) => { let win = BrowserWindow.getFocusedWindow(); win.minimize(); return true; }); ipcMain.handle("win-toggle", (e) => { let win = BrowserWindow.getFocusedWindow(); if (win.isMaximized()) { win.unmaximize(); return false; } else { win.maximize(); return true; } }); ipcMain.handle("win-quit", (e) => { this.closeAllWin(); }); } trayManager() { console.log("create tray started..."); if (this.tray) return; const trayMenu = Menu.buildFromTemplate([ { label: "打开主面板", icon: join(__root, "resources/tray-win.png"), click: () => { for (let i in this.winDict) { let win = this.getWinById(i); if (!win) return; win.restore(); win.show(); } } }, { label: "设置", icon: join(__root, "resources/tray-setting.png"), click: () => this.sendByMainWin("win-ipcdata", { type: "WINIPC_SETTINGWIN", value: null }) }, { label: "锁定系统", icon: join(__root, "resources/tray-lock.png"), click: () => null }, { label: "关于", icon: join(__root, "resources/tray-about.png"), click: () => this.sendByMainWin("win-ipcdata", { type: "WINIPC_ABOUTWIN", value: null }) }, { label: "退出系统", icon: join(__root, "resources/tray-exit.png"), click: () => { dialog.showMessageBox(this.winMain, { title: "提示", message: "确定要退出系统程序吗?", buttons: ["取消", "最小化托盘", "确认退出"], type: "error", noLink: false, cancelId: 0 }).then((res) => { const index = res.response; if (index === 0) { console.log("用户取消操作"); } else if (index === 1) { console.log("最小化到托盘"); this.winMain.hide(); } else if (index === 2) { console.log("退出程序"); app.quit(); } }); } } ]); this.tray = new Tray(this.trayIcon); console.log("tray图标加载"); this.tray.setContextMenu(trayMenu); this.tray.setToolTip(app.name); this.tray.on("double-click", () => { console.log("tray double clicked!"); }); } trayFlash(bool) { let flag = false; if (bool) { if (this.trayTimer) return; this.trayTimer = setInterval(() => { this.tray.setImage(flag ? this.trayIcon : this.trayEmpty); flag = !flag; }, 500); } else { if (this.trayTimer) { clearInterval(this.trayTimer); this.trayTimer = null; } this.tray.setImage(this.trayIcon); } } trayDestroy() { this.trayFlash(false); this.tray.destroy(); this.tray = null; } } process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = true; const createWindow = () => { let win = new WindowManager(); win.create({ isMajor: true }); win.trayManager(); win.ipcManager(); }; app.whenReady().then(() => { createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); });