应用菜单 Menu
本教程共 45 篇 · 第 18 篇 · 更新于 2026-08-03
18. 应用菜单 Menu
本节目标
- 用 Menu.buildFromTemplate 构建应用菜单
- 理解 MenuItem 的关键属性与 role 内置角色
- 掌握子菜单、加速器的写法
- 处理 macOS 与 Windows/Linux 的菜单差异
桌面应用顶部那条「文件 / 编辑 / 视图」菜单,在 Electron 里由 Menu 模块负责。它既能做整个应用的菜单栏,也能做右键弹出的上下文菜单(下一章讲)。本章先聚焦应用菜单。
我们会从 Menu.buildFromTemplate 入手,认识 MenuItem 的关键属性,学会用 role 复用系统标准行为,再处理子菜单、快捷键,以及不同系统的呈现差异。
18-1 用模板构建菜单
Menu.buildFromTemplate 接收一个数组,每个元素描述一个菜单项。数组最外层通常是「顶级菜单」,而每个顶级菜单的 submenu 才是它展开后看到的那些项。
// main.js(主进程)
const { app, Menu } = require('electron/main')
const template = [
{
label: '文件',
submenu: [
{ label: '新建', click: () => console.log('新建') },
{ label: '退出', click: () => app.quit() }
]
},
{
label: '帮助',
submenu: [
{ label: '关于', click: () => console.log('关于') }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
注意一个硬性规则:每个顶级菜单项必须带 submenu。如果你直接把普通项放最外层,菜单不会按预期显示。这是新手最常遇到的坑。
18-2 MenuItem 的关键属性
单个菜单项(MenuItem)由模板里的对象描述,常用属性有这些:
label:显示的文字。click:被点击时的回调。type:类型,如normal、separator(分隔线)、checkbox、radio。enabled:是否可点击,默认true。visible:是否显示,默认true。submenu:子菜单。accelerator:快捷键,如Ctrl+N。role:内置角色,复用系统标准行为。
分隔线在视觉上分组很有用。下面加一条分隔线,把「退出」和其他项分开:
const template = [
{
label: '文件',
submenu: [
{ label: '新建', click: () => console.log('新建') },
{ type: 'separator' },
{ label: '退出', click: () => app.quit() }
]
}
]
18-3 用 role 复用系统行为
很多菜单项其实是跨应用通用的:复制、粘贴、撤销、全屏、最小化、退出。Electron 把这些行为封装成 role,你只需声明 role,平台会给出正确的文字、图标和行为,连快捷键都帮你绑定好。
const { Menu } = require('electron/main')
const template = [
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
}
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
常用 role 包括:undo、redo、cut、copy、paste、selectAll、reload、toggleDevTools、resetZoom、zoomIn、zoomOut、togglefullscreen、minimize、close、quit 等。设置 role 后,click 会被忽略,因为行为已由系统定义。
18-4 子菜单与加速器
子菜单就是菜单项里再嵌一层 submenu,可以无限嵌套。它适合把相关功能归类,比如「编辑」下面再放一个「语音」子菜单。
const template = [
{
label: '编辑',
submenu: [
{ role: 'copy' },
{ role: 'paste' },
{
label: '语音',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
]
}
]
加速器(accelerator)给菜单项绑定快捷键。写法上跨平台用 CommandOrControl 表示 macOS 的 Cmd 或其他系统的 Ctrl。
const template = [
{
label: '文件',
submenu: [
{ label: '新建', accelerator: 'CommandOrControl+N', click: () => console.log('新建') },
{ label: '保存', accelerator: 'CommandOrControl+S', click: () => console.log('保存') }
]
}
]
在 Windows 和 Linux,你还可以在顶级菜单名前用 & 指定「Alt+字母」快捷键,比如 &File 会生成 Alt+F。
18-5 macOS 与 Windows/Linux 的差异
菜单的呈现方式因系统而异。在 macOS 上,应用菜单显示在屏幕顶部的系统菜单栏,且第一个子菜单的标题会被强制替换成应用名。在 Windows 和 Linux 上,菜单显示在每个窗口的顶部。
因此官方推荐的做法是:先判断平台,给 macOS 单独加一个「应用菜单」子菜单(包含关于、隐藏、退出等)。下面是一段标准写法:
const { app, Menu } = require('electron/main')
const isMac = process.platform === 'darwin'
const template = [
...(isMac
? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'quit' }
]
}]
: []),
{
label: '文件',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
{ role: 'editMenu' },
{ role: 'viewMenu' }
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
这里 editMenu、viewMenu 是「整段复用」的角色,Electron 会一次性把标准编辑菜单、视图菜单填好,省去逐项手写。
18-6 运行时修改菜单
菜单不是建好就固定不变。你可以动态增删项、改文字、改可用状态。menu.append(item) 追加一项,menu.insert(pos, item) 在指定位置插入,menu.getMenuItemById(id) 按 id 找到某项后修改其属性。
const { Menu, MenuItem } = require('electron/main')
const menu = Menu.buildFromTemplate([{ label: '文件', submenu: [] }])
const fileSub = menu.items[0].submenu
fileSub.append(new MenuItem({ label: '最近打开', enabled: false }))
Menu.setApplicationMenu(menu)
注意官方提醒:用 getApplicationMenu 拿到的菜单实例,不支持动态增删项,只能改已有项的属性。需要结构性变化时,重新 buildFromTemplate 再 setApplicationMenu 最稳妥。
18-7 窗口级菜单与隐藏
在 Windows 和 Linux 上,每个窗口顶部都能有独立菜单。用 win.setMenu(menu) 可给单个窗口设置专属菜单,覆盖全局应用菜单;想去掉某个窗口的菜单,用 win.removeMenu()。
const win = new BrowserWindow()
win.setMenu(Menu.buildFromTemplate([
{ label: '自定义', submenu: [{ role: 'copy' }, { role: 'paste' }] }
]))
macOS 的应用菜单是系统级的,无法按窗口覆盖,这是平台差异决定的。若传 null 给 setApplicationMenu,会抑制默认菜单,在 Windows/Linux 还会顺带移除窗口的菜单栏。
18-8 菜单的可见性与可用性
单个菜单项可用 visible 和 enabled 控制显示与可用。比如未登录时隐藏「我的订单」,或操作不可用时置灰,都是常见需求。
const item = menu.items[0].submenu.items[1]
item.enabled = false // 置灰
item.visible = true // 显示
type 还能做勾选项。checkbox 和 radio 类型带 checked 状态,点击会切换,适合「自动保存」「主题」这类开关。注意修改 MenuItem 属性后,多数平台会即时反映,但 macOS 应用菜单的某些项需重建才更新。
18-9 一个完整菜单示例
把前面知识点串起来,下面是一份较完整的应用菜单。它兼顾 macOS 应用菜单、标准编辑/视图菜单,又加了自定义的「帮助」项,平台差异用 isMac 处理。
const { app, Menu } = require('electron/main')
const isMac = process.platform === 'darwin'
const template = [
...(isMac ? [{ label: app.name, submenu: [
{ role: 'about' }, { type: 'separator' },
{ role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' },
{ type: 'separator' }, { role: 'quit' }
]}] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
{ label: '帮助', submenu: [
{ label: '官方文档', click: () => console.log('打开文档') },
{ type: 'separator' },
{ label: '检查更新', click: () => console.log('检查更新') }
]}
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
fileMenu、editMenu、viewMenu、windowMenu 是「整段复用」角色,Electron 会填好平台对应的标准项。这样你只需补少量自定义项,就能得到既地道又省事的菜单。
18-10 菜单设计的小建议
做菜单时有几条经验值得记。第一,尽量复用 role,不要手写系统已有行为。复制、粘贴、全屏这些项系统已处理妥当,自己写既容易出错,又和别的软件不一致,用户会不习惯。
第二,顺序要符合平台习惯。文件、编辑、视图、窗口、帮助这样的排列,用户一眼就懂。不要为了突出功能把重要项塞到奇怪的位置。macOS 的应用菜单尤其要放在最前,且标题会被系统替换成应用名。
第三,别把菜单当功能抽屉滥用。菜单里堆上百个项,用户根本找不到。常用功能用工具栏或快捷键,菜单只放必要的、可发现的命令。分隔线用得好,能让长菜单立刻变得好读。
18-11 小结
应用菜单用 Menu.buildFromTemplate 加 Menu.setApplicationMenu 两步走。模板数组里,顶级项必须带 submenu,子菜单可任意嵌套。
能用 role 就别手写 click,既省事又保证平台一致。快捷键用 CommandOrControl 做跨平台兼容。最后记得按 macOS 与 Windows/Linux 的差异补齐应用菜单,体验才地道。