Electron 原生功能集成

Electron 之所以强大,是因为它不仅能写前端,还能访问系统的原生功能,比如菜单、对话框、托盘、通知和全局快捷键等。

本章节我们来看看如何通过主进程模块调用这些功能。


菜单系统

Electron 提供 Menu 模块用于创建菜单栏、上下文菜单和系统托盘菜单。

应用菜单(Menu)

应用菜单是显示在窗口顶部(macOS)或应用窗口中的菜单栏。

实例

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

const template = [
  {
    label: '文件',
    submenu: [
      { label: '新建文件', accelerator: 'CmdOrCtrl+N', click: () => console.log('新建文件') },
      { label: '打开...', accelerator: 'CmdOrCtrl+O' },
      { type: 'separator' },
      { label: '退出', role: 'quit' }
    ]
  },
  {
    label: '编辑',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' }
    ]
  }
]

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

说明:

  • label:菜单标题。
  • submenu:子菜单数组。
  • accelerator:快捷键(macOS、Windows 通用写法 CmdOrCtrl+X)。
  • role:系统预定义行为,如 quitcopypaste
  • click:点击事件回调函数。

上下文菜单(Context Menu)

右键菜单通常在渲染进程中触发:

实例

// main.js
const { ipcMain, Menu } = require('electron')

ipcMain.on('show-context-menu', (event) => {
  const menu = Menu.buildFromTemplate([
    { label: '刷新', click: () => event.sender.reload() },
    { label: '复制', role: 'copy' },
    { label: '粘贴', role: 'paste' }
  ])
  menu.popup()
})

实例

// renderer.js
window.addEventListener('contextmenu', (e) => {
  e.preventDefault()
  window.electronAPI.showContextMenu()
})

系统托盘菜单

在系统通知区域显示图标与菜单。

实例

const { Tray, Menu, nativeImage } = require('electron')
let tray = null

function createTray() {
  const icon = nativeImage.createFromPath('icon.png')
  tray = new Tray(icon)
  const contextMenu = Menu.buildFromTemplate([
    { label: '打开主窗口', click: () => mainWindow.show() },
    { label: '退出', click: () => app.quit() }
  ])
  tray.setToolTip('我的 Electron 应用')
  tray.setContextMenu(contextMenu)
}

菜单快捷键

通过 accelerator 定义。例如:

{ label: '保存', accelerator: 'CmdOrCtrl+S', click: saveFile }
  • CmdOrCtrl:自动适配 macOS/Windows。
  • Alt+F4:Windows 关闭窗口。
  • Cmd+Q:macOS 退出应用。

系统对话框

dialog 模块提供原生文件、消息、错误等系统对话框。

文件/文件夹选择

const { dialog } = require('electron')
async function openFileDialog() {
  const result = await dialog.showOpenDialog({
    title: '选择文件',
    properties: ['openFile', 'multiSelections'],
    filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
  })
  console.log(result.filePaths)
}

保存对话框

async function saveFileDialog() {
  const result = await dialog.showSaveDialog({
    title: '保存文件',
    defaultPath: '新建文档.txt'
  })
  console.log(result.filePath)
}

消息/错误对话框

dialog.showMessageBox({
  type: 'info',
  title: '提示',
  message: '操作已完成!'
})

dialog.showErrorBox('错误', '文件保存失败,请重试。')

系统托盘

托盘(Tray)让应用在后台运行时仍能通过系统图标交互。

创建托盘图标

const { Tray } = require('electron')
const path = require('path')

let tray = new Tray(path.join(__dirname, 'trayIcon.png'))

托盘菜单与提示

const contextMenu = Menu.buildFromTemplate([
  { label: '显示窗口', click: () => mainWindow.show() },
  { label: '退出', click: () => app.quit() }
])
tray.setToolTip('Electron 应用运行中')
tray.setContextMenu(contextMenu)

托盘通知

tray.displayBalloon({
  icon: path.join(__dirname, 'icon.png'),
  title: '新消息',
  content: '您有一条新通知'
})

最小化到托盘

监听关闭事件隐藏窗口:

mainWindow.on('close', (event) => {
  event.preventDefault()
  mainWindow.hide()
})

通知(Notifications)

Electron 的 Notification 类可跨平台显示系统通知。

系统通知发送

new Notification({
  title: '任务完成',
  body: '您的文件已下载完成'
}).show()

通知交互处理

const notif = new Notification({ title: '点击通知', body: '点击我触发事件' })
notif.on('click', () => {
  console.log('用户点击了通知')
})
notif.show()

跨平台兼容性

  • macOS、Windows、Linux 均支持;
  • macOS 需启用"通知权限";
  • Linux 通常需系统支持 libnotify

全局快捷键

通过 globalShortcut 注册系统级热键。

注册全局快捷键

const { globalShortcut } = require('electron')

app.whenReady().then(() => {
  globalShortcut.register('CmdOrCtrl+Shift+I', () => {
    console.log('快捷键触发:打开调试工具')
    mainWindow.webContents.openDevTools()
  })
})

快捷键冲突处理

注册前可检测:

if (globalShortcut.isRegistered('CmdOrCtrl+Shift+I')) {
  console.log('快捷键已被占用')
}

平台差异处理

可用条件判断:

const shortcut = process.platform === 'darwin' ? 'Cmd+Shift+T' : 'Ctrl+Shift+T'
globalShortcut.register(shortcut, () => console.log('快捷键触发'))

小结

功能 模块 典型用途
菜单 Menu 应用菜单、右键菜单、托盘菜单
对话框 dialog 打开文件、保存文件、显示提示或错误
托盘 Tray 后台运行、系统状态展示
通知 Notification 提示用户事件进度、系统提醒
全局快捷键 globalShortcut 注册系统级快捷操作

Electron 原生功能演示项目

项目结构

my-electron-native-demo/
├── package.json
├── main.js             # 主进程逻辑
├── preload.js          # 预加载脚本(安全通信)
└── index.html          # 主界面

package.json

注意:不要写注释在 JSON 文件里(否则会报 EJSONPARSE 错误)

{
  "name": "my-electron-native-demo",
  "version": "1.0.0",
  "description": "Electron 原生功能演示项目",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "latest"
  }
}

安装依赖:

npm install

启动应用:

npm start

main.js(主进程)

实例

const { app, BrowserWindow, Menu, Tray, dialog, Notification, globalShortcut, ipcMain } = require('electron')
const path = require('path')

let mainWindow, tray

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 900,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  mainWindow.loadFile('index.html')

  // 页面加载完成后显示
  mainWindow.once('ready-to-show', () => mainWindow.show())

  // 监听窗口关闭:最小化到托盘
  mainWindow.on('close', (event) => {
    event.preventDefault()
    mainWindow.hide()
  })
}

function createMenu() {
  const template = [
    {
      label: '文件',
      submenu: [
        { label: '打开文件', click: openFile },
        { label: '保存文件', click: saveFile },
        { type: 'separator' },
        { label: '退出', click: () => app.quit() }
      ]
    },
    {
      label: '工具',
      submenu: [
        { label: '显示通知', click: showNotification },
        { label: '关于', click: showAbout }
      ]
    }
  ]

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

function createTray() {
  const icon = path.join(__dirname, 'icon.png')  // 请准备一张 16x16 或 32x32 图标
  tray = new Tray(icon)
  const contextMenu = Menu.buildFromTemplate([
    { label: '显示窗口', click: () => mainWindow.show() },
    { label: '退出', click: () => app.quit() }
  ])
  tray.setToolTip('Electron 原生功能演示')
  tray.setContextMenu(contextMenu)
}

function registerShortcuts() {
  globalShortcut.register('CmdOrCtrl+Shift+I', () => {
    mainWindow.webContents.openDevTools()
  })
}

async function openFile() {
  const result = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
  })
  if (!result.canceled && result.filePaths.length > 0) {
    mainWindow.webContents.send('file-opened', result.filePaths[0])
  }
}

async function saveFile() {
  const result = await dialog.showSaveDialog({
    title: '保存文件',
    defaultPath: '新建文档.txt'
  })
  if (!result.canceled) {
    dialog.showMessageBox({ type: 'info', message: `文件将保存到:${result.filePath}` })
  }
}

function showNotification() {
  const notif = new Notification({
    title: 'Electron 通知',
    body: '这是一条系统通知!'
  })
  notif.on('click', () => {
    dialog.showMessageBox({ message: '你点击了通知!' })
  })
  notif.show()
}

function showAbout() {
  dialog.showMessageBox({
    type: 'info',
    title: '关于',
    message: 'Electron 原生功能演示项目\n版本:1.0.0'
  })
}

// 渲染进程调用
ipcMain.handle('show-dialog', async () => {
  const result = await dialog.showMessageBox({
    type: 'info',
    title: '来自主进程',
    message: '这是主进程显示的对话框!'
  })
  return result.response
})

// 生命周期
app.whenReady().then(() => {
  createWindow()
  createMenu()
  createTray()
  registerShortcuts()
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

preload.js(安全桥梁)

实例

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  showDialog: () => ipcRenderer.invoke('show-dialog')
})

index.html(前端界面)

实例

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Electron 原生功能演示</title>
  <style>
    body { font-family: sans-serif; text-align: center; margin-top: 80px; }
    button { padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; }
    #filePath { color: #666; font-size: 14px; }
  </style>
</head>
<body>
  <h1>Electron 原生功能演示</h1>
  <p><button id="dialogBtn">显示对话框</button></p>
  <p id="filePath"></p>

  <script>
    document.getElementById('dialogBtn').addEventListener('click', async () => {
      await window.electronAPI.showDialog()
    })

    const filePathEl = document.getElementById('filePath')
    window.electronAPI && window.electronAPI.fileOpened && window.electronAPI.fileOpened((path) => {
     filePathEl.textContent = '打开的文件路径:' + path
   })
 </script>
</body>
</html>

运行 npm start 后,功能如下:

  • 顶部菜单栏包含 "文件"、"工具" 菜单。
  • "文件" → 打开文件 / 保存文件 调出系统对话框。
  • "工具" → 显示系统通知、显示"关于"窗口。
  • 托盘区域出现应用图标,可右键显示菜单。
  • 窗口关闭后最小化到托盘。
  • 注册全局快捷键 CmdOrCtrl+Shift+I 打开开发者工具。