tray.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // tray = 系统托盘
  2. import path from 'path';
  3. import {Tray, Menu, app, dialog, nativeImage, BrowserWindow, Notification} from 'electron';
  4. import {_PATHS} from '../paths';
  5. import {$env, isDev} from '../env';
  6. const TrayIcons = {
  7. normal: nativeImage.createFromPath(path.join(_PATHS.publicRoot, 'logo.png')),
  8. empty: nativeImage.createEmpty(),
  9. };
  10. // 创建托盘图标
  11. export function createTray(win: BrowserWindow) {
  12. const tray = new Tray(TrayIcons.normal);
  13. const TrayUtils = useTray(tray, win);
  14. tray.setToolTip($env.VITE_GLOB_APP_TITLE! + (isDev ? ' (开发环境)' : ''));
  15. // 左键托盘图标显示主窗口
  16. tray.on('click', () => TrayUtils.showMainWindow());
  17. // 右键托盘图标显示托盘菜单
  18. tray.on('right-click', () => showTrayContextMenu());
  19. function showTrayContextMenu() {
  20. const trayContextMenu = getTrayMenus(win, TrayUtils);
  21. // 弹出托盘菜单,不使用 setContextMenu 方法是因为要实时更新菜单内容
  22. tray.popUpContextMenu(trayContextMenu);
  23. }
  24. }
  25. export function useTray(tray: Tray, win: BrowserWindow) {
  26. let isBlinking = false;
  27. let blinkTimer: NodeJS.Timeout | null = null;
  28. function showMainWindow() {
  29. win.show();
  30. }
  31. // 开始闪动
  32. function startBlink() {
  33. isBlinking = true;
  34. tray.setImage(TrayIcons.empty);
  35. blinkTimer = setTimeout(() => {
  36. tray.setImage(TrayIcons.normal);
  37. setTimeout(() => {
  38. if (isBlinking) {
  39. startBlink();
  40. }
  41. }, 500);
  42. }, 500);
  43. }
  44. // 结束闪动
  45. function stopBlink() {
  46. isBlinking = false;
  47. if (blinkTimer) {
  48. clearTimeout(blinkTimer);
  49. blinkTimer = null;
  50. }
  51. tray.setImage(TrayIcons.normal);
  52. }
  53. // 发送桌面通知
  54. function sendDesktopNotice() {
  55. // 判断是否支持桌面通知
  56. if (!Notification.isSupported()) {
  57. // todo 实际开发中不需要提示,直接返回或者换一种提示方式
  58. dialog.showMessageBoxSync(win, {
  59. type: 'error',
  60. title: '错误',
  61. message: '当前系统不支持桌面通知',
  62. });
  63. return;
  64. }
  65. const ins = new Notification({
  66. title: '通知标题',
  67. subtitle: '通知副标题',
  68. body: '通知内容第一行\n通知内容第二行',
  69. icon: TrayIcons.normal.resize({width: 32, height: 32}),
  70. });
  71. ins.on('click', () => {
  72. dialog.showMessageBoxSync(win, {
  73. type: 'info',
  74. title: '提示',
  75. message: '通知被点击',
  76. });
  77. });
  78. ins.show();
  79. }
  80. return {
  81. showMainWindow,
  82. startBlink,
  83. stopBlink,
  84. isBlinking: () => isBlinking,
  85. sendDesktopNotice,
  86. };
  87. }
  88. const MenuIcon = {
  89. exit: nativeImage
  90. .createFromDataURL(
  91. '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='
  92. )
  93. .resize({
  94. width: 16,
  95. height: 16,
  96. }),
  97. };
  98. // 设置托盘菜单
  99. function getTrayMenus(win: BrowserWindow, TrayUtils: ReturnType<typeof useTray>) {
  100. const {startBlink, stopBlink, sendDesktopNotice} = TrayUtils;
  101. const isBlinking = TrayUtils.isBlinking();
  102. return Menu.buildFromTemplate([
  103. ...(isDev
  104. ? [
  105. {
  106. label: '开发工具',
  107. submenu: [
  108. {
  109. label: '以下菜单仅显示在开发环境',
  110. sublabel: '当前为开发环境',
  111. enabled: false,
  112. },
  113. {type: 'separator'},
  114. {
  115. label: '切换 DevTools',
  116. click: () => win.webContents.toggleDevTools(),
  117. },
  118. {
  119. label: `托盘图标${isBlinking ? '停止' : '开始'}闪烁`,
  120. sublabel: '模拟新消息提醒',
  121. click: () => (isBlinking ? stopBlink() : startBlink()),
  122. },
  123. {
  124. label: '发送桌面通知示例',
  125. click: () => sendDesktopNotice(),
  126. },
  127. ],
  128. },
  129. {type: 'separator'},
  130. ]
  131. : ([] as any)),
  132. {
  133. label: '显示主窗口',
  134. // 文件图标
  135. icon: TrayIcons.normal.resize({width: 16, height: 16}),
  136. click: () => win.show(),
  137. },
  138. {type: 'separator'},
  139. {
  140. label: '退出',
  141. // base64图标
  142. icon: MenuIcon.exit,
  143. click: () => {
  144. // 弹出是否确认退出提示框
  145. const choice = dialog.showMessageBoxSync(win, {
  146. type: 'question',
  147. title: '提示',
  148. message: '确定要退出应用吗?',
  149. buttons: ['退出', '取消'],
  150. defaultId: 1,
  151. cancelId: 1,
  152. noLink: true,
  153. });
  154. // 用户选择了退出,直接 exit
  155. if (choice === 0) {
  156. // global.isQuitting = true;
  157. app.exit(0);
  158. }
  159. },
  160. },
  161. ]);
  162. }