Newer
Older
dxCard-admin / electron / utils / tray.ts
YFJ on 23 Sep 6 KB 项目推送
// 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);
        }
      },
    },
  ]);
}