408 lines
11 KiB
JavaScript
408 lines
11 KiB
JavaScript
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();
|
||
});
|