系统通知(Notification)
基本用法
const { Notification } = require('electron');
// 检查是否支持通知
if (Notification.isSupported()) {
const notification = new Notification({
title: '文件保存成功',
body: '您的文档已保存到 /Documents/report.pdf',
icon: path.join(__dirname, 'icon.png'),
silent: false // 是否静音
});
notification.show();
// 监听点击事件
notification.on('click', () => {
console.log('Notification clicked');
// 聚焦主窗口
const mainWindow = BrowserWindow.getAllWindows()[0];
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
// 监听关闭事件
notification.on('close', () => {
console.log('Notification closed');
});
}
macOS 通知设置
macOS 的通知需要请求权限:
// macOS 上需要请求通知权限
if (process.platform === 'darwin') {
const { Notification } = require('electron');
// macOS 会自动请求权限,或者用户首次调用 Notification 时系统会弹出授权框
}
通知按钮(macOS)
const notification = new Notification({
title: '新消息',
body: '来自张三的消息:今晚开会吗?',
actions: [
{ type: 'button', text: '回复' },
{ type: 'button', text: '忽略' }
]
});
notification.on('action', (event, index) => {
if (index === 0) {
// 回复
openReplyWindow();
} else {
// 忽略
console.log('Ignored');
}
});
通知与 IPC
主进程管理通知
// main.js
const { Notification, ipcMain, BrowserWindow } = require('electron');
// 处理来自渲染进程的通知请求
ipcMain.handle('notification:show', async (event, options) => {
if (!Notification.isSupported()) {
return { success: false, error: 'Notifications not supported' };
}
const notification = new Notification({
title: options.title,
body: options.body,
icon: options.icon,
silent: options.silent
});
return new Promise((resolve) => {
notification.on('click', () => {
// 通知被点击
resolve({ clicked: true });
});
notification.on('close', () => {
resolve({ clicked: false });
});
notification.show();
});
});
// 显示带操作的通知
ipcMain.handle('notification:show-with-action', async (event, options) => {
const notification = new Notification({
title: options.title,
body: options.body,
actions: options.actions || []
});
return new Promise((resolve) => {
notification.on('action', (e, index) => {
resolve({ action: index });
});
notification.on('click', () => {
resolve({ action: 'click' });
});
notification.show();
});
});
渲染进程调用通知
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
showNotification: (options) => {
return ipcRenderer.invoke('notification:show', options);
}
});
// renderer.js
async function notifyUser(message) {
const result = await window.electronAPI.showNotification({
title: 'My App',
body: message,
icon: '/icon.png'
});
if (result.clicked) {
console.log('User clicked notification');
}
}
进度通知
// 带有进度条的通知(Windows/macOS)
const notification = new Notification({
title: '下载中',
body: '正在下载 document.pdf',
silent: true
});
notification.progress = 0;
// 模拟进度更新
let progress = 0;
const interval = setInterval(() => {
progress += 10;
notification.progress = progress / 100;
if (progress >= 100) {
clearInterval(interval);
notification.body = '下载完成';
notification.progress = -1; // 隐藏进度条
}
}, 1000);
剪贴板(Clipboard)
基本操作
const { clipboard } = require('electron');
// 读取文本
const text = clipboard.readText();
// 写入文本
clipboard.writeText('Hello, clipboard!');
// 清除
clipboard.clear();
读取不同格式
const { clipboard } = require('electron');
// 读取纯文本
const plainText = clipboard.readText();
// 读取 HTML
const html = clipboard.readHTML();
// 读取 RTF(富文本)
const rtf = clipboard.readRTF();
// 读取图片
const image = clipboard.readImage();
// 检查剪贴板内容
const formats = clipboard.availableFormats();
console.log(formworks);
// ['text/plain', 'text/html', 'text/rtf', 'image/png']
写入不同格式
const { clipboard, nativeImage } = require('electron');
// 写入纯文本
clipboard.writeText('Plain text content');
// 写入 HTML
clipboard.writeHTML('<strong>Bold</strong> and <em>italic</em>');
// 写入 RTF
clipboard.writeRTF('{\\rtf1\\ansi Rich Text Content}');
// 写入图片
const image = nativeImage.createFromPath('/path/to/image.png');
clipboard.writeImage(image);
复制图片
const { clipboard, nativeImage } = require('electron');
// 从文件复制图片
const image = nativeImage.createFromPath('/path/to/screenshot.png');
clipboard.writeImage(image);
// 从 URL 复制图片
async function copyImageFromURL(url) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const image = nativeImage.createFromBuffer(Buffer.from(buffer));
clipboard.writeImage(image);
}
// 获取图片的 Base64
function getImageAsDataURL(image) {
return image.toDataURL();
}
// 从剪贴板获取图片并保存
function saveImageFromClipboard() {
const image = clipboard.readImage();
if (!image.isEmpty()) {
const buffer = image.toPNG();
require('fs').writeFileSync('clipboard-image.png', buffer);
}
}
剪贴板与 IPC
主进程处理剪贴板
// main.js
ipcMain.handle('clipboard:read', (event, format) => {
switch (format) {
case 'text':
return clipboard.readText();
case 'html':
return clipboard.readHTML();
case 'image':
return clipboard.readImage().toPNG().toString('base64');
default:
return null;
}
});
ipcMain.handle('clipboard:write', (event, format, content) => {
switch (format) {
case 'text':
clipboard.writeText(content);
break;
case 'html':
clipboard.writeHTML(content);
break;
case 'image':
const image = nativeImage.createFromDataURL(content);
clipboard.writeImage(image);
break;
}
return true;
});
渲染进程使用剪贴板
// renderer.js
async function copyToClipboard(text) {
await window.electronAPI.clipboardWrite('text', text);
}
async function pasteFromClipboard() {
return await window.electronAPI.clipboardRead('text');
}
高级用法
监听剪贴板变化
// 使用定时器检查剪贴板变化(轮询方式)
let lastClipboardContent = clipboard.readText();
setInterval(() => {
const currentContent = clipboard.readText();
if (currentContent !== lastClipboardContent) {
console.log('Clipboard changed!');
lastClipboardContent = currentContent;
// 触发事件
mainWindow.webContents.send('clipboard:changed', currentContent);
}
}, 1000);
复制格式化文本
function copyFormattedText(htmlContent, plainContent) {
const { clipboard } = require('electron');
// 同时写入 HTML 和纯文本
// 粘贴到富文本编辑器会使用 HTML,粘贴到纯文本编辑器会使用 plainContent
clipboard.write({
text: plainContent,
html: htmlContent
});
}
// 使用示例
copyFormattedText(
'<p><strong>粗体文字</strong> 和 <em>斜体文字</em></p>',
'粗体文字 和 斜体文字'
);
复制代码片段
function copyCodeSnippet(code, language = 'javascript') {
const { clipboard } = require('electron');
// 写入带语言的纯文本
// VS Code 等编辑器会根据语言识别
const formattedCode = `\`\`\`${language}\n${code}\n\`\`\``;
clipboard.writeText(formattedCode);
}
// 使用示例
copyCodeSnippet(`
function hello() {
console.log('Hello, World!');
}
`, 'javascript');
实际应用场景
1. 截图并复制
const { desktopCapturer, clipboard, nativeImage } = require('electron');
async function captureAndCopy() {
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 1920, height: 1080 }
});
if (sources.length > 0) {
const screenshot = sources[0].thumbnail;
clipboard.writeImage(screenshot);
return true;
}
return false;
}
2. 复制文件路径
const { clipboard, shell } = require('electron');
function copyFilePath(filePath) {
// Windows 需要使用反斜杠
const formattedPath = process.platform === 'win32'
? filePath.replace(/\//g, '\\')
: filePath;
clipboard.writeText(formattedPath);
}
// 复制文件链接(macOS)
function copyFileLink(filePath) {
shell.writeShortcutLink; // 需要创建快捷方式
}
3. 实现粘贴板历史
class ClipboardHistory {
constructor(maxItems = 50) {
this.history = [];
this.maxItems = maxItems;
this.pollInterval = null;
}
start() {
let lastContent = clipboard.readText();
this.pollInterval = setInterval(() => {
const currentContent = clipboard.readText();
if (currentContent && currentContent !== lastContent) {
this.add(currentContent);
lastContent = currentContent;
}
}, 500);
}
stop() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
}
add(content) {
// 去重
this.history = this.history.filter(item => item !== content);
// 添加到开头
this.history.unshift(content);
// 限制数量
if (this.history.length > this.maxItems) {
this.history.pop();
}
}
get(index) {
return this.history[index];
}
clear() {
this.history = [];
}
}
最佳实践
1. 通知使用注意
- 不要频繁发送通知,会打扰用户
- 通知内容应该简洁明了
- 点击通知后应该聚焦到相关窗口
2. 剪贴板使用注意
- 剪贴板可能包含敏感信息,不要随意读取
- 大图片会占用大量内存,谨慎处理
- 使用完毕及时清理敏感数据
3. 结合使用示例
// 当用户复制内容时,发送通知确认
clipboard.on('change', (content) => {
if (content.length > 1000) {
new Notification({
title: '已复制',
body: `已复制 ${content.length} 个字符`
}).show();
}
});
这一章想说的
系统通知和剪贴板是桌面应用的重要交互方式:
- 通知:使用 Notification API,支持点击回调和操作按钮
- 剪贴板:支持文本、HTML、图片等多种格式
- IPC 结合:渲染进程通过 IPC 调用主进程的原生能力
- 实际场景:截图复制、代码片段、粘贴板历史
合理使用这些功能可以显著提升用户体验。