Electron Forge【实战】自定义菜单 -- 顶部菜单 vs 右键快捷菜单
效果预览
定义菜单 src/menu.ts
import { Menu, BrowserWindow, MenuItemConstructorOptions } from "electron";import { configManager } from "./config";
import en from "./language/en";
import zh from "./language/zh";type MessageSchema = typeof zh;
const messages: Record<string, MessageSchema> = {en,zh,
};// 创建一个通用的翻译函数
const createTranslator = () => {const config = configManager.get();return (key: string) => {const keys = key.split(".");let result: any = messages[config.language];for (const k of keys) {result = result[k];}return result as string;};
};// 顶部菜单
const createMenu = (mainWindow: BrowserWindow) => {const t = createTranslator();const template: MenuItemConstructorOptions[] = [{label: t("menu.app.myApp"),submenu: [{label: t("menu.app.newConversation"),accelerator: "CmdOrCtrl+N",click: () => {mainWindow.webContents.send("menu-new-conversation");},},{label: t("menu.app.settings"),accelerator: "CmdOrCtrl+,",click: () => {mainWindow.webContents.send("menu-open-settings");},},{ type: "separator" },{role: "quit",label: t("menu.app.quit"),},],},{label: t("menu.edit.title"),submenu: [{role: "undo",label: t("menu.edit.undo"),},{role: "redo",label: t("menu.edit.redo"),},{ type: "separator" },{role: "cut",label: t("menu.edit.cut"),},{role: "copy",label: t("menu.edit.copy"),},{role: "paste",label: t("menu.edit.paste"),},{role: "selectAll",label: t("menu.edit.selectAll"),},...(process.platform === "darwin"? ([{ type: "separator" as const },{label: t("menu.edit.speech.title"),submenu: [{role: "startSpeaking",label: t("menu.edit.speech.startSpeaking"),},{role: "stopSpeaking",label: t("menu.edit.speech.stopSpeaking"),},],},{role: "emoji",label: t("menu.edit.emoji"),},] as MenuItemConstructorOptions[]): []),],},{label: t("menu.view.title"),submenu: [{role: "reload",label: t("menu.view.reload"),},{role: "forceReload",label: t("menu.view.forceReload"),},{role: "toggleDevTools",label: t("menu.view.toggleDevTools"),},{ type: "separator" },{role: "resetZoom",label: t("menu.view.resetZoom"),},{role: "zoomIn",label: t("menu.view.zoomIn"),},{role: "zoomOut",label: t("menu.view.zoomOut"),},{ type: "separator" },{role: "togglefullscreen",label: t("menu.view.togglefullscreen"),},],},...(process.platform === "darwin"? [{role: "windowMenu" as const,},]: []),];const menu = Menu.buildFromTemplate(template);Menu.setApplicationMenu(menu);
};// 右键菜单
const createContextMenu = (win: BrowserWindow, id: number) => {const t = createTranslator();const template = [{label: t("contextMenu.deleteConversation"),click: () => {win.webContents.send("delete-conversation", id);},},];const menu = Menu.buildFromTemplate(template);menu.popup({ window: win });
};// 导出一个更新菜单的函数,在语言改变时调用
const updateMenu = (mainWindow: BrowserWindow) => {createMenu(mainWindow);
};export { createMenu, updateMenu, createContextMenu };
顶部菜单
加载顶部菜单 src/main.ts
import { createMenu } from "./menu";
// 加载菜单createMenu(mainWindow);
添加 IPC 通信 src/preload.ts
onMenuNewConversation: (callback: () => void) =>ipcRenderer.on("menu-new-conversation", () => callback()),onMenuOpenSettings: (callback: () => void) =>ipcRenderer.on("menu-open-settings", () => callback()),
全局监听菜单事件 src/App.vue
// 监听菜单事件
window.electronAPI.onMenuNewConversation(() => {router.push("/");
});window.electronAPI.onMenuOpenSettings(() => {router.push("/settings");
});
修改设置时,更新顶部菜单 src/ipc.ts
import { createContextMenu, updateMenu } from "./menu";
ipcMain.handle("update-config", async (event, newConfig) => {const updatedConfig = await configManager.update(newConfig);// 如果语言发生变化,更新菜单if (newConfig.language) {updateMenu(mainWindow);}return updatedConfig;});
右键快捷菜单
目标元素上添加右键快捷菜单事件
src/components/ConversationList.vue
@contextmenu.prevent="showContextMenu(item.id)"
const showContextMenu = (id: number) => {window.electronAPI.showContextMenu(id);
};
添加 IPC 通信
src/preload.ts
// 显示右键菜单showContextMenu: (id: number) => ipcRenderer.send("show-context-menu", id),// 删除会话onDeleteConversation: (callback: (id: number) => void) =>ipcRenderer.on("delete-conversation", (_event, id) => callback(id)),
src/ipc.ts
import { createContextMenu, updateMenu } from "./menu";
// 弹出右键菜单ipcMain.on("show-context-menu", (event, id) => {const win = BrowserWindow.fromWebContents(event.sender);if (!win) return;createContextMenu(win, id);});
页面响应右键快捷菜单事件
src/components/ConversationList.vue
onMounted(() => {window.electronAPI.onDeleteConversation(async (id: number) => {await store.deleteConversation(id);if (store.selectedId === id) {store.selectedId = -1;router.push("/");}});
});