Electron 中文菜单栏实现完全指南

前言

在开发 Electron 桌面应用时,菜单栏是用户与应用交互的重要界面元素。对于中文用户来说,一个本地化的中文菜单栏能够显著提升用户体验。本文将详细介绍如何在 Electron 应用中实现完整的中文菜单栏,包括跨平台适配、快捷键配置和最佳实践。

鸿蒙PC效果

先来看一下鸿蒙PC效果

image-20251215090845745

背景介绍

Electron 作为跨平台桌面应用框架,其菜单系统是连接原生系统体验与 Web 交互逻辑的核心组件。默认情况下,Electron 应用可能没有菜单栏,或者菜单栏是英文的。为了提供更好的中文用户体验,我们需要:

  • 创建完整的中文菜单栏

  • 配置符合用户习惯的快捷键

  • 适配不同操作系统的菜单规范

  • 实现菜单项的功能逻辑

核心概念

菜单类型

Electron 支持三种主要的菜单类型:

菜单类型 应用场景 核心 API
应用菜单(Application Menu) 窗口顶部导航栏 Menu.setApplicationMenu()
上下文菜单(Context Menu) 右键点击触发 Menu.popup()
托盘菜单(Tray Menu) 系统托盘图标右键菜单 Tray.setContextMenu()

本文主要介绍应用菜单的实现。

核心 API

  • Menu.buildFromTemplate(template): 从模板创建菜单实例

  • Menu.setApplicationMenu(menu): 设置应用菜单

  • MenuItem: 单个菜单项,可配置文本、快捷键、点击事件等

实现步骤

步骤 1: 引入 Menu 模块

首先,在主进程文件中引入 Menu 模块:

const { app, BrowserWindow, Menu, dialog } = require('electron');

步骤 2: 创建菜单模板

菜单模板是一个数组,每个元素对应一个顶级菜单项(如"文件"、"编辑"等)。我们使用中文标签来定义菜单项:

const menuTemplate = [
  {
    label: '文件',
    submenu: [
      { label: '新建', accelerator: 'CmdOrCtrl+N', click: () => {} },
      { label: '打开', accelerator: 'CmdOrCtrl+O', click: () => {} },
      // ... 更多菜单项
    ]
  },
  // ... 更多顶级菜单
];

步骤 3: 构建并设置菜单

使用模板创建菜单实例,并设置为应用菜单:

const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);

步骤 4: 在应用启动时调用

app.whenReady() 中调用菜单创建函数:

app.whenReady().then(() => {
  createWindow();
  createApplicationMenu(); // 创建菜单栏
});

代码详解

完整实现代码

以下是完整的中文菜单栏实现代码:

// 引入 Menu 模块
const { app, BrowserWindow, Menu, dialog } = require('electron');
​
let mainWindow;
​
// 创建应用菜单栏
function createApplicationMenu() {
  const menuTemplate = [
    {
      label: '文件',
      submenu: [
        {
          label: '新建',
          accelerator: 'CmdOrCtrl+N',
          click: () => {
            if (mainWindow) {
              mainWindow.webContents.send('menu:new');
            }
          }
        },
        {
          label: '打开',
          accelerator: 'CmdOrCtrl+O',
          click: async () => {
            if (mainWindow) {
              mainWindow.webContents.send('menu:open');
            }
          }
        },
        { type: 'separator' }, // 分隔线
        {
          label: '保存',
          accelerator: 'CmdOrCtrl+S',
          click: () => {
            if (mainWindow) {
              mainWindow.webContents.send('menu:save');
            }
          }
        },
        { type: 'separator' },
        {
          label: '退出',
          accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
          click: () => {
            app.quit();
          }
        }
      ]
    },
    {
      label: '编辑',
      submenu: [
        { label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
        { label: '重做', accelerator: 'CmdOrCtrl+Shift+Z', role: 'redo' },
        { type: 'separator' },
        { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
        { label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
        { label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' },
        { label: '全选', accelerator: 'CmdOrCtrl+A', role: 'selectAll' }
      ]
    },
    {
      label: '视图',
      submenu: [
        { label: '重新加载', accelerator: 'CmdOrCtrl+R', role: 'reload' },
        { label: '强制重新加载', accelerator: 'CmdOrCtrl+Shift+R', role: 'forceReload' },
        { label: '切换开发者工具', accelerator: 'F12', role: 'toggleDevTools' },
        { type: 'separator' },
        { label: '实际大小', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' },
        { label: '放大', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' },
        { label: '缩小', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' },
        { type: 'separator' },
        { label: '切换全屏', accelerator: 'F11', role: 'togglefullscreen' }
      ]
    },
    {
      label: '窗口',
      submenu: [
        { label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' },
        { label: '关闭', accelerator: 'CmdOrCtrl+W', role: 'close' }
      ]
    },
    {
      label: '帮助',
      submenu: [
        {
          label: '关于',
          click: () => {
            dialog.showMessageBox(mainWindow, {
              type: 'info',
              title: '关于',
              message: 'Hello World',
              detail: '基于 Electron 构建的应用程序'
            });
          }
        }
      ]
    }
  ];
​
  // macOS 特殊处理
  if (process.platform === 'darwin') {
    menuTemplate.unshift({
      label: app.getName(),
      submenu: [
        { label: '关于 ' + app.getName(), role: 'about' },
        { type: 'separator' },
        { label: '服务', role: 'services', submenu: [] },
        { type: 'separator' },
        { label: '隐藏 ' + app.getName(), accelerator: 'Command+H', role: 'hide' },
        { label: '隐藏其他', accelerator: 'Command+Shift+H', role: 'hideOthers' },
        { label: '显示全部', role: 'unhide' },
        { type: 'separator' },
        { label: '退出', accelerator: 'Command+Q', click: () => app.quit() }
      ]
    });
​
    // 窗口菜单
    menuTemplate[4].submenu = [
      { label: '关闭', accelerator: 'CmdOrCtrl+W', role: 'close' },
      { label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' },
      { label: '缩放', role: 'zoom' },
      { type: 'separator' },
      { label: '前置全部窗口', role: 'front' }
    ];
  }
​
  const menu = Menu.buildFromTemplate(menuTemplate);
  Menu.setApplicationMenu(menu);
}

关键配置项说明

1. label(菜单项文本)

菜单项显示的文本,我们使用中文:

{ label: '文件' }  // 显示为"文件"
{ label: '新建' }  // 显示为"新建"
2. accelerator(快捷键)

定义菜单项的快捷键。使用 CmdOrCtrl 可以自动适配 macOS(Cmd)和 Windows/Linux(Ctrl):

{ label: '新建', accelerator: 'CmdOrCtrl+N' }  // macOS: Cmd+N, Windows: Ctrl+N

常用快捷键格式:

  • CmdOrCtrl+N: 跨平台快捷键

  • Command+H: macOS 专用

  • Ctrl+Shift+R: 组合键

  • F12: 功能键

3. role(系统预定义行为)

Electron 提供了系统预定义的角色,使用 role 可以自动处理系统级功能:

{ label: '复制', role: 'copy' }  // 自动实现复制功能

常用 role 值:

  • 编辑类: undo, redo, cut, copy, paste, delete, selectAll

  • 视图类: reload, forceReload, toggleDevTools, resetZoom, zoomIn, zoomOut, togglefullscreen

  • 窗口类: minimize, close, zoom, front

  • 应用类: about, hide, hideOthers, unhide, quit

使用 role 的优势:

  • 自动适配系统语言(macOS 下 copy 会显示为"拷贝")

  • 自动绑定系统默认快捷键

  • 无需编写 click 事件(系统自动处理)

4. click(点击事件)

自定义菜单项的点击行为:

{
  label: '新建',
  click: () => {
    // 向渲染进程发送消息
    if (mainWindow) {
      mainWindow.webContents.send('menu:new');
    }
  }
}
5. type(菜单项类型)

定义菜单项的类型:

{ type: 'separator' }  // 分隔线
{ type: 'normal' }     // 普通菜单项(默认)
{ type: 'checkbox' }   // 复选框
{ type: 'radio' }      // 单选框
6. submenu(子菜单)

定义子菜单项数组:

{
  label: '文件',
  submenu: [
    { label: '新建' },
    { label: '打开' },
    // ... 更多子菜单项
  ]
}

跨平台适配

macOS 特殊处理

macOS 有独特的菜单栏规范,需要特殊处理:

  1. 应用菜单位置: macOS 的应用菜单会自动显示在系统菜单栏的第一位,标签会被替换为应用名称。

  2. 必需的系统菜单项: macOS 应用菜单需要包含:

    • "关于 [应用名]"

    • "服务"

    • "隐藏 [应用名]"

    • "隐藏其他"

    • "显示全部"

    • "退出"

  3. 窗口菜单: macOS 的窗口菜单需要包含"缩放"和"前置全部窗口"等选项。

实现代码:

// macOS 特殊处理
if (process.platform === 'darwin') {
  // 在菜单模板最前面插入应用菜单
  menuTemplate.unshift({
    label: app.getName(),  // 使用应用名称
    submenu: [
      { label: '关于 ' + app.getName(), role: 'about' },
      { type: 'separator' },
      { label: '服务', role: 'services', submenu: [] },
      { type: 'separator' },
      { label: '隐藏 ' + app.getName(), accelerator: 'Command+H', role: 'hide' },
      { label: '隐藏其他', accelerator: 'Command+Shift+H', role: 'hideOthers' },
      { label: '显示全部', role: 'unhide' },
      { type: 'separator' },
      { label: '退出', accelerator: 'Command+Q', click: () => app.quit() }
    ]
  });
​
  // 修改窗口菜单
  menuTemplate[4].submenu = [
    { label: '关闭', accelerator: 'CmdOrCtrl+W', role: 'close' },
    { label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' },
    { label: '缩放', role: 'zoom' },
    { type: 'separator' },
    { label: '前置全部窗口', role: 'front' }
  ];
}

Windows/Linux 处理

Windows 和 Linux 的菜单栏显示在窗口顶部,不需要特殊的应用菜单,直接使用我们定义的中文菜单即可。

高级功能

1. 动态菜单项

根据应用状态动态显示/隐藏菜单项:

{
  label: '保存',
  enabled: hasUnsavedChanges,  // 根据状态启用/禁用
  visible: isEditorMode,        // 根据状态显示/隐藏
  click: () => {
    saveFile();
  }
}

2. 条件菜单项

根据条件显示不同的菜单项:

const fileMenu = {
  label: '文件',
  submenu: [
    { label: '新建', accelerator: 'CmdOrCtrl+N' },
    ...(isMac ? [
      { type: 'separator' },
      { label: '最近打开的文件', submenu: recentFiles }
    ] : [])
  ]
};

3. 菜单项分组

使用分隔线对菜单项进行分组:

{
  label: '文件',
  submenu: [
    { label: '新建' },
    { label: '打开' },
    { type: 'separator' },  // 第一组结束
    { label: '保存' },
    { label: '另存为' },
    { type: 'separator' },  // 第二组结束
    { label: '退出' }
  ]
}

4. 与渲染进程通信

菜单项点击时,通过 IPC 与渲染进程通信:

{
  label: '新建',
  click: () => {
    // 向渲染进程发送消息
    if (mainWindow) {
      mainWindow.webContents.send('menu:new');
    }
  }
}

在渲染进程中监听:

// preload.js
const { ipcRenderer } = require('electron');
​
window.electronAPI = {
  onMenuNew: (callback) => {
    ipcRenderer.on('menu:new', callback);
  }
};
​
// renderer.js
window.electronAPI.onMenuNew(() => {
  // 处理新建操作
  console.log('用户点击了新建菜单');
});

最佳实践

1. 菜单结构设计

  • 文件菜单: 新建、打开、保存、另存为、最近文件、退出

  • 编辑菜单: 撤销、重做、剪切、复制、粘贴、查找、替换

  • 视图菜单: 刷新、缩放、全屏、开发者工具

  • 窗口菜单: 最小化、最大化、关闭、新建窗口

  • 帮助菜单: 帮助文档、关于、检查更新

2. 快捷键设计

遵循各平台的快捷键规范:

  • 通用快捷键: 使用 CmdOrCtrl 实现跨平台

  • 平台特定: macOS 使用 Command,Windows/Linux 使用 Ctrl

  • 功能键: F11(全屏)、F12(开发者工具)等

3. 使用 role 属性

尽可能使用 role 属性,而不是自定义 click 事件:

// ✅ 推荐:使用 role
{ label: '复制', role: 'copy' }
​
// ❌ 不推荐:自定义实现
{ 
  label: '复制',
  click: () => {
    // 手动实现复制逻辑
  }
}

4. 错误处理

在菜单项点击事件中添加错误处理:

{
  label: '打开文件',
  click: async () => {
    try {
      const result = await dialog.showOpenDialog(mainWindow, {
        properties: ['openFile']
      });
      if (!result.canceled) {
        // 处理文件
      }
    } catch (error) {
      console.error('打开文件失败:', error);
      dialog.showErrorBox('错误', '无法打开文件');
    }
  }
}

5. 菜单项状态管理

根据应用状态动态更新菜单项:

function updateMenu() {
  const menu = Menu.getApplicationMenu();
  const fileMenu = menu.items.find(item => item.label === '文件');
  const saveItem = fileMenu.submenu.items.find(item => item.label === '保存');
  
  // 更新菜单项状态
  saveItem.enabled = hasUnsavedChanges;
}

常见问题

Q1: 菜单栏不显示?

原因: Menu.setApplicationMenu() 未在 app.ready 事件后调用。

解决方案: 确保菜单创建逻辑在 app.whenReady() 内部:

app.whenReady().then(() => {
  createWindow();
  createApplicationMenu(); // ✅ 正确位置
});

Q2: macOS 菜单显示为英文?

原因: 使用了 role 属性,系统会自动使用系统语言。

解决方案: 如果希望完全控制菜单文本,不使用 role,而是自定义 click 事件:

// 使用中文标签,自定义实现
{ 
  label: '复制',
  accelerator: 'CmdOrCtrl+C',
  click: () => {
    // 自定义复制逻辑
  }
}

Q3: 快捷键不生效?

原因: 快捷键格式错误或与其他应用冲突。

解决方案:

  • 检查快捷键格式是否正确

  • 使用 CmdOrCtrl 而不是 CmdCtrl

  • 避免使用系统保留的快捷键

Q4: 如何禁用某个菜单项?

使用 enabled 属性:

{
  label: '保存',
  enabled: false,  // 禁用菜单项
  click: () => {}
}

Q5: 如何隐藏某个菜单项?

使用 visible 属性:

{
  label: '高级功能',
  visible: isAdvancedMode,  // 根据条件显示/隐藏
  click: () => {}
}

总结

本文详细介绍了如何在 Electron 应用中实现完整的中文菜单栏,包括:

  1. 基础实现: 创建菜单模板、设置菜单栏

  2. 跨平台适配: macOS 特殊处理、Windows/Linux /HarmonyOS PC适配

  3. 高级功能: 动态菜单、条件显示、IPC 通信

  4. 最佳实践: 菜单结构设计、快捷键规范、错误处理

  5. 问题解决: 常见问题及解决方案

通过本文的指导,你可以为 Electron 应用创建一个完整、专业、用户友好的中文菜单栏,提升用户体验和应用的专业度。

完整代码示例

完整的实现代码已包含在本文中,你可以直接复制使用。记住:

  • app.whenReady() 中调用 createApplicationMenu()

  • 根据实际需求调整菜单项和功能

  • 测试不同平台下的菜单显示效果

  • 遵循各平台的菜单设计规范

希望本文能帮助你成功实现 Electron 中文菜单栏!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐