110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
import { app, Menu, nativeImage, Tray } from 'electron';
|
||
import log from 'electron-log/main';
|
||
|
||
import { getAutoLaunchEnabled, setAutoLaunchEnabled } from './auto-launch';
|
||
import { promptAndClearCache } from './cache';
|
||
import { getResourcePath } from './config';
|
||
import { checkForUpdates } from './updater';
|
||
import { getMainWindow, markQuitting, showMainWindow } from './window';
|
||
|
||
/**
|
||
* 系统托盘:应用常驻入口
|
||
* - Windows 单击托盘图标恢复主窗口;mac 单击弹出菜单(跟随平台惯例)
|
||
* - 菜单:显示主窗口 / 开机自启 / 重新启动 / 切换账号 / 清除缓存 / 检查更新 / 退出
|
||
* 注意:托盘实例必须保持模块级引用,否则会被 GC 导致图标消失(Electron 经典坑)
|
||
*/
|
||
|
||
let tray: Tray | null = null;
|
||
|
||
/** 重新启动整个 Electron 进程(保留启动参数,常用于卡死/配置变更后自愈) */
|
||
function restartApp(): void {
|
||
markQuitting();
|
||
app.relaunch();
|
||
app.exit(0);
|
||
}
|
||
|
||
/** 唤起主窗口并通知渲染端打开「切换账号」弹窗(未登录时弹窗自行失败即可) */
|
||
function openSwitchAccount(): void {
|
||
showMainWindow();
|
||
getMainWindow()?.webContents.send('desktop:open-switch-account');
|
||
}
|
||
|
||
/** 构建托盘菜单:开机自启是动态勾选项,每次状态变化后需重建菜单 */
|
||
function buildTrayMenu(): Menu {
|
||
return Menu.buildFromTemplate([
|
||
{
|
||
label: '显示主窗口',
|
||
click: () => showMainWindow(),
|
||
},
|
||
{ type: 'separator' },
|
||
{
|
||
label: '开机自启',
|
||
type: 'checkbox',
|
||
checked: getAutoLaunchEnabled(),
|
||
// 开发模式登录项无意义,置灰避免误操作
|
||
enabled: app.isPackaged,
|
||
click: (menuItem) => {
|
||
setAutoLaunchEnabled(menuItem.checked);
|
||
refreshTrayMenu();
|
||
},
|
||
},
|
||
{
|
||
label: '重新启动',
|
||
click: () => restartApp(),
|
||
},
|
||
{
|
||
label: '切换账号',
|
||
click: () => openSwitchAccount(),
|
||
},
|
||
{
|
||
label: '清除缓存',
|
||
click: () => {
|
||
void promptAndClearCache();
|
||
},
|
||
},
|
||
{
|
||
label: '检查更新',
|
||
click: () => checkForUpdates(true),
|
||
},
|
||
{ type: 'separator' },
|
||
{
|
||
label: '退出',
|
||
click: () => {
|
||
// 先标记退出,放行主窗口 close 拦截,再走正常退出流程
|
||
markQuitting();
|
||
app.quit();
|
||
},
|
||
},
|
||
]);
|
||
}
|
||
|
||
/** 刷新托盘菜单(开机自启状态从渲染端 IPC 修改后也要同步勾选态) */
|
||
export function refreshTrayMenu(): void {
|
||
tray?.setContextMenu(buildTrayMenu());
|
||
}
|
||
|
||
/**
|
||
* 创建系统托盘
|
||
* 图标用打包的应用图标缩放(mac 菜单栏建议 18px、Windows 托盘 16px 的倍数由系统处理)
|
||
* TODO(待确认):正式图标到位后,mac 建议换单色 Template 图以适配深浅菜单栏
|
||
*/
|
||
export function createTray(): void {
|
||
try {
|
||
const icon = nativeImage.createFromPath(getResourcePath('icon.png'));
|
||
const trayIcon =
|
||
process.platform === 'darwin'
|
||
? icon.resize({ width: 18, height: 18 })
|
||
: icon.resize({ width: 16, height: 16 });
|
||
tray = new Tray(trayIcon);
|
||
tray.setToolTip('萧康云医');
|
||
tray.setContextMenu(buildTrayMenu());
|
||
// Windows 单击直接唤起主窗口;mac 上设置了 contextMenu 后单击默认弹菜单,不重复绑定
|
||
if (process.platform === 'win32') {
|
||
tray.on('click', () => showMainWindow());
|
||
}
|
||
} catch (error) {
|
||
// 托盘创建失败(图标缺失等)不阻塞应用启动,仅损失常驻入口
|
||
log.error('创建系统托盘失败', error);
|
||
}
|
||
}
|