托盘菜单
功能特性
- ✅ 系统托盘图标显示
- ✅ 点击图标显示/隐藏主窗口
- ✅ 右键显示上下文菜单
- ✅ 丰富的快捷操作
- ✅ 动态菜单项(如悬浮球状态)
托盘初始化
创建托盘
javascript
function addTrayMenu(win, updateHandler, FloatingBallService) {
try {
// 创建系统托盘
const tray = new Tray(getAppIcons('small'));
tray.setToolTip(APP_TITLE);
// 创建托盘菜单
const menuItems = getTrayMenuConfig(win, updateHandler, FloatingBallService);
const trayMenu = Menu.buildFromTemplate(menuItems);
// 设置托盘点击事件和上下文菜单
tray.on('click', () => {
win.isVisible() ? win.hide() : win.show();
});
tray.setContextMenu(trayMenu);
logger.info('托盘菜单设置成功');
} catch (error) {
logger.error('托盘菜单设置失败:', error);
}
}托盘图标
javascript
// 获取托盘图标(使用小尺寸图标)
const tray = new Tray(getAppIcons('small'));
// Windows: 16x16 或 32x32 .ico 文件
// macOS: 16x16 @2x .png 文件(模板图像)
// Linux: 16x16 或 22x22 .png 文件托盘菜单配置
完整菜单结构
javascript
const getTrayMenuConfig = (win, updateHandler, FloatingBallService) => [
// 关于
getAboutMessageBox(win),
// 刷新
{
label: '刷新',
accelerator: 'CmdOrCtrl+R',
click: () => win.webContents.reload(),
},
{ type: 'separator' },
// 窗口操作
{
label: '窗口操作',
submenu: [
// 缩放
{
label: '放大',
accelerator: 'CmdOrCtrl+Plus',
click: () => win.webContents.setZoomLevel(win.webContents.getZoomLevel() + 1),
},
{
label: '缩小',
accelerator: 'CmdOrCtrl+-',
click: () => win.webContents.setZoomLevel(win.webContents.getZoomLevel() - 1),
},
{ type: 'separator' },
// 窗口状态
{
label: '最大化',
click: () => win.maximize(),
},
{
label: '最小化',
accelerator: 'CmdOrCtrl+M',
click: () => win.minimize(),
},
{ type: 'separator' },
// 悬浮球
{
label: '收起到悬浮球',
click: () => {
FloatingBallService.showFloatingBall();
win.hide();
},
},
],
},
// 更新检查
{
label: '检查更新',
click: () => updateHandler.checkUpdate(true),
},
// 开发工具
{
label: '开发工具',
submenu: [
{
label: '打开开发者工具',
accelerator: 'CmdOrCtrl+Shift+I',
click: () => win.webContents.openDevTools(),
},
{
label: '重新加载',
accelerator: 'CmdOrCtrl+Shift+R',
click: () => win.webContents.reloadIgnoringCache(),
},
],
},
{ type: 'separator' },
// 退出
{
label: '退出',
accelerator: 'CmdOrCtrl+Q',
click: () => {
app.isQuitting = true;
app.quit();
},
},
];菜单项类型
普通菜单项
javascript
{
label: '菜单项名称',
click: () => {
// 点击处理逻辑
}
}快捷键
javascript
{
label: '刷新',
accelerator: 'CmdOrCtrl+R', // 跨平台快捷键
click: () => win.webContents.reload()
}常用快捷键:
CmdOrCtrl: macOS 上为 Cmd,其他平台为 CtrlAlt: Alt 键Shift: Shift 键Plus: + 号Space: 空格- 字母和数字:
A、1等
子菜单
javascript
{
label: '窗口操作',
submenu: [
{ label: '最大化', click: () => win.maximize() },
{ label: '最小化', click: () => win.minimize() },
]
}分隔符
javascript
{
type: 'separator';
}复选框菜单项
javascript
{
label: '自动启动',
type: 'checkbox',
checked: app.getLoginItemSettings().openAtLogin,
click: (menuItem) => {
app.setLoginItemSettings({
openAtLogin: menuItem.checked
});
}
}单选菜单项
javascript
{
label: '主题',
submenu: [
{
label: '浅色',
type: 'radio',
checked: theme === 'light',
click: () => setTheme('light')
},
{
label: '深色',
type: 'radio',
checked: theme === 'dark',
click: () => setTheme('dark')
}
]
}特色功能
关于对话框
javascript
const getAboutMessageBox = (win) => {
return {
label: '关于',
click: async () => {
const aboutInfo = assembleAboutInfo();
const contentToCopy = `${APP_TITLE}\n${aboutInfo}`;
const result = await dialog.showMessageBox(win, {
type: 'info',
title: '关于',
message: APP_TITLE,
icon: ICON_MAX_PATH,
detail: aboutInfo,
buttons: ['复制', '关闭'],
noLink: true,
cancelId: 1,
defaultId: 1,
});
// 如果点击了"复制"按钮
if (result.response === 0) {
clipboard.writeText(contentToCopy);
}
},
};
};显示内容:
Version版本: 1.0.0
Electron版本: 28.0.0
Chromium版本: 120.0.0
NodeJs版本: 18.17.0
V8版本: 12.0.0
OS版本: x64 Windows 10.0.19045
IP地址: 192.168.1.100
MAC地址: 00:11:22:33:44:55
Copyright©2025-2026 应用名称版权所有悬浮球集成
javascript
{
label: '收起到悬浮球',
click: () => {
FloatingBallService.showFloatingBall();
win.hide();
}
}工作流程:
- 显示悬浮球窗口
- 隐藏主窗口
- 用户可通过悬浮球快速恢复主窗口
更新检查
javascript
{
label: '检查更新',
click: () => {
updateHandler.checkUpdate(true); // manual = true
}
}manual 参数:
true: 手动检查,无更新时也会提示false: 自动检查,无更新时不提示
托盘交互
单击行为
javascript
tray.on('click', () => {
// 切换主窗口显示/隐藏
win.isVisible() ? win.hide() : win.show();
});右键菜单
javascript
// 右键点击显示上下文菜单
tray.setContextMenu(trayMenu);双击行为(可选)
javascript
tray.on('double-click', () => {
// 双击显示主窗口并聚焦
win.show();
win.focus();
});气球提示(Windows)
javascript
tray.displayBalloon({
title: '标题',
content: '内容',
icon: 'path/to/icon.png',
});
// 监听气球点击
tray.on('balloon-click', () => {
win.show();
});托盘提示文本
设置提示文本
javascript
// 静态文本
tray.setToolTip(APP_TITLE);
// 动态更新
function updateTrayTooltip(status) {
tray.setToolTip(`${APP_TITLE} - ${status}`);
}
// 使用示例
updateTrayTooltip('运行中');
updateTrayTooltip('后台运行');动态菜单更新
更新菜单项
javascript
// 重新构建菜单
function updateTrayMenu() {
const menuItems = getTrayMenuConfig(win, updateHandler, FloatingBallService);
const trayMenu = Menu.buildFromTemplate(menuItems);
tray.setContextMenu(trayMenu);
}
// 在状态改变时更新
FloatingBallService.on('state-change', () => {
updateTrayMenu();
});条件显示菜单项
javascript
const menuItems = [
{
label: '开发工具',
submenu: [...],
// 仅在开发环境显示
visible: !app.isPackaged
},
{
label: '调试模式',
type: 'checkbox',
checked: debugMode,
// 仅在启用某功能时显示
visible: isFeatureEnabled('debug')
}
];平台差异
Windows
javascript
// Windows 托盘图标
- 尺寸: 16x16 或 32x32
- 格式: .ico
- 位置: 任务栏右下角macOS
javascript
// macOS 菜单栏图标
- 尺寸: 16x16 @2x (实际 32x32)
- 格式: .png(模板图像)
- 位置: 顶部菜单栏右侧
- 特性: 支持深色/浅色模式自动切换
// 使用模板图像
tray.setImage(nativeImage.createFromPath('icon.png').resize({ width: 16, height: 16 }));Linux
javascript
// Linux 托盘图标
- 尺寸: 16x16 或 22x22
- 格式: .png
- 位置: 取决于桌面环境
- 注意: 某些桌面环境可能不支持托盘托盘图标状态
动态切换图标
javascript
// 不同状态使用不同图标
const icons = {
idle: 'icon-idle.png',
active: 'icon-active.png',
warning: 'icon-warning.png',
error: 'icon-error.png',
};
function setTrayIcon(state) {
tray.setImage(icons[state]);
}
// 使用示例
setTrayIcon('active'); // 活动状态
setTrayIcon('warning'); // 警告状态闪烁效果(Windows)
javascript
let isBlinking = false;
let blinkInterval = null;
function startBlinking() {
if (isBlinking) return;
isBlinking = true;
let visible = true;
blinkInterval = setInterval(() => {
visible = !visible;
tray.setImage(visible ? 'icon.png' : 'icon-empty.png');
}, 500);
}
function stopBlinking() {
if (!isBlinking) return;
isBlinking = false;
clearInterval(blinkInterval);
tray.setImage('icon.png');
}最佳实践
1. 优雅退出
javascript
{
label: '退出',
click: () => {
// 设置标志防止隐藏到托盘
app.isQuitting = true;
// 执行清理工作
performCleanup();
// 退出应用
app.quit();
}
}2. 窗口隐藏到托盘
javascript
win.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
win.hide();
// 首次隐藏时提示用户
if (!hasShownTrayNotification) {
tray.displayBalloon({
title: APP_TITLE,
content: '应用已最小化到托盘,点击托盘图标可恢复窗口',
});
hasShownTrayNotification = true;
}
}
});3. 菜单项国际化
javascript
const i18n = {
'zh-CN': {
show: '显示窗口',
hide: '隐藏窗口',
quit: '退出',
},
'en-US': {
show: 'Show Window',
hide: 'Hide Window',
quit: 'Quit',
},
};
const t = (key) => i18n[currentLocale][key];
const menuItems = [
{ label: t('show'), click: () => win.show() },
{ label: t('quit'), click: () => app.quit() },
];4. 性能优化
javascript
// 避免频繁重建菜单
let trayMenuCache = null;
function getTrayMenu() {
if (!trayMenuCache) {
trayMenuCache = Menu.buildFromTemplate(getTrayMenuConfig());
}
return trayMenuCache;
}
// 仅在必要时重建
function invalidateTrayMenuCache() {
trayMenuCache = null;
}5. 错误处理
javascript
try {
const tray = new Tray(getAppIcons('small'));
tray.setToolTip(APP_TITLE);
tray.setContextMenu(trayMenu);
} catch (error) {
logger.error('托盘创建失败:', error);
// 降级处理:禁用托盘功能
dialog.showMessageBox({
type: 'warning',
message: '托盘功能不可用',
detail: '应用将以窗口模式运行',
});
}托盘生命周期
创建
javascript
app.whenReady().then(() => {
const win = createWindow();
addTrayMenu(win, updateHandler, FloatingBallService);
});销毁
javascript
app.on('before-quit', () => {
if (tray) {
tray.destroy();
tray = null;
}
});常见问题
Q1: 托盘图标不显示?
原因: 图标路径错误或格式不支持
解决方案:
javascript
// 使用绝对路径
const iconPath = path.join(__dirname, 'icons', 'tray.png');
const tray = new Tray(iconPath);
// 检查文件是否存在
if (!fs.existsSync(iconPath)) {
logger.error('托盘图标文件不存在:', iconPath);
}Q2: macOS 上图标模糊?
原因: 未使用 @2x 高分辨率图标
解决方案:
javascript
// 提供高分辨率图标
const icon = nativeImage.createFromPath('tray@2x.png');
icon.setTemplateImage(true); // 设置为模板图像
tray.setImage(icon);Q3: Linux 上托盘不工作?
原因: 某些 Linux 桌面环境不支持系统托盘
解决方案:
javascript
// 检测托盘支持
if (process.platform === 'linux') {
const { systemPreferences } = require('electron');
if (!systemPreferences.getSystemTrayIcon) {
logger.warn('当前系统不支持托盘功能');
// 使用替代方案
}
}Q4: 如何在后台保持应用运行?
javascript
// 阻止窗口关闭导致应用退出
win.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
win.hide();
}
});
// 退出时设置标志
app.on('before-quit', () => {
app.isQuitting = true;
});相关文件
packages/main/src/core/tray.js- 托盘创建和管理packages/main/src/config/menu-config.js- 托盘菜单配置packages/main/src/config/app-icon-config.js- 图标配置resources/icons/- 图标资源目录