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