Skip to content

托盘菜单

功能特性

  • ✅ 系统托盘图标显示
  • ✅ 点击图标显示/隐藏主窗口
  • ✅ 右键显示上下文菜单
  • ✅ 丰富的快捷操作
  • ✅ 动态菜单项(如悬浮球状态)

托盘初始化

创建托盘

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,其他平台为 Ctrl
  • Alt: Alt 键
  • Shift: Shift 键
  • Plus: + 号
  • Space: 空格
  • 字母和数字:A1

子菜单

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();
  }
}

工作流程:

  1. 显示悬浮球窗口
  2. 隐藏主窗口
  3. 用户可通过悬浮球快速恢复主窗口

更新检查

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/ - 图标资源目录

相关文档

基于 MIT 许可发布