首页 / Electron 入门教程 / 应用菜单 Menu

Electron 入门教程

应用菜单 Menu

本教程共 45 篇 · 第 18 篇 · 更新于 2026-08-03

Electron应用菜单MenuMenuItemrole加速器

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:类型,如 normalseparator(分隔线)、checkboxradio
  • 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 包括:undoredocutcopypasteselectAllreloadtoggleDevToolsresetZoomzoomInzoomOuttogglefullscreenminimizeclosequit 等。设置 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))

这里 editMenuviewMenu 是「整段复用」的角色,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 拿到的菜单实例,不支持动态增删项,只能改已有项的属性。需要结构性变化时,重新 buildFromTemplatesetApplicationMenu 最稳妥。

18-7 窗口级菜单与隐藏

在 Windows 和 Linux 上,每个窗口顶部都能有独立菜单。用 win.setMenu(menu) 可给单个窗口设置专属菜单,覆盖全局应用菜单;想去掉某个窗口的菜单,用 win.removeMenu()

const win = new BrowserWindow()
win.setMenu(Menu.buildFromTemplate([
  { label: '自定义', submenu: [{ role: 'copy' }, { role: 'paste' }] }
]))

macOS 的应用菜单是系统级的,无法按窗口覆盖,这是平台差异决定的。若传 nullsetApplicationMenu,会抑制默认菜单,在 Windows/Linux 还会顺带移除窗口的菜单栏。

18-8 菜单的可见性与可用性

单个菜单项可用 visibleenabled 控制显示与可用。比如未登录时隐藏「我的订单」,或操作不可用时置灰,都是常见需求。

const item = menu.items[0].submenu.items[1]
item.enabled = false      // 置灰
item.visible = true       // 显示

type 还能做勾选项。checkboxradio 类型带 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))

fileMenueditMenuviewMenuwindowMenu 是「整段复用」角色,Electron 会填好平台对应的标准项。这样你只需补少量自定义项,就能得到既地道又省事的菜单。

18-10 菜单设计的小建议

做菜单时有几条经验值得记。第一,尽量复用 role,不要手写系统已有行为。复制、粘贴、全屏这些项系统已处理妥当,自己写既容易出错,又和别的软件不一致,用户会不习惯。

第二,顺序要符合平台习惯。文件、编辑、视图、窗口、帮助这样的排列,用户一眼就懂。不要为了突出功能把重要项塞到奇怪的位置。macOS 的应用菜单尤其要放在最前,且标题会被系统替换成应用名。

第三,别把菜单当功能抽屉滥用。菜单里堆上百个项,用户根本找不到。常用功能用工具栏或快捷键,菜单只放必要的、可发现的命令。分隔线用得好,能让长菜单立刻变得好读。

18-11 小结

应用菜单用 Menu.buildFromTemplateMenu.setApplicationMenu 两步走。模板数组里,顶级项必须带 submenu,子菜单可任意嵌套。

能用 role 就别手写 click,既省事又保证平台一致。快捷键用 CommandOrControl 做跨平台兼容。最后记得按 macOS 与 Windows/Linux 的差异补齐应用菜单,体验才地道。