Skip to content

第7章:Electron 原生功能访问

7.1 桌面通知(Notification)

7.1.1 通知创建与配置

Electron 提供了 Notification 模块,允许应用发送系统桌面通知。这对于需要向用户显示重要信息或提醒的应用非常有用。

7.1.1.1 基本用法

javascript
const { Notification } = require('electron')

// 创建通知
const notification = new Notification({
  title: '通知标题',
  body: '这是通知的内容',
  icon: __dirname + '/assets/icon.png' // 可选,通知图标
})

// 显示通知
notification.show()

7.1.1.2 通知配置选项

  • title:通知标题(必需)
  • body:通知内容(必需)
  • icon:通知图标路径
  • subtitle:通知副标题(macOS)
  • silent:是否静默显示通知(不播放声音)
  • hasReply:是否显示回复字段(macOS)
  • replyPlaceholder:回复字段的占位文本(macOS)
  • actions:通知操作按钮(macOS)
  • urgency:通知紧急程度(Linux)

7.1.2 通知事件

javascript
const { Notification } = require('electron')

const notification = new Notification({
  title: '通知标题',
  body: '点击通知查看详情'
})

// 监听通知点击事件
notification.on('click', () => {
  console.log('用户点击了通知')
  // 执行相应操作,如打开应用窗口
  mainWindow.show()
})

// 监听通知关闭事件
notification.on('close', () => {
  console.log('通知被关闭')
})

// 监听通知回复事件(macOS)
notification.on('reply', (event, reply) => {
  console.log('用户回复:', reply)
})

// 监听通知操作事件(macOS)
notification.on('action', (event, action) => {
  console.log('用户执行了操作:', action)
})

notification.show()

7.2 系统菜单与托盘

7.2.1 应用菜单(Menu)

Electron 提供了 Menu 模块,允许创建应用菜单和上下文菜单。

7.2.1.1 创建应用菜单

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

// 菜单模板
const template = [
  {
    label: '文件',
    submenu: [
      {
        label: '新建',
        accelerator: 'CmdOrCtrl+N',
        click: () => console.log('新建')
      },
      {
        label: '打开',
        accelerator: 'CmdOrCtrl+O',
        click: () => console.log('打开')
      },
      { type: 'separator' },
      {
        label: '退出',
        accelerator: 'CmdOrCtrl+Q',
        click: () => app.quit()
      }
    ]
  },
  {
    label: '编辑',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' }
    ]
  },
  {
    label: '帮助',
    submenu: [
      {
        label: '关于',
        click: () => console.log('关于')
      }
    ]
  }
]

// 构建菜单
const menu = Menu.buildFromTemplate(template)

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

7.2.1.2 创建上下文菜单

javascript
const { Menu } = require('electron')

// 上下文菜单模板
const contextMenuTemplate = [
  {
    label: '复制',
    role: 'copy'
  },
  {
    label: '粘贴',
    role: 'paste'
  },
  { type: 'separator' },
  {
    label: '全选',
    role: 'selectall'
  }
]

// 构建上下文菜单
const contextMenu = Menu.buildFromTemplate(contextMenuTemplate)

// 在渲染进程中监听右键点击事件
// 渲染进程代码:
document.addEventListener('contextmenu', (e) => {
  e.preventDefault()
  ipcRenderer.send('show-context-menu')
})

// 主进程中显示上下文菜单
ipcMain.on('show-context-menu', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender)
  contextMenu.popup({ window: win })
})

7.2.2 系统托盘(Tray)

Electron 提供了 Tray 模块,允许创建系统托盘图标和菜单。

7.2.2.1 创建系统托盘

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

let tray

function createTray() {
  // 创建托盘图标
  tray = new Tray(path.join(__dirname, 'assets', 'tray.png'))
  
  // 创建托盘菜单
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '显示窗口',
      click: () => mainWindow.show()
    },
    {
      label: '隐藏窗口',
      click: () => mainWindow.hide()
    },
    { type: 'separator' },
    {
      label: '退出',
      click: () => app.quit()
    }
  ])
  
  // 设置托盘提示
  tray.setToolTip('My Electron App')
  
  // 设置托盘菜单
  tray.setContextMenu(contextMenu)
  
  // 点击托盘显示/隐藏窗口
  tray.on('click', () => {
    if (mainWindow.isVisible()) {
      mainWindow.hide()
    } else {
      mainWindow.show()
    }
  })
}

// 在应用就绪后创建托盘
app.whenReady().then(() => {
  createWindow()
  createTray()
})

7.3 文件系统操作

7.3.1 文件对话框(dialog)

Electron 提供了 dialog 模块,允许显示原生文件对话框,用于打开和保存文件。

7.3.1.1 打开文件对话框

javascript
const { dialog } = require('electron')

// 打开文件对话框
async function openFile() {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ['openFile', 'multiSelections'],
    filters: [
      { name: '文本文件', extensions: ['txt', 'md'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  })
  
  if (!canceled) {
    console.log('选择的文件:', filePaths)
    return filePaths
  }
}

// 调用函数
openFile()

7.3.1.2 保存文件对话框

javascript
const { dialog } = require('electron')

// 保存文件对话框
async function saveFile() {
  const { canceled, filePath } = await dialog.showSaveDialog({
    defaultPath: 'document.txt',
    filters: [
      { name: '文本文件', extensions: ['txt'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  })
  
  if (!canceled) {
    console.log('保存路径:', filePath)
    return filePath
  }
}

// 调用函数
saveFile()

7.3.2 文件系统操作(fs)

Electron 可以使用 Node.js 的 fs 模块进行文件系统操作。由于这些操作可能涉及到敏感权限,建议在主进程中执行。

7.3.2.1 读取文件

javascript
const fs = require('fs')
const path = require('path')

// 读取文件
function readFile(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

// 调用函数
async function main() {
  try {
    const content = await readFile(path.join(__dirname, 'test.txt'))
    console.log('文件内容:', content)
  } catch (error) {
    console.error('读取文件失败:', error)
  }
}

main()

7.3.2.2 写入文件

javascript
const fs = require('fs')
const path = require('path')

// 写入文件
function writeFile(filePath, content) {
  return new Promise((resolve, reject) => {
    fs.writeFile(filePath, content, 'utf8', (err) => {
      if (err) {
        reject(err)
      } else {
        resolve('文件写入成功')
      }
    })
  })
}

// 调用函数
async function main() {
  try {
    const result = await writeFile(
      path.join(__dirname, 'output.txt'),
      'Hello, Electron!\nThis is a test file.'
    )
    console.log(result)
  } catch (error) {
    console.error('写入文件失败:', error)
  }
}

main()

7.4 实操案例:原生功能集成

7.4.1 场景描述

创建一个具有以下功能的 Electron 应用:

  • 发送系统桌面通知
  • 显示自定义应用菜单
  • 创建系统托盘图标
  • 打开文件对话框选择文件
  • 读取并显示文件内容

7.4.2 实现步骤

  1. 修改 main.js
javascript
const { app, BrowserWindow, ipcMain, Notification, Menu, Tray, dialog } = require('electron')
const fs = require('fs')
const path = require('path')

let mainWindow
let tray

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  })

  mainWindow.loadFile('index.html')
  mainWindow.webContents.openDevTools()

  // 处理发送通知请求
  ipcMain.on('send-notification', (event, message) => {
    const notification = new Notification({
      title: '应用通知',
      body: message,
      icon: path.join(__dirname, 'assets', 'icon.png')
    })
    notification.show()
  })

  // 处理打开文件请求
  ipcMain.handle('open-file-dialog', async () => {
    const { canceled, filePaths } = await dialog.showOpenDialog({
      properties: ['openFile'],
      filters: [
        { name: '文本文件', extensions: ['txt', 'md'] },
        { name: '所有文件', extensions: ['*'] }
      ]
    })
    
    if (!canceled && filePaths.length > 0) {
      return filePaths[0]
    }
    return null
  })

  // 处理读取文件请求
  ipcMain.handle('read-file', async (event, filePath) => {
    return new Promise((resolve, reject) => {
      fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      })
    })
  })

  // 窗口关闭事件
  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

function createMenu() {
  const template = [
    {
      label: '文件',
      submenu: [
        {
          label: '打开文件',
          accelerator: 'CmdOrCtrl+O',
          click: () => {
            mainWindow.webContents.send('open-file')
          }
        },
        { type: 'separator' },
        {
          label: '退出',
          accelerator: 'CmdOrCtrl+Q',
          click: () => app.quit()
        }
      ]
    },
    {
      label: '工具',
      submenu: [
        {
          label: '发送通知',
          click: () => {
            mainWindow.webContents.send('send-notification')
          }
        }
      ]
    },
    {
      label: '帮助',
      submenu: [
        {
          label: '关于',
          click: () => {
            const notification = new Notification({
              title: '关于',
              body: 'Electron 原生功能示例应用',
              icon: path.join(__dirname, 'assets', 'icon.png')
            })
            notification.show()
          }
        }
      ]
    }
  ]

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

function createTray() {
  tray = new Tray(path.join(__dirname, 'assets', 'tray.png'))
  
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '显示窗口',
      click: () => mainWindow.show()
    },
    {
      label: '隐藏窗口',
      click: () => mainWindow.hide()
    },
    { type: 'separator' },
    {
      label: '发送测试通知',
      click: () => {
        const notification = new Notification({
          title: '托盘通知',
          body: '这是来自系统托盘的测试通知',
          icon: path.join(__dirname, 'assets', 'icon.png')
        })
        notification.show()
      }
    },
    { type: 'separator' },
    {
      label: '退出',
      click: () => app.quit()
    }
  ])
  
  tray.setToolTip('Electron 原生功能示例')
  tray.setContextMenu(contextMenu)
  
  tray.on('click', () => {
    if (mainWindow.isVisible()) {
      mainWindow.hide()
    } else {
      mainWindow.show()
    }
  })
}

app.whenReady().then(() => {
  createWindow()
  createMenu()
  createTray()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})
  1. 修改 index.html
html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>原生功能示例</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
      padding: 0;
      background-color: #f0f0f0;
    }
    .container {
      max-width: 700px;
      margin: 0 auto;
      background-color: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    h1 {
      color: #333;
      text-align: center;
    }
    .section {
      margin: 20px 0;
      padding: 15px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    h2 {
      color: #555;
      margin-top: 0;
    }
    button {
      padding: 10px 15px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      margin: 5px;
    }
    button:hover {
      background-color: #45a049;
    }
    #file-content {
      width: 100%;
      height: 200px;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      resize: vertical;
      font-family: monospace;
    }
    #status {
      margin-top: 10px;
      padding: 10px;
      background-color: #f9f9f9;
      border-radius: 4px;
      font-size: 14px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>Electron 原生功能示例</h1>
    
    <div class="section">
      <h2>桌面通知</h2>
      <button id="send-notification">发送通知</button>
    </div>
    
    <div class="section">
      <h2>文件操作</h2>
      <button id="open-file">打开文件</button>
      <textarea id="file-content" placeholder="文件内容将显示在这里"></textarea>
      <div id="status"></div>
    </div>
    
    <div class="section">
      <h2>系统托盘</h2>
      <p>查看系统托盘区域,点击托盘图标查看菜单选项。</p>
    </div>
  </div>
  
  <script>
    const { ipcRenderer } = require('electron')
    
    // 发送通知按钮点击事件
    document.getElementById('send-notification').addEventListener('click', () => {
      ipcRenderer.send('send-notification', '这是一条测试通知')
    })
    
    // 打开文件按钮点击事件
    document.getElementById('open-file').addEventListener('click', async () => {
      try {
        const filePath = await ipcRenderer.invoke('open-file-dialog')
        if (filePath) {
          const content = await ipcRenderer.invoke('read-file', filePath)
          document.getElementById('file-content').value = content
          document.getElementById('status').textContent = `已读取文件: ${filePath}`
        }
      } catch (error) {
        document.getElementById('status').textContent = `错误: ${error.message}`
      }
    })
    
    // 监听来自主进程的消息
    ipcRenderer.on('open-file', () => {
      document.getElementById('open-file').click()
    })
    
    ipcRenderer.on('send-notification', () => {
      document.getElementById('send-notification').click()
    })
  </script>
</body>
</html>
  1. 创建资源文件夹和图标
  • 创建 assets 文件夹
  • 添加 icon.png(应用图标)
  • 添加 tray.png(托盘图标)

7.4.3 运行效果

  1. 启动应用

    bash
    npm start
  2. 测试功能

    • 点击 "发送通知" 按钮,查看系统通知
    • 点击 "打开文件" 按钮,选择并读取文本文件
    • 查看系统托盘图标,点击查看菜单选项
    • 使用应用菜单栏测试功能

7.5 小结

通过本章的学习,你已经掌握了 Electron 原生功能访问的核心 API:

  • 桌面通知:使用 Notification 模块发送系统通知
  • 系统菜单:使用 Menu 模块创建应用菜单和上下文菜单
  • 系统托盘:使用 Tray 模块创建系统托盘图标和菜单
  • 文件对话框:使用 dialog 模块打开和保存文件
  • 文件系统操作:使用 Node.js fs 模块进行文件读写

这些功能是 Electron 应用开发的重要组成部分,掌握它们可以帮助你创建更加功能丰富和用户友好的桌面应用。在接下来的章节中,我们将学习 Electron 与前端框架的集成以及网络请求和本地存储。

© 2026 编程马·菜鸟教程 版权所有