tray.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { Tray, Menu, dialog, BrowserWindow } from 'electron';
  2. import { join } from 'path';
  3. import { getBaseDir } from 'ee-core/ps';
  4. import { logger } from 'ee-core/log';
  5. import { app as electronApp } from 'electron';
  6. import { getMainWindow, getCloseAndQuit, setCloseAndQuit } from 'ee-core/electron';
  7. import { isSmartWindowOpen } from '../window/smartWindow';
  8. /**
  9. * 托盘
  10. * @class
  11. */
  12. class TrayService {
  13. constructor() {
  14. this.tray = null;
  15. this.quitConfirming = false;
  16. this.config = {
  17. title: 'electron-egg',
  18. icon: '/public/images/tray.png'
  19. }
  20. }
  21. /**
  22. * 创建托盘
  23. */
  24. create () {
  25. logger.info('[tray] load');
  26. const cfg = this.config;
  27. const mainWindow = getMainWindow();
  28. // tray icon
  29. const iconPath = join(getBaseDir(), cfg.icon);
  30. /**
  31. * 托盘退出前确认:退出会中止智能工作台正在进行的对话/任务。
  32. *
  33. * <p>托盘点击发生在主进程,此时主窗口可能已经收进托盘(不可见),
  34. * 没法可靠地在渲染层弹 Ant Design 确认框,所以用系统原生对话框。
  35. */
  36. const quitWithConfirm = async () => {
  37. if (this.quitConfirming) return;
  38. this.quitConfirming = true;
  39. try {
  40. const options = {
  41. type: 'warning',
  42. title: '退出程序',
  43. message: '确认退出清鉴?',
  44. detail: isSmartWindowOpen()
  45. ? '退出程序会中止智能工作台中正在进行的对话或任务,已生成的内容会保留。'
  46. : '退出程序会关闭所有窗口。',
  47. buttons: ['取消', '退出'],
  48. defaultId: 0,
  49. cancelId: 0,
  50. noLink: true,
  51. };
  52. // 有聚焦窗口就挂成它的模态;托盘菜单打开时通常没有聚焦窗口,用独立对话框,
  53. // 避免始终挂到可能被智能窗口挡住的 mainWindow 后面
  54. const focused = BrowserWindow.getFocusedWindow();
  55. const parent = focused && !focused.isDestroyed() ? focused : undefined;
  56. const result = parent
  57. ? await dialog.showMessageBox(parent, options)
  58. : await dialog.showMessageBox(options);
  59. if (result.response === 1) {
  60. electronApp.quit();
  61. }
  62. } catch (e) {
  63. logger.error('[tray] 退出确认失败:', e instanceof Error ? e.message : String(e));
  64. } finally {
  65. this.quitConfirming = false;
  66. }
  67. };
  68. // 托盘菜单功能列表
  69. // 智能工作台不放入口:顶栏「智能」开关是唯一入口(案件/账号生命周期也只在主窗口)
  70. const trayMenuTemplate = [
  71. {
  72. label: '显示主窗口',
  73. click: function () {
  74. if (mainWindow.isMinimized()) {
  75. mainWindow.restore();
  76. }
  77. mainWindow.show();
  78. mainWindow.focus();
  79. }
  80. },
  81. {
  82. label: '退出程序',
  83. click: () => {
  84. void quitWithConfirm();
  85. }
  86. }
  87. ]
  88. // 设置一个标识,点击关闭,最小化到托盘
  89. setCloseAndQuit(false);
  90. mainWindow.on('close', (event) => {
  91. if (getCloseAndQuit()) {
  92. return;
  93. }
  94. mainWindow.hide();
  95. event.preventDefault();
  96. });
  97. // 实例化托盘
  98. this.tray = new Tray(iconPath);
  99. this.tray.setToolTip(cfg.title);
  100. const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
  101. this.tray.setContextMenu(contextMenu);
  102. // 左键单击的时候能够显示主窗口
  103. this.tray.on('click', () => {
  104. mainWindow.show()
  105. })
  106. }
  107. }
  108. TrayService.toString = () => '[class TrayService]';
  109. export const trayService = new TrayService();