mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
feat(窗口管理): 添加系统托盘功能并优化窗口关闭行为
- 新增 TrayService 实现系统托盘功能 - 主窗口关闭时默认隐藏而非退出 - 添加 window_zoom 配置项支持窗口缩放 - 引入 TrayServiceToken 作为依赖注入标识 - 通过 isQuitting 状态控制应用退出行为
This commit is contained in:
@@ -2,6 +2,8 @@ import { Context as BaseContext } from '../shared/kernel'
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
export class MainContext extends BaseContext {
|
||||
public isQuitting = false
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ export {
|
||||
EventRepositoryToken,
|
||||
SettlementRepositoryToken,
|
||||
ThemeServiceToken,
|
||||
WindowManagerToken
|
||||
WindowManagerToken,
|
||||
TrayServiceToken
|
||||
} from './tokens'
|
||||
export type {
|
||||
appRuntimeContext,
|
||||
|
||||
@@ -12,3 +12,4 @@ export const EventRepositoryToken = Symbol.for('secscore.eventRepository')
|
||||
export const SettlementRepositoryToken = Symbol.for('secscore.settlementRepository')
|
||||
export const ThemeServiceToken = Symbol.for('secscore.themeService')
|
||||
export const WindowManagerToken = Symbol.for('secscore.windowManager')
|
||||
export const TrayServiceToken = Symbol.for('secscore.trayService')
|
||||
|
||||
+11
-1
@@ -14,6 +14,7 @@ import { AuthService } from './services/AuthService'
|
||||
import { DataService } from './services/DataService'
|
||||
import { ThemeService } from './services/ThemeService'
|
||||
import { WindowManager, type windowManagerOptions } from './services/WindowManager'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { StudentRepository } from './repos/StudentRepository'
|
||||
import { ReasonRepository } from './repos/ReasonRepository'
|
||||
import { EventRepository } from './repos/EventRepository'
|
||||
@@ -31,7 +32,8 @@ import {
|
||||
SettingsStoreToken,
|
||||
StudentRepositoryToken,
|
||||
ThemeServiceToken,
|
||||
WindowManagerToken
|
||||
WindowManagerToken,
|
||||
TrayServiceToken
|
||||
} from './hosting'
|
||||
|
||||
type mainAppConfig = {
|
||||
@@ -137,6 +139,10 @@ app.whenReady().then(async () => {
|
||||
WindowManagerToken,
|
||||
(p) => new WindowManager(p.get(MainContext), config.window)
|
||||
)
|
||||
services.addSingleton(
|
||||
TrayServiceToken,
|
||||
(p) => new TrayService(p.get(MainContext), config.window)
|
||||
)
|
||||
})
|
||||
.configure(async (_builderContext, appCtx) => {
|
||||
const services = appCtx.services
|
||||
@@ -156,6 +162,8 @@ app.whenReady().then(async () => {
|
||||
services.get(SettlementRepositoryToken)
|
||||
services.get(ThemeServiceToken)
|
||||
services.get(WindowManagerToken)
|
||||
const tray = services.get(TrayServiceToken) as TrayService
|
||||
tray.initialize()
|
||||
|
||||
const logLevelSetting = ctx.settings.getValue('log_level') as logLevel
|
||||
if (logLevelSetting) {
|
||||
@@ -254,12 +262,14 @@ app.whenReady().then(async () => {
|
||||
})
|
||||
|
||||
const host = await builder.build()
|
||||
const ctx = host.services.get(MainContext) as MainContext
|
||||
await host.start()
|
||||
|
||||
let disposing = false
|
||||
const beforeQuitHandler = () => {
|
||||
if (disposing) return
|
||||
disposing = true
|
||||
ctx.isQuitting = true
|
||||
app.removeListener('before-quit', beforeQuitHandler)
|
||||
void host.dispose()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Service } from '../../shared/kernel'
|
||||
import { MainContext } from '../context'
|
||||
import { Tray, Menu, app, nativeImage } from 'electron'
|
||||
import type { windowManagerOptions } from './WindowManager'
|
||||
|
||||
export class TrayService extends Service {
|
||||
private tray: Tray | null = null
|
||||
|
||||
constructor(
|
||||
ctx: MainContext,
|
||||
private readonly opts: windowManagerOptions
|
||||
) {
|
||||
super(ctx, 'tray')
|
||||
}
|
||||
|
||||
private get mainCtx() {
|
||||
return this.ctx as MainContext
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
const icon = nativeImage.createFromPath(this.opts.icon)
|
||||
this.tray = new Tray(icon)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '显示主窗口',
|
||||
click: () => {
|
||||
this.showMainWindow()
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出 SecScore',
|
||||
click: () => {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
this.tray.setToolTip('SecScore 积分管理')
|
||||
this.tray.setContextMenu(contextMenu)
|
||||
|
||||
this.tray.on('double-click', () => {
|
||||
this.showMainWindow()
|
||||
})
|
||||
}
|
||||
|
||||
private showMainWindow() {
|
||||
const mainWin = this.mainCtx.windows.get('main')
|
||||
if (mainWin) {
|
||||
if (mainWin.isMinimized()) mainWin.restore()
|
||||
mainWin.show()
|
||||
mainWin.focus()
|
||||
} else {
|
||||
this.mainCtx.windows.open({ key: 'main', title: 'SecScore', route: '/' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,14 @@ export class WindowManager extends Service {
|
||||
win.webContents.setZoomFactor(zoom)
|
||||
|
||||
this.windows.set(input.key, win)
|
||||
|
||||
win.on('close', (event) => {
|
||||
if (!this.mainCtx.isQuitting && input.key === 'main') {
|
||||
event.preventDefault()
|
||||
win.hide()
|
||||
}
|
||||
})
|
||||
|
||||
win.on('closed', () => {
|
||||
this.windows.delete(input.key)
|
||||
})
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface themeConfig {
|
||||
export type settingsSpec = {
|
||||
is_wizard_completed: boolean
|
||||
log_level: logLevel
|
||||
window_zoom: number
|
||||
}
|
||||
|
||||
export type settingsKey = keyof settingsSpec
|
||||
|
||||
Reference in New Issue
Block a user