Appearance
第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 实现步骤
- 修改 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()
})- 修改 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>- 创建资源文件夹和图标
- 创建
assets文件夹 - 添加
icon.png(应用图标) - 添加
tray.png(托盘图标)
7.4.3 运行效果
启动应用:
bashnpm start测试功能:
- 点击 "发送通知" 按钮,查看系统通知
- 点击 "打开文件" 按钮,选择并读取文本文件
- 查看系统托盘图标,点击查看菜单选项
- 使用应用菜单栏测试功能
7.5 小结
通过本章的学习,你已经掌握了 Electron 原生功能访问的核心 API:
- 桌面通知:使用 Notification 模块发送系统通知
- 系统菜单:使用 Menu 模块创建应用菜单和上下文菜单
- 系统托盘:使用 Tray 模块创建系统托盘图标和菜单
- 文件对话框:使用 dialog 模块打开和保存文件
- 文件系统操作:使用 Node.js fs 模块进行文件读写
这些功能是 Electron 应用开发的重要组成部分,掌握它们可以帮助你创建更加功能丰富和用户友好的桌面应用。在接下来的章节中,我们将学习 Electron 与前端框架的集成以及网络请求和本地存储。
