refactor: 重构主窗口创建逻辑,优化显示器适配和最小尺寸计算

This commit is contained in:
2025-09-15 22:51:16 +08:00
parent e79830565e
commit ac9418f787

View File

@@ -1,4 +1,14 @@
import { app, BrowserWindow, ipcMain, dialog, shell, Tray, Menu, nativeImage } from 'electron'
import {
app,
BrowserWindow,
ipcMain,
dialog,
shell,
Tray,
Menu,
nativeImage,
screen,
} from 'electron'
import * as path from 'path'
import * as fs from 'fs'
import { spawn } from 'child_process'
@@ -58,7 +68,6 @@ function restartAsAdmin(): void {
}
}
let mainWindow: BrowserWindow | null = null
let tray: Tray | null = null
let isQuitting = false
let saveWindowStateTimeout: NodeJS.Timeout | null = null
@@ -86,12 +95,12 @@ const defaultConfig: AppConfig = {
IfToTray: false,
location: '100,100',
maximized: false,
size: '1600,1000'
size: '1600,1000',
},
Start: {
IfMinimizeDirectly: false,
IfSelfStart: false
}
IfSelfStart: false,
},
}
// 加载配置
@@ -137,7 +146,7 @@ function createTray() {
path.join(__dirname, '../public/AUTO-MAS.ico'),
path.join(process.resourcesPath, 'assets/AUTO-MAS.ico'),
path.join(app.getAppPath(), 'public/AUTO-MAS.ico'),
path.join(app.getAppPath(), 'dist/AUTO-MAS.ico')
path.join(app.getAppPath(), 'dist/AUTO-MAS.ico'),
]
let trayIcon
@@ -178,7 +187,7 @@ function createTray() {
mainWindow.show()
mainWindow.focus()
}
}
},
},
{
label: '隐藏窗口',
@@ -190,7 +199,7 @@ function createTray() {
}
mainWindow.hide()
}
}
},
},
{ type: 'separator' },
{
@@ -198,8 +207,8 @@ function createTray() {
click: () => {
isQuitting = true
app.quit()
}
}
},
},
])
tray.setContextMenu(contextMenu)
@@ -267,124 +276,165 @@ function updateTrayVisibility(config: AppConfig) {
log.info('托盘图标已销毁')
}
}
let mainWindow: Electron.BrowserWindow | null = null
function createWindow() {
log.info('开始创建主窗口')
const config = loadConfig()
// 解析窗口大小
const [width, height] = config.UI.size.split(',').map(s => parseInt(s.trim()) || 1600)
const [x, y] = config.UI.location.split(',').map(s => parseInt(s.trim()) || 100)
// 解析配置
const [cfgW, cfgH] = config.UI.size.split(',').map((s: string) => parseInt(s.trim(), 10) || 1600)
const [cfgX, cfgY] = config.UI.location
.split(',')
.map((s: string) => parseInt(s.trim(), 10) || 100)
mainWindow = new BrowserWindow({
width: Math.max(width, 1600),
height: Math.max(height, 900),
x,
y,
minWidth: 1600,
minHeight: 900,
// 以目标位置选最近显示器
const targetDisplay = screen.getDisplayNearestPoint({ x: cfgX, y: cfgY })
const sf = targetDisplay.scaleFactor
// 逻辑最小尺寸DIP
const minDipW = Math.floor(1600 / sf)
const minDipH = Math.floor(900 / sf)
// 初始窗口逻辑尺寸DIP
let initW = Math.max(cfgW, minDipW)
let initH = Math.max(cfgH, minDipH)
// 不超过工作区
const { width: waW, height: waH } = targetDisplay.workAreaSize
initW = Math.min(initW, waW)
initH = Math.min(initH, waH)
// 关键:用局部常量 win全程用它类型不为 null
const win = new BrowserWindow({
x: cfgX,
y: cfgY,
width: initW,
height: initH,
minWidth: minDipW,
minHeight: minDipH,
useContentSize: true,
frame: false,
titleBarStyle: 'hidden',
icon: path.join(__dirname, '../public/AUTO-MAS.ico'),
frame: false, // 去掉系统标题栏
titleBarStyle: 'hidden', // 隐藏标题栏
autoHideMenuBar: true,
show: !config.Start.IfMinimizeDirectly,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
autoHideMenuBar: true,
show: !config.Start.IfMinimizeDirectly, // 根据配置决定是否直接显示
})
// 如果配置为最大化,则最大化窗口
if (config.UI.maximized) {
mainWindow.maximize()
// 把局部的 win 赋值给模块级(供其他模块/函数用)
mainWindow = win
// 根据显示器动态更新最小尺寸/边界
const recomputeMinSize = () => {
// 这里用 win不会是 null
const bounds = win.getBounds()
const disp = screen.getDisplayMatching(bounds)
const s = disp.scaleFactor
const w = Math.floor(1600 / s)
const h = Math.floor(900 / s)
const [curMinW, curMinH] = win.getMinimumSize()
if (w !== curMinW || h !== curMinH) {
win.setMinimumSize(w, h)
if (win.isMaximized()) return
const { width: wW, height: wH } = disp.workAreaSize
const newBounds = { ...bounds }
if (newBounds.width > wW) newBounds.width = wW
if (newBounds.height > wH) newBounds.height = wH
if (newBounds.width < w) newBounds.width = w
if (newBounds.height < h) newBounds.height = h
win.setBounds(newBounds)
}
}
mainWindow.setMenuBarVisibility(false)
// 监听显示器变化/窗口移动
win.on('moved', recomputeMinSize)
win.on('resized', recomputeMinSize)
screen.on('display-metrics-changed', recomputeMinSize)
// 最大化配置
if (config.UI.maximized) {
win.maximize()
}
win.setMenuBarVisibility(false)
const devServer = process.env.VITE_DEV_SERVER_URL
if (devServer) {
log.info(`加载开发服务器: ${devServer}`)
mainWindow.loadURL(devServer)
win.loadURL(devServer)
} else {
const indexHtmlPath = path.join(app.getAppPath(), 'dist', 'index.html')
log.info(`加载生产环境页面: ${indexHtmlPath}`)
mainWindow.loadFile(indexHtmlPath)
win.loadFile(indexHtmlPath)
}
// 窗口事件处理
mainWindow.on('close', (event) => {
win.on('close', (event: Electron.Event) => {
const currentConfig = loadConfig()
if (!isQuitting && currentConfig.UI.IfToTray) {
// 如果启用了最小化到托盘,阻止关闭并隐藏窗口
event.preventDefault()
mainWindow?.hide()
mainWindow?.setSkipTaskbar(true)
// 更新托盘状态
win.hide()
win.setSkipTaskbar(true)
updateTrayVisibility(currentConfig)
log.info('窗口已最小化到托盘,任务栏图标已隐藏')
} else {
// 保存窗口状态
saveWindowState()
}
})
mainWindow.on('closed', () => {
win.on('closed', () => {
log.info('主窗口已关闭')
// 清理监听(可选)
screen.removeListener('display-metrics-changed', recomputeMinSize)
// 置空模块级引用
mainWindow = null
})
// 窗口最小化事件
mainWindow.on('minimize', () => {
win.on('minimize', () => {
const currentConfig = loadConfig()
if (currentConfig.UI.IfToTray) {
// 如果启用了最小化到托盘,隐藏窗口并从任务栏移除
mainWindow?.hide()
mainWindow?.setSkipTaskbar(true)
// 更新托盘状态
win.hide()
win.setSkipTaskbar(true)
updateTrayVisibility(currentConfig)
log.info('窗口已最小化到托盘,任务栏图标已隐藏')
}
})
// 窗口显示/隐藏事件,用于更新托盘状态
mainWindow.on('show', () => {
win.on('show', () => {
const currentConfig = loadConfig()
// 窗口显示时,恢复任务栏图标
mainWindow?.setSkipTaskbar(false)
win.setSkipTaskbar(false)
updateTrayVisibility(currentConfig)
log.info('窗口已显示,任务栏图标已恢复')
})
mainWindow.on('hide', () => {
win.on('hide', () => {
const currentConfig = loadConfig()
// 窗口隐藏时,根据配置决定是否隐藏任务栏图标
if (currentConfig.UI.IfToTray) {
mainWindow?.setSkipTaskbar(true)
win.setSkipTaskbar(true)
log.info('窗口已隐藏,任务栏图标已隐藏')
}
updateTrayVisibility(currentConfig)
})
// 窗口移动调整大小时保存状态
mainWindow.on('moved', saveWindowState)
mainWindow.on('resized', saveWindowState)
mainWindow.on('maximize', saveWindowState)
mainWindow.on('unmaximize', saveWindowState)
// 移动/调整大小/最大化状态变化时保存
win.on('moved', saveWindowState)
win.on('resized', saveWindowState)
win.on('maximize', saveWindowState)
win.on('unmaximize', saveWindowState)
// 设置各个服务的主窗口引用
if (mainWindow) {
setDownloadMainWindow(mainWindow)
setPythonMainWindow(mainWindow)
setGitMainWindow(mainWindow)
log.info('主窗口创建完成,服务引用已设置')
}
// 设置各个服务的主窗口引用(此处 win 一定存在,可直接传)
setDownloadMainWindow(win)
setPythonMainWindow(win)
setGitMainWindow(win)
log.info('主窗口创建完成,服务引用已设置')
// 根据配置初始化托盘
updateTrayVisibility(config)
@@ -541,7 +591,7 @@ ipcMain.handle('check-critical-files', async () => {
pythonExists,
pipExists,
gitExists,
mainPyExists
mainPyExists,
}
log.info('关键文件检查结果:', result)
@@ -552,7 +602,7 @@ ipcMain.handle('check-critical-files', async () => {
pythonExists: false,
pipExists: false,
gitExists: false,
mainPyExists: false
mainPyExists: false,
}
}
})
@@ -627,15 +677,15 @@ ipcMain.handle('check-git-update', async () => {
cwd: appRoot,
})
fetchProc.stdout?.on('data', (data) => {
fetchProc.stdout?.on('data', data => {
log.info('git fetch output:', data.toString())
})
fetchProc.stderr?.on('data', (data) => {
fetchProc.stderr?.on('data', data => {
log.info('git fetch stderr:', data.toString())
})
fetchProc.on('close', (code) => {
fetchProc.on('close', code => {
if (code === 0) {
resolve()
} else {
@@ -655,30 +705,34 @@ ipcMain.handle('check-git-update', async () => {
})
let output = ''
statusProc.stdout?.on('data', (data) => {
statusProc.stdout?.on('data', data => {
output += data.toString()
})
statusProc.stderr?.on('data', (data) => {
statusProc.stderr?.on('data', data => {
log.info('git status stderr:', data.toString())
})
statusProc.on('close', (code) => {
statusProc.on('close', code => {
if (code === 0) {
// 检查是否有 "Your branch is behind" 的信息
// 使用 git rev-list 来比较本地和远程分支
const revListProc = spawn(gitPath, ['rev-list', '--count', 'HEAD..origin/feature/refactor'], {
stdio: 'pipe',
env: gitEnv,
cwd: appRoot,
})
const revListProc = spawn(
gitPath,
['rev-list', '--count', 'HEAD..origin/feature/refactor'],
{
stdio: 'pipe',
env: gitEnv,
cwd: appRoot,
}
)
let revOutput = ''
revListProc.stdout?.on('data', (data) => {
revListProc.stdout?.on('data', data => {
revOutput += data.toString()
})
revListProc.on('close', (revCode) => {
revListProc.on('close', revCode => {
if (revCode === 0) {
const commitsBehind = parseInt(revOutput.trim())
const hasUpdates = commitsBehind > 0
@@ -704,7 +758,6 @@ ipcMain.handle('check-git-update', async () => {
log.info(`Git更新检查完成hasUpdate: ${hasUpdate}`)
return { hasUpdate }
} catch (error) {
log.error('检查Git更新失败:', error)
// 如果检查失败返回true以触发更新流程确保代码是最新的
@@ -815,7 +868,7 @@ ipcMain.handle('get-log-path', async () => {
}
})
ipcMain.handle('get-log-files', async (_event) => {
ipcMain.handle('get-log-files', async _event => {
try {
return getLogFiles()
} catch (error) {
@@ -1008,4 +1061,3 @@ app.on('window-all-closed', () => {
app.on('activate', () => {
if (mainWindow === null) createWindow()
})