Electron 如何自定义菜单?这篇帮你实现原生体验!

目录

  • [1. 概述](#1. 概述 "#1-%E6%A6%82%E8%BF%B0")
  • [2. Menu 类](#2. Menu 类 "#2-menu-%E7%B1%BB")
  • [3. MenuItem 类](#3. MenuItem 类 "#3-menuitem-%E7%B1%BB")
  • [4. 菜单类型](#4. 菜单类型 "#4-%E8%8F%9C%E5%8D%95%E7%B1%BB%E5%9E%8B")
  • [5. 角色 (Roles)](#5. 角色 (Roles) "#5-%E8%A7%92%E8%89%B2-roles")
  • [6. 快捷键 (Accelerator)](#6. 快捷键 (Accelerator) "#6-%E5%BF%AB%E6%8D%B7%E9%94%AE-accelerator")
  • [7. 实际应用示例](#7. 实际应用示例 "#7-%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E7%A4%BA%E4%BE%8B")
  • [8. 平台差异](#8. 平台差异 "#8-%E5%B9%B3%E5%8F%B0%E5%B7%AE%E5%BC%82")

1. 概述

css 复制代码
Menu(菜单)
  │
  ├── MenuItem(菜单项)
  ├── MenuItem(菜单项)
  │     │
  │     └── Submenu(子菜单)
  │           ├── MenuItem
  │           └── MenuItem
  ├── MenuItem(复选框)
  └── MenuItem(分隔线)

菜单类型

类型 说明 使用场景
应用菜单 窗口顶部的菜单栏 主菜单
上下文菜单 右键点击弹出的菜单 操作菜单
子菜单 菜单项的嵌套菜单 组织功能

2.1 创建菜单

javascript 复制代码
const { Menu, MenuItem } = require('electron')

const menu = new Menu()

2.2 设置应用菜单

javascript 复制代码
const template = [
  {
    label: '文件',
    submenu: [
      { label: '新建', accelerator: 'CmdOrCtrl+N', click: () => {} },
      { type: 'separator' },
      { label: '退出', accelerator: 'CmdOrCtrl+Q', click: () => app.quit() }
    ]
  },
  {
    label: '编辑',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' }
    ]
  }
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

2.3 静态方法

方法 说明
Menu.setApplicationMenu(menu) 设置应用菜单
Menu.getApplicationMenu() 获取应用菜单
Menu.buildFromTemplate(template) 从模板构建菜单
Menu.sendActionToFirstResponder(action) macOS 专用

2.4 实例方法

方法 说明
menu.popup([options]) 弹出菜单(作为上下文菜单)
menu.closePopup() 关闭弹出的菜单
menu.append(menuItem) 追加菜单项
menu.insert(pos, menuItem) 在指定位置插入菜单项
menu.getMenuItemById(id) 根据 ID 获取菜单项
javascript 复制代码
menu.popup({
  window: BrowserWindow,      // 指定窗口,默认聚焦窗口
  x: 100,                     // x 坐标
  y: 200,                     // y 坐标
  positioningItem: 0,         // macOS:在鼠标位置高亮的项
  callback: () => {}          // 菜单关闭后的回调
})

2.6 实例事件

事件 说明
menu-will-show 调用 popup() 时触发
menu-will-close 菜单关闭时触发

3.1 基本创建

javascript 复制代码
const { MenuItem } = require('electron')

const item = new MenuItem({
  label: '新建文件',
  click: () => {
    console.log('点击了新建文件')
  }
})

3.2 构造函数选项

选项 类型 说明
label string 菜单项文本
click function 点击回调
role string 预定义角色
type string 菜单项类型
accelerator Accelerator 快捷键
icon NativeImage/string 图标
enabled boolean 是否启用
visible boolean 是否可见
checked boolean 是否选中(checkbox/radio)
submenu Menu/MenuItem\[\] 子菜单
id string 唯一标识
before string\[\] 在某项之前插入
after string\[\] 在某项之后插入

3.3 实例属性

属性 说明 可修改
id 唯一标识
label 显示文本
click 点击函数
type 类型
role 角色
accelerator 快捷键
icon 图标
sublabel 子标签 (macOS)
toolTip 提示文本 (macOS)
enabled 是否启用
visible 是否可见
checked 是否选中
submenu 子菜单

4. 菜单类型

4.1 普通菜单项

javascript 复制代码
{
  label: '新建',
  click: () => createNewFile()
}

4.2 分隔线

javascript 复制代码
{ type: 'separator' }

4.3 子菜单

javascript 复制代码
{
  label: '文件',
  submenu: [
    { label: '新建', click: () => {} },
    { label: '打开', click: () => {} }
  ]
}

4.4 复选框菜单项

javascript 复制代码
{
  label: '自动保存',
  type: 'checkbox',
  checked: true,
  click: (menuItem) => {
    console.log('自动保存:', menuItem.checked)
  }
}

4.5 单选菜单项

javascript 复制代码
{
  label: '简体中文',
  type: 'radio',
  checked: true,
  click: () => setLanguage('zh')
},
{
  label: 'English',
  type: 'radio',
  checked: false,
  click: () => setLanguage('en')
}

4.6 动态构建子菜单

javascript 复制代码
{
  label: '最近文件',
  submenu: [
    { label: 'file1.txt', click: () => openFile('file1.txt') },
    { label: 'file2.txt', click: () => openFile('file2.txt') },
    { type: 'separator' },
    { label: '清除最近文件', role: 'clearRecentDocuments' }
  ]
}

5. 角色 (Roles)

5.1 通用角色

角色 说明
undo 撤销
redo 重做
cut 剪切
copy 复制
paste 粘贴
delete 删除
selectAll 全选
reload 重新加载
forceReload 强制重新加载
toggleDevTools 切换开发者工具
togglefullscreen 切换全屏
resetZoom 重置缩放
zoomIn 放大
zoomOut 缩小
minimize 最小化窗口
close 关闭窗口
quit 退出应用

5.2 macOS 特有角色

角色 说明
about 关于面板
hide 隐藏应用
hideOthers 隐藏其他应用
unhide 显示所有应用
startSpeaking 开始朗读
stopSpeaking 停止朗读
front 窗口前置
appMenu 应用菜单
fileMenu 文件菜单
editMenu 编辑菜单
viewMenu 视图菜单
windowMenu 窗口菜单
help 帮助菜单
services 服务菜单
shareMenu 分享菜单

5.3 使用角色

javascript 复制代码
{
  label: '编辑',
  submenu: [
    { role: 'undo', label: '撤销' },
    { role: 'redo', label: '重做' },
    { type: 'separator' },
    { role: 'cut', label: '剪切' },
    { role: 'copy', label: '复制' },
    { role: 'paste', label: '粘贴' }
  ]
}

⚠️ 注意 :macOS 上使用 role 时,labelaccelerator 会被忽略,会自动使用系统默认值。


6. 快捷键 (Accelerator)

6.1 基本语法

javascript 复制代码
{
  label: '新建',
  accelerator: 'CmdOrCtrl+N',
  click: () => {}
}

6.2 常用快捷键格式

格式 说明 示例
CmdOrCtrl+N 命令/控制 + N Ctrl+N / ⌘N
Cmd+N 仅 macOS ⌘N
Ctrl+Shift+N 控制 + Shift + N Ctrl+Shift+N
Alt+F Alt + F Alt+F
F5 功能键 F5

6.3 修饰键

修饰键 Windows/Linux macOS
Ctrl
Alt
Shift
Cmd / Command
CmdOrCtrl ✅ (使用 ⌘)

6.4 快捷键选项

javascript 复制代码
{
  label: '粘贴',
  accelerator: 'CmdOrCtrl+V',
  acceleratorWorksWhenHidden: false,  // macOS:隐藏时是否生效
  registerAccelerator: true          // 是否注册到系统
}

7. 实际应用示例

7.1 应用菜单完整示例

javascript 复制代码
const { app, Menu, MenuItem, BrowserWindow, shell } = require('electron')

function createMenu() {
  const template = [
    {
      label: '文件',
      submenu: [
        {
          label: '新建窗口',
          accelerator: 'CmdOrCtrl+N',
          click: () => createNewWindow()
        },
        {
          label: '打开文件',
          accelerator: 'CmdOrCtrl+O',
          click: () => openFile()
        },
        {
          label: '保存',
          accelerator: 'CmdOrCtrl+S',
          click: () => saveFile()
        },
        { type: 'separator' },
        {
          label: '导出',
          submenu: [
            { label: '导出为 PDF', click: () => exportPDF() },
            { label: '导出为 Word', click: () => exportWord() }
          ]
        },
        { type: 'separator' },
        {
          label: '退出',
          accelerator: 'CmdOrCtrl+Q',
          click: () => app.quit()
        }
      ]
    },
    {
      label: '编辑',
      submenu: [
        { role: 'undo' },
        { role: 'redo' },
        { type: 'separator' },
        { role: 'cut' },
        { role: 'copy' },
        { role: 'paste' },
        { role: 'delete' },
        { type: 'separator' },
        { role: 'selectAll' }
      ]
    },
    {
      label: '视图',
      submenu: [
        { role: 'reload' },
        { role: 'forceReload' },
        { role: 'toggleDevTools' },
        { type: 'separator' },
        { role: 'resetZoom' },
        { role: 'zoomIn' },
        { role: 'zoomOut' },
        { type: 'separator' },
        { role: 'togglefullscreen' }
      ]
    },
    {
      label: '窗口',
      submenu: [
        { role: 'minimize' },
        { role: 'zoom' },
        { role: 'close' }
      ]
    },
    {
      label: '帮助',
      submenu: [
        {
          label: '关于',
          click: () => showAboutDialog()
        },
        {
          label: '文档',
          click: () => shell.openExternal('https://electronjs.org')
        }
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
}

7.2 自定义上下文菜单

javascript 复制代码
const { Menu } = require('electron')

function showContextMenu(window) {
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '复制',
      accelerator: 'CmdOrCtrl+C',
      role: 'copy'
    },
    {
      label: '粘贴',
      accelerator: 'CmdOrCtrl+V',
      role: 'paste'
    },
    { type: 'separator' },
    {
      label: '全选',
      accelerator: 'CmdOrCtrl+A',
      role: 'selectAll'
    },
    { type: 'separator' },
    {
      label: '开发者选项',
      submenu: [
        {
          label: '刷新',
          accelerator: 'CmdOrCtrl+R',
          role: 'reload'
        },
        {
          label: '开发者工具',
          accelerator: 'F12',
          role: 'toggleDevTools'
        }
      ]
    }
  ])

  contextMenu.popup(window)
}

// 在渲染进程中使用
webContents.on('context-menu', (event, params) => {
  const contextMenu = Menu.buildFromTemplate([
    { label: '复制', role: 'copy', enabled: params.selectionText.length > 0 },
    { label: '粘贴', role: 'paste', enabled: params.editFlags.canPaste },
    { type: 'separator' },
    { label: `选中文字: "${params.selectionText}"` }
  ])
  contextMenu.popup()
})

7.3 动态菜单(根据状态)

javascript 复制代码
function updateMenuState(isLoggedIn, userName) {
  const template = [
    {
      label: '文件',
      submenu: [
        { label: '新建', click: () => {} },
        { label: '打开', click: () => {} }
      ]
    },
    {
      label: '账户',
      submenu: isLoggedIn ? [
        { label: `用户: ${userName}` },
        { label: '退出登录', click: () => logout() }
      ] : [
        { label: '登录', click: () => showLogin() },
        { label: '注册', click: () => showRegister() }
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
}

8. 平台差异

8.1 macOS vs Windows/Linux

特性 macOS Windows/Linux
菜单位置 屏幕顶部(系统级) 窗口顶部
角色默认行为 自动应用 需要手动设置
sublabel 支持 不支持
toolTip 支持 不支持
右键菜单样式 原生样式 Chromium 样式

8.2 Windows/Linux 快捷键前缀

javascript 复制代码
// Windows/Linux 可以用 & 设置快捷键
{
  label: '&File',      // Alt+F 打开文件菜单
  submenu: [
    { label: '&New', accelerator: 'Ctrl+N' },
    { label: '&Open', accelerator: 'Ctrl+O' },
    { label: '&Save', accelerator: 'Ctrl+S' }
  ]
}

// 转义 & 字符
{ label: '&&Settings' }  // 显示为 &Settings

8.3 禁用默认菜单

javascript 复制代码
// 禁用默认菜单
Menu.setApplicationMenu(null)

最佳实践

✅ 推荐做法

javascript 复制代码
// 1. 使用角色而不是手动实现
{ role: 'copy' }  // ✅ 原生体验

// 2. 避免手动实现
{ label: '复制', click: () => document.execCommand('copy') }  // ❌

// 3. 动态菜单使用模板方式
Menu.buildFromTemplate(template)  // ✅

// 4. macOS 考虑系统菜单规范

❌ 避免做法

  1. 不要在 macOS 上手动设置 label/accelerator

    javascript 复制代码
    // macOS 会忽略这些值
    { role: 'copy', label: '复制' }  // ❌
    { role: 'copy' }  // ✅
  2. 不要忘记分隔线类型

    javascript 复制代码
    { type: 'separator' }  // ✅
    { label: '---' }  // ❌
  3. 不要忽略 enabled 状态

    javascript 复制代码
    { label: '删除', enabled: false }  // 禁用时置灰显示

速查表

css 复制代码
Menu.setApplicationMenu(menu)      设置应用菜单
Menu.getApplicationMenu()          获取应用菜单
Menu.buildFromTemplate(template)   从模板构建菜单
css 复制代码
menu.popup(options)                弹出菜单
menu.closePopup()                  关闭菜单
menu.append(item)                  添加菜单项
menu.insert(pos, item)             插入菜单项
menu.getMenuItemById(id)           获取菜单项
css 复制代码
normal                              普通菜单项
separator                          分隔线
submenu                            子菜单
checkbox                           复选框
radio                              单选按钮

常用角色

bash 复制代码
undo, redo, cut, copy, paste       编辑操作
reload, toggleDevTools             视图操作
minimize, close                    窗口操作
togglefullscreen                   全屏操作

文档基于 Electron v28+ Menu/MenuItem API 编写

相关推荐
ZC跨境爬虫4 小时前
跟着 MDN 学JavaScript day_7:数学运算与逻辑判断实战测试
开发语言·前端·javascript·学习·ecmascript
fangdengfu1234 小时前
ES分析系统各个服务日志占用量
java·前端·elasticsearch
JustHappy6 小时前
古法编程秘籍(六):程序到底是怎么跑起来的?从 IO 到中断,一次讲明白
前端·后端·全栈
HYCS6 小时前
用pixi.js实现fabric.js(六):从线性代数的角度理解编辑器交互
前端·javascript·canvas
卷帘依旧7 小时前
useImperativeHandle的作用
前端
卷帘依旧7 小时前
Hooks在Fiber上的存储原理
前端
you45807 小时前
学成在线--day02 CMS前端开发(含Vue基础知识得回顾)
前端·javascript·vue.js
xiaofeichaichai7 小时前
虚拟 DOM
前端·javascript·vue.js
2401_878454537 小时前
前端高频得手写题
前端