202 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
// tray = 系统托盘
 | 
						||
import path from 'path';
 | 
						||
import { Tray, Menu, app, dialog, nativeImage, BrowserWindow, Notification, ipcMain } from 'electron';
 | 
						||
import type { IpcMainInvokeEvent } from 'electron';
 | 
						||
import {_PATHS} from '../paths';
 | 
						||
import {$env, isDev} from '../env';
 | 
						||
 | 
						||
const TrayIcons = {
 | 
						||
  // update-begin--author:liaozhiyang---date:20250725---for:【JHHB-13】桌面应用消息通知
 | 
						||
  normal: nativeImage.createFromPath(
 | 
						||
    process.platform === 'win32'
 | 
						||
      ? path.join(_PATHS.publicRoot, 'logo.png')
 | 
						||
      : path.join(_PATHS.electronRoot, './icons/mac/tray-icon.png').replace(/[\\/]dist[\\/]/, '/')
 | 
						||
  ),
 | 
						||
  // update-end--author:liaozhiyang---date:20250725---for:【JHHB-13】桌面应用消息通知
 | 
						||
  empty: nativeImage.createEmpty(),
 | 
						||
};
 | 
						||
 | 
						||
// 创建托盘图标
 | 
						||
export function createTray(win: BrowserWindow) {
 | 
						||
  const tray = new Tray(TrayIcons.normal);
 | 
						||
 | 
						||
  const TrayUtils = useTray(tray, win);
 | 
						||
 | 
						||
  tray.setToolTip($env.VITE_GLOB_APP_TITLE! + (isDev ? ' (开发环境)' : ''));
 | 
						||
 | 
						||
  // 左键托盘图标显示主窗口
 | 
						||
  tray.on('click', () => TrayUtils.showMainWindow());
 | 
						||
  // 右键托盘图标显示托盘菜单
 | 
						||
  tray.on('right-click', () => showTrayContextMenu());
 | 
						||
 | 
						||
  function showTrayContextMenu() {
 | 
						||
    const trayContextMenu = getTrayMenus(win, TrayUtils);
 | 
						||
    // 弹出托盘菜单,不使用 setContextMenu 方法是因为要实时更新菜单内容
 | 
						||
    tray.popUpContextMenu(trayContextMenu);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
export function useTray(tray: Tray, win: BrowserWindow) {
 | 
						||
  let isBlinking = false;
 | 
						||
  let blinkTimer: NodeJS.Timeout | null = null;
 | 
						||
 | 
						||
  function showMainWindow() {
 | 
						||
    win.show();
 | 
						||
  }
 | 
						||
 | 
						||
  // 开始闪动
 | 
						||
  function startBlink() {
 | 
						||
    isBlinking = true;
 | 
						||
    tray.setImage(TrayIcons.empty);
 | 
						||
    blinkTimer = setTimeout(() => {
 | 
						||
      tray.setImage(TrayIcons.normal);
 | 
						||
      setTimeout(() => {
 | 
						||
        if (isBlinking) {
 | 
						||
          startBlink();
 | 
						||
        }
 | 
						||
      }, 500);
 | 
						||
    }, 500);
 | 
						||
  }
 | 
						||
 | 
						||
  // 结束闪动
 | 
						||
  function stopBlink() {
 | 
						||
    isBlinking = false;
 | 
						||
    if (blinkTimer) {
 | 
						||
      clearTimeout(blinkTimer);
 | 
						||
      blinkTimer = null;
 | 
						||
    }
 | 
						||
    tray.setImage(TrayIcons.normal);
 | 
						||
  }
 | 
						||
  ipcMain.on('tray-flash', (event: IpcMainInvokeEvent) => {
 | 
						||
    // 仅在 Windows 系统中闪烁
 | 
						||
    if (process.platform === 'win32') {
 | 
						||
      startBlink();
 | 
						||
    }
 | 
						||
  });
 | 
						||
  ipcMain.on('tray-flash-stop', (event: IpcMainInvokeEvent) => {
 | 
						||
    // 仅在 Windows 系统中停止闪烁
 | 
						||
    if (process.platform === 'win32') {
 | 
						||
      stopBlink();
 | 
						||
    }
 | 
						||
  });
 | 
						||
  win.on('focus', () => {
 | 
						||
    stopBlink();
 | 
						||
  });
 | 
						||
  // 发送桌面通知
 | 
						||
  function sendDesktopNotice() {
 | 
						||
    // 判断是否支持桌面通知
 | 
						||
    if (!Notification.isSupported()) {
 | 
						||
      // todo 实际开发中不需要提示,直接返回或者换一种提示方式
 | 
						||
      dialog.showMessageBoxSync(win, {
 | 
						||
        type: 'error',
 | 
						||
        title: '错误',
 | 
						||
        message: '当前系统不支持桌面通知',
 | 
						||
      });
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    const ins = new Notification({
 | 
						||
      title: '通知标题',
 | 
						||
      body: '通知内容第一行\n通知内容第二行',
 | 
						||
      // icon: TrayIcons.normal.resize({width: 32, height: 32}),
 | 
						||
    });
 | 
						||
 | 
						||
    ins.on('click', () => {
 | 
						||
      dialog.showMessageBoxSync(win, {
 | 
						||
        type: 'info',
 | 
						||
        title: '提示',
 | 
						||
        message: '通知被点击',
 | 
						||
      });
 | 
						||
    });
 | 
						||
 | 
						||
    ins.show();
 | 
						||
  }
 | 
						||
 | 
						||
  return {
 | 
						||
    showMainWindow,
 | 
						||
 | 
						||
    startBlink,
 | 
						||
    stopBlink,
 | 
						||
    isBlinking: () => isBlinking,
 | 
						||
 | 
						||
    sendDesktopNotice,
 | 
						||
  };
 | 
						||
}
 | 
						||
 | 
						||
const MenuIcon = {
 | 
						||
  exit: nativeImage
 | 
						||
    .createFromDataURL(
 | 
						||
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACJ0lEQVR4nH1TzWvUQBRP7fpxsWqVXsSLiAevRWhhN28msRJo981kay4WRBCF/QdEFJpbaUHw4kFBQTwUKX4gKh48KPiBBcGLJ1F0uzPZ7ibWXf0DIjObielS+mDIm/fxm9/85sWyBixN06E0CIaV3wB2XhC8puOWNZSG4Y7B+k2mi7Kl9l2n9rHnzvbWJoLRYn7r5jTViQjwzM8ynlC+AFyVgN2NU8G+Rnn6QETx3FfP223A/jeHfWqCsAUJ7Hlryh9Te0nYqiDsz9rE6VHVIABvNwEf/ADYk4OsZPeVFbwiCHtcZBVR9k4CJhJmDuUxwEVJ8H4fINOkC9Vjbeq/UTR1IgPturX3f93Z35+B7ddxgJL6dih/skF9zE9KCJ//5bDLpii1+npIuzolKTubC5gBxzarJo6vWWjrUP+etFlF+ds9lRFOXalN+NPEmxvRDS3KH34v8+PFIgNmTh0EahH+InGCwzoQEbYcuTMnlR8aYbaxGHFvRNiznssP6sA65UsxrdU1+hYnFhlpAGAkdvzlPLFu88mY8pcrVjCsxcqGapC2eYW249/tUH4xS4QaVQLeigi/YWJqPl4DlNRSrAwzSaoXIspeWUYrI9qXINglgT1qAt5JPG+kkNN5BSAJuyoJfhAVdmST4PlPBFASNs6rIgnspqC8HlF+SQAuRQTfKpYiEy6fwuIdP42P71T+t0l/TBKcE8AXm4DXBfB6w50+apgUhf4HZ5j+Z5+zNTAAAAAASUVORK5CYII='
 | 
						||
    )
 | 
						||
    .resize({
 | 
						||
      width: 16,
 | 
						||
      height: 16,
 | 
						||
    }),
 | 
						||
};
 | 
						||
 | 
						||
// 设置托盘菜单
 | 
						||
function getTrayMenus(win: BrowserWindow, TrayUtils: ReturnType<typeof useTray>) {
 | 
						||
  const {startBlink, stopBlink, sendDesktopNotice} = TrayUtils;
 | 
						||
  const isBlinking = TrayUtils.isBlinking();
 | 
						||
 | 
						||
  return Menu.buildFromTemplate([
 | 
						||
    ...(isDev
 | 
						||
      ? [
 | 
						||
        {
 | 
						||
          label: '开发工具',
 | 
						||
          submenu: [
 | 
						||
            {
 | 
						||
              label: '以下菜单仅显示在开发环境',
 | 
						||
              sublabel: '当前为开发环境',
 | 
						||
              enabled: false,
 | 
						||
            },
 | 
						||
            {type: 'separator'},
 | 
						||
            {
 | 
						||
              label: '切换 DevTools',
 | 
						||
              click: () => win.webContents.toggleDevTools(),
 | 
						||
            },
 | 
						||
            {
 | 
						||
              label: `托盘图标${isBlinking ? '停止' : '开始'}闪烁`,
 | 
						||
              sublabel: '模拟新消息提醒',
 | 
						||
              click: () => (isBlinking ? stopBlink() : startBlink()),
 | 
						||
            },
 | 
						||
            {
 | 
						||
              label: '发送桌面通知示例',
 | 
						||
              click: () => sendDesktopNotice(),
 | 
						||
            },
 | 
						||
          ],
 | 
						||
        },
 | 
						||
        {type: 'separator'},
 | 
						||
      ]
 | 
						||
      : ([] as any)),
 | 
						||
    {
 | 
						||
      label: '显示主窗口',
 | 
						||
      // 文件图标
 | 
						||
      icon: TrayIcons.normal.resize({width: 16, height: 16}),
 | 
						||
      click: () => win.show(),
 | 
						||
    },
 | 
						||
    {type: 'separator'},
 | 
						||
    {
 | 
						||
      label: '退出',
 | 
						||
      // base64图标
 | 
						||
      icon: MenuIcon.exit,
 | 
						||
      click: () => {
 | 
						||
        // 弹出是否确认退出提示框
 | 
						||
        const choice = dialog.showMessageBoxSync(win, {
 | 
						||
          type: 'question',
 | 
						||
          title: '提示',
 | 
						||
          message: '确定要退出应用吗?',
 | 
						||
          buttons: ['退出', '取消'],
 | 
						||
          defaultId: 1,
 | 
						||
          cancelId: 1,
 | 
						||
          noLink: true,
 | 
						||
        });
 | 
						||
        // 用户选择了退出,直接 exit
 | 
						||
        if (choice === 0) {
 | 
						||
          // global.isQuitting = true;
 | 
						||
          app.exit(0);
 | 
						||
        }
 | 
						||
      },
 | 
						||
    },
 | 
						||
  ]);
 | 
						||
}
 |