refactor: 重构主窗口创建逻辑,优化显示器适配和最小尺寸计算
This commit is contained in:
@@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user