2025-04-10 15:11:22 +08:00

408 lines
11 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.

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();
});