main.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // 使用 CommonJS 格式
  2. const { app, BrowserWindow, ipcMain, shell, session, Menu, Tray, nativeImage } = require('electron');
  3. const { join } = require('path');
  4. const fs = require('fs');
  5. let mainWindow: typeof BrowserWindow.prototype | null = null;
  6. let tray: typeof Tray.prototype | null = null;
  7. let isQuitting = false;
  8. const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;
  9. // 获取图标路径
  10. function getIconPath() {
  11. return VITE_DEV_SERVER_URL
  12. ? join(__dirname, '../public/favicon.svg')
  13. : join(__dirname, '../dist/favicon.svg');
  14. }
  15. // 创建托盘图标
  16. function createTrayIcon(): typeof nativeImage.prototype {
  17. // 创建一个简单的蓝色图标作为托盘图标
  18. // 由于 SVG 在某些系统上可能不支持,这里使用 data URL 创建图标
  19. const iconSize = 16;
  20. const canvas = `
  21. <svg xmlns="http://www.w3.org/2000/svg" width="${iconSize}" height="${iconSize}" viewBox="0 0 512 512">
  22. <rect x="32" y="32" width="448" height="448" rx="96" fill="#4f8cff"/>
  23. <circle cx="256" cy="256" r="100" fill="#fff"/>
  24. <path d="M 228 190 L 228 322 L 330 256 Z" fill="#4f8cff"/>
  25. </svg>
  26. `;
  27. const dataUrl = `data:image/svg+xml;base64,${Buffer.from(canvas).toString('base64')}`;
  28. return nativeImage.createFromDataURL(dataUrl);
  29. }
  30. // 创建系统托盘
  31. function createTray() {
  32. const trayIcon = createTrayIcon();
  33. tray = new Tray(trayIcon);
  34. const contextMenu = Menu.buildFromTemplate([
  35. {
  36. label: '显示主窗口',
  37. click: () => {
  38. if (mainWindow) {
  39. mainWindow.show();
  40. mainWindow.focus();
  41. }
  42. }
  43. },
  44. {
  45. label: '最小化到托盘',
  46. click: () => {
  47. mainWindow?.hide();
  48. }
  49. },
  50. { type: 'separator' },
  51. {
  52. label: '退出',
  53. click: () => {
  54. isQuitting = true;
  55. app.quit();
  56. }
  57. }
  58. ]);
  59. tray.setToolTip('多平台媒体管理系统');
  60. tray.setContextMenu(contextMenu);
  61. // 点击托盘图标显示窗口
  62. tray.on('click', () => {
  63. if (mainWindow) {
  64. if (mainWindow.isVisible()) {
  65. mainWindow.focus();
  66. } else {
  67. mainWindow.show();
  68. mainWindow.focus();
  69. }
  70. }
  71. });
  72. // 双击托盘图标显示窗口
  73. tray.on('double-click', () => {
  74. if (mainWindow) {
  75. mainWindow.show();
  76. mainWindow.focus();
  77. }
  78. });
  79. }
  80. function createWindow() {
  81. // 隐藏默认菜单栏
  82. Menu.setApplicationMenu(null);
  83. const iconPath = getIconPath();
  84. mainWindow = new BrowserWindow({
  85. width: 1400,
  86. height: 900,
  87. minWidth: 1200,
  88. minHeight: 700,
  89. icon: iconPath,
  90. webPreferences: {
  91. preload: join(__dirname, 'preload.js'),
  92. nodeIntegration: false,
  93. contextIsolation: true,
  94. webviewTag: true, // 启用 webview 标签
  95. },
  96. frame: false, // 无边框窗口,自定义标题栏
  97. transparent: false,
  98. backgroundColor: '#f0f2f5',
  99. show: false,
  100. });
  101. // 窗口准备好后再显示,避免白屏
  102. mainWindow.once('ready-to-show', () => {
  103. mainWindow?.show();
  104. setupWindowEvents();
  105. });
  106. // 加载页面
  107. if (VITE_DEV_SERVER_URL) {
  108. mainWindow.loadURL(VITE_DEV_SERVER_URL);
  109. mainWindow.webContents.openDevTools();
  110. } else {
  111. mainWindow.loadFile(join(__dirname, '../dist/index.html'));
  112. }
  113. // 处理外部链接
  114. mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => {
  115. shell.openExternal(url);
  116. return { action: 'deny' };
  117. });
  118. // 关闭按钮默认最小化到托盘
  119. mainWindow.on('close', (event: Event) => {
  120. if (!isQuitting) {
  121. event.preventDefault();
  122. mainWindow?.hide();
  123. // 显示托盘通知(仅首次)
  124. if (tray && !app.isPackaged) {
  125. // 开发模式下可以显示通知
  126. }
  127. }
  128. });
  129. mainWindow.on('closed', () => {
  130. mainWindow = null;
  131. });
  132. }
  133. // 单实例锁定
  134. const gotTheLock = app.requestSingleInstanceLock();
  135. if (!gotTheLock) {
  136. app.quit();
  137. } else {
  138. app.on('second-instance', () => {
  139. if (mainWindow) {
  140. mainWindow.show();
  141. if (mainWindow.isMinimized()) mainWindow.restore();
  142. mainWindow.focus();
  143. }
  144. });
  145. app.whenReady().then(() => {
  146. createTray();
  147. createWindow();
  148. app.on('activate', () => {
  149. if (BrowserWindow.getAllWindows().length === 0) {
  150. createWindow();
  151. } else if (mainWindow) {
  152. mainWindow.show();
  153. }
  154. });
  155. });
  156. }
  157. // 阻止默认的 window-all-closed 行为,保持托盘运行
  158. app.on('window-all-closed', () => {
  159. // 不退出应用,保持托盘运行
  160. // 只有在 isQuitting 为 true 时才真正退出
  161. });
  162. // 应用退出前清理托盘
  163. app.on('before-quit', () => {
  164. isQuitting = true;
  165. });
  166. app.on('quit', () => {
  167. if (tray) {
  168. tray.destroy();
  169. tray = null;
  170. }
  171. });
  172. // IPC 处理
  173. ipcMain.handle('get-app-version', () => {
  174. return app.getVersion();
  175. });
  176. ipcMain.handle('get-platform', () => {
  177. return process.platform;
  178. });
  179. // 窗口控制
  180. ipcMain.on('window-minimize', () => {
  181. mainWindow?.minimize();
  182. });
  183. ipcMain.on('window-maximize', () => {
  184. if (mainWindow?.isMaximized()) {
  185. mainWindow.unmaximize();
  186. } else {
  187. mainWindow?.maximize();
  188. }
  189. });
  190. // 关闭窗口(最小化到托盘)
  191. ipcMain.on('window-close', () => {
  192. mainWindow?.hide();
  193. });
  194. // 真正退出应用
  195. ipcMain.on('app-quit', () => {
  196. isQuitting = true;
  197. app.quit();
  198. });
  199. // 获取窗口最大化状态
  200. ipcMain.handle('window-is-maximized', () => {
  201. return mainWindow?.isMaximized() || false;
  202. });
  203. // 监听窗口最大化/还原事件,通知渲染进程
  204. function setupWindowEvents() {
  205. mainWindow?.on('maximize', () => {
  206. mainWindow?.webContents.send('window-maximized', true);
  207. });
  208. mainWindow?.on('unmaximize', () => {
  209. mainWindow?.webContents.send('window-maximized', false);
  210. });
  211. }
  212. // 获取 webview 的 cookies
  213. ipcMain.handle('get-webview-cookies', async (_event: unknown, partition: string, url: string) => {
  214. try {
  215. const ses = session.fromPartition(partition);
  216. const cookies = await ses.cookies.get({ url });
  217. return cookies;
  218. } catch (error) {
  219. console.error('获取 cookies 失败:', error);
  220. return [];
  221. }
  222. });
  223. // 清除 webview 的 cookies
  224. ipcMain.handle('clear-webview-cookies', async (_event: unknown, partition: string) => {
  225. try {
  226. const ses = session.fromPartition(partition);
  227. await ses.clearStorageData({ storages: ['cookies'] });
  228. return true;
  229. } catch (error) {
  230. console.error('清除 cookies 失败:', error);
  231. return false;
  232. }
  233. });