842 lines
22 KiB
TypeScript
842 lines
22 KiB
TypeScript
import { app, BrowserWindow, ipcMain, dialog, shell, Tray, Menu, nativeImage } from 'electron'
|
||
import * as path from 'path'
|
||
import * as fs from 'fs'
|
||
import { spawn } from 'child_process'
|
||
import { getAppRoot, checkEnvironment } from './services/environmentService'
|
||
import { setMainWindow as setDownloadMainWindow } from './services/downloadService'
|
||
import {
|
||
setMainWindow as setPythonMainWindow,
|
||
downloadPython,
|
||
installPipPackage,
|
||
installDependencies,
|
||
startBackend,
|
||
stopBackend,
|
||
} from './services/pythonService'
|
||
import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService'
|
||
import { setupLogger, log, getLogPath, getLogFiles, cleanOldLogs } from './services/logService'
|
||
|
||
// 检查是否以管理员权限运行
|
||
function isRunningAsAdmin(): boolean {
|
||
try {
|
||
// 在Windows上,尝试写入系统目录来检查管理员权限
|
||
if (process.platform === 'win32') {
|
||
const testPath = path.join(process.env.WINDIR || 'C:\\Windows', 'temp', 'admin-test.tmp')
|
||
try {
|
||
fs.writeFileSync(testPath, 'test')
|
||
fs.unlinkSync(testPath)
|
||
return true
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
return true // 非Windows系统暂时返回true
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 重新以管理员权限启动应用
|
||
function restartAsAdmin(): void {
|
||
if (process.platform === 'win32') {
|
||
const exePath = process.execPath
|
||
const args = process.argv.slice(1)
|
||
|
||
// 使用PowerShell以管理员权限启动
|
||
spawn(
|
||
'powershell',
|
||
[
|
||
'-Command',
|
||
`Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs`,
|
||
],
|
||
{
|
||
detached: true,
|
||
stdio: 'ignore',
|
||
}
|
||
)
|
||
|
||
app.quit()
|
||
}
|
||
}
|
||
|
||
let mainWindow: BrowserWindow | null = null
|
||
let tray: Tray | null = null
|
||
let isQuitting = false
|
||
let saveWindowStateTimeout: NodeJS.Timeout | null = null
|
||
|
||
// 配置接口
|
||
interface AppConfig {
|
||
UI: {
|
||
IfShowTray: boolean
|
||
IfToTray: boolean
|
||
location: string
|
||
maximized: boolean
|
||
size: string
|
||
}
|
||
Start: {
|
||
IfMinimizeDirectly: boolean
|
||
IfSelfStart: boolean
|
||
}
|
||
[key: string]: any
|
||
}
|
||
|
||
// 默认配置
|
||
const defaultConfig: AppConfig = {
|
||
UI: {
|
||
IfShowTray: false,
|
||
IfToTray: false,
|
||
location: '100,100',
|
||
maximized: false,
|
||
size: '1600,1000'
|
||
},
|
||
Start: {
|
||
IfMinimizeDirectly: false,
|
||
IfSelfStart: false
|
||
}
|
||
}
|
||
|
||
// 加载配置
|
||
function loadConfig(): AppConfig {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const configPath = path.join(appRoot, 'config', 'frontend_config.json')
|
||
|
||
if (fs.existsSync(configPath)) {
|
||
const configData = fs.readFileSync(configPath, 'utf8')
|
||
const config = JSON.parse(configData)
|
||
return { ...defaultConfig, ...config }
|
||
}
|
||
} catch (error) {
|
||
log.error('加载配置失败:', error)
|
||
}
|
||
return defaultConfig
|
||
}
|
||
|
||
// 保存配置
|
||
function saveConfig(config: AppConfig) {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const configDir = path.join(appRoot, 'config')
|
||
const configPath = path.join(configDir, 'frontend_config.json')
|
||
|
||
if (!fs.existsSync(configDir)) {
|
||
fs.mkdirSync(configDir, { recursive: true })
|
||
}
|
||
|
||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8')
|
||
} catch (error) {
|
||
log.error('保存配置失败:', error)
|
||
}
|
||
}
|
||
|
||
// 创建托盘
|
||
function createTray() {
|
||
if (tray) return
|
||
|
||
// 尝试多个可能的图标路径
|
||
const iconPaths = [
|
||
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')
|
||
]
|
||
|
||
let trayIcon
|
||
|
||
try {
|
||
// 尝试加载图标
|
||
for (const iconPath of iconPaths) {
|
||
if (fs.existsSync(iconPath)) {
|
||
trayIcon = nativeImage.createFromPath(iconPath)
|
||
if (!trayIcon.isEmpty()) {
|
||
log.info(`成功加载托盘图标: ${iconPath}`)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果所有路径都失败,创建一个默认图标
|
||
if (!trayIcon || trayIcon.isEmpty()) {
|
||
log.warn('无法加载托盘图标,使用默认图标')
|
||
trayIcon = nativeImage.createEmpty()
|
||
}
|
||
} catch (error) {
|
||
log.error('加载托盘图标失败:', error)
|
||
trayIcon = nativeImage.createEmpty()
|
||
}
|
||
|
||
tray = new Tray(trayIcon)
|
||
|
||
const contextMenu = Menu.buildFromTemplate([
|
||
{
|
||
label: '显示窗口',
|
||
click: () => {
|
||
if (mainWindow) {
|
||
if (mainWindow.isMinimized()) {
|
||
mainWindow.restore()
|
||
}
|
||
mainWindow.setSkipTaskbar(false) // 恢复任务栏图标
|
||
mainWindow.show()
|
||
mainWindow.focus()
|
||
}
|
||
}
|
||
},
|
||
{
|
||
label: '隐藏窗口',
|
||
click: () => {
|
||
if (mainWindow) {
|
||
const currentConfig = loadConfig()
|
||
if (currentConfig.UI.IfToTray) {
|
||
mainWindow.setSkipTaskbar(true) // 隐藏任务栏图标
|
||
}
|
||
mainWindow.hide()
|
||
}
|
||
}
|
||
},
|
||
{ type: 'separator' },
|
||
{
|
||
label: '退出',
|
||
click: () => {
|
||
isQuitting = true
|
||
app.quit()
|
||
}
|
||
}
|
||
])
|
||
|
||
tray.setContextMenu(contextMenu)
|
||
tray.setToolTip('AUTO_MAA')
|
||
|
||
// 双击托盘图标显示/隐藏窗口
|
||
tray.on('double-click', () => {
|
||
if (mainWindow) {
|
||
const currentConfig = loadConfig()
|
||
if (mainWindow.isVisible()) {
|
||
if (currentConfig.UI.IfToTray) {
|
||
mainWindow.setSkipTaskbar(true) // 隐藏任务栏图标
|
||
}
|
||
mainWindow.hide()
|
||
} else {
|
||
if (mainWindow.isMinimized()) {
|
||
mainWindow.restore()
|
||
}
|
||
mainWindow.setSkipTaskbar(false) // 恢复任务栏图标
|
||
mainWindow.show()
|
||
mainWindow.focus()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 销毁托盘
|
||
function destroyTray() {
|
||
if (tray) {
|
||
tray.destroy()
|
||
tray = null
|
||
}
|
||
}
|
||
|
||
// 更新托盘状态
|
||
function updateTrayVisibility(config: AppConfig) {
|
||
// 根据需求逻辑判断是否应该显示托盘
|
||
let shouldShowTray = false
|
||
|
||
if (config.UI.IfShowTray && config.UI.IfToTray) {
|
||
// 勾选常驻显示托盘和最小化到托盘,就一直展示托盘
|
||
shouldShowTray = true
|
||
} else if (config.UI.IfShowTray && !config.UI.IfToTray) {
|
||
// 勾选常驻显示托盘但没有最小化到托盘,就一直展示托盘
|
||
shouldShowTray = true
|
||
} else if (!config.UI.IfShowTray && config.UI.IfToTray) {
|
||
// 没有常驻显示托盘但勾选最小化到托盘,有窗口时就只有窗口,最小化后任务栏消失,只有托盘
|
||
shouldShowTray = !mainWindow || !mainWindow.isVisible()
|
||
} else {
|
||
// 没有常驻显示托盘也没有最小化到托盘,托盘一直不展示
|
||
shouldShowTray = false
|
||
}
|
||
|
||
// 特殊情况:如果没有窗口显示且没有托盘,强制显示托盘避免程序成为幽灵
|
||
if (!shouldShowTray && (!mainWindow || !mainWindow.isVisible()) && !tray) {
|
||
shouldShowTray = true
|
||
log.warn('防幽灵机制:强制显示托盘图标')
|
||
}
|
||
|
||
if (shouldShowTray && !tray) {
|
||
createTray()
|
||
log.info('托盘图标已创建')
|
||
} else if (!shouldShowTray && tray) {
|
||
destroyTray()
|
||
log.info('托盘图标已销毁')
|
||
}
|
||
}
|
||
|
||
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)
|
||
|
||
mainWindow = new BrowserWindow({
|
||
width: Math.max(width, 800),
|
||
height: Math.max(height, 600),
|
||
x,
|
||
y,
|
||
minWidth: 800,
|
||
minHeight: 600,
|
||
icon: path.join(__dirname, '../public/AUTO-MAS.ico'),
|
||
frame: false, // 去掉系统标题栏
|
||
titleBarStyle: 'hidden', // 隐藏标题栏
|
||
webPreferences: {
|
||
preload: path.join(__dirname, 'preload.js'),
|
||
nodeIntegration: false,
|
||
contextIsolation: true,
|
||
},
|
||
autoHideMenuBar: true,
|
||
show: !config.Start.IfMinimizeDirectly, // 根据配置决定是否直接显示
|
||
})
|
||
|
||
// 如果配置为最大化,则最大化窗口
|
||
if (config.UI.maximized) {
|
||
mainWindow.maximize()
|
||
}
|
||
|
||
mainWindow.setMenuBarVisibility(false)
|
||
const devServer = process.env.VITE_DEV_SERVER_URL
|
||
if (devServer) {
|
||
log.info(`加载开发服务器: ${devServer}`)
|
||
mainWindow.loadURL(devServer)
|
||
} else {
|
||
const indexHtmlPath = path.join(app.getAppPath(), 'dist', 'index.html')
|
||
log.info(`加载生产环境页面: ${indexHtmlPath}`)
|
||
mainWindow.loadFile(indexHtmlPath)
|
||
}
|
||
|
||
// 窗口事件处理
|
||
mainWindow.on('close', (event) => {
|
||
const currentConfig = loadConfig()
|
||
|
||
if (!isQuitting && currentConfig.UI.IfToTray) {
|
||
// 如果启用了最小化到托盘,阻止关闭并隐藏窗口
|
||
event.preventDefault()
|
||
mainWindow?.hide()
|
||
mainWindow?.setSkipTaskbar(true)
|
||
|
||
// 更新托盘状态
|
||
updateTrayVisibility(currentConfig)
|
||
|
||
log.info('窗口已最小化到托盘,任务栏图标已隐藏')
|
||
} else {
|
||
// 保存窗口状态
|
||
saveWindowState()
|
||
}
|
||
})
|
||
|
||
mainWindow.on('closed', () => {
|
||
log.info('主窗口已关闭')
|
||
mainWindow = null
|
||
})
|
||
|
||
// 窗口最小化事件
|
||
mainWindow.on('minimize', () => {
|
||
const currentConfig = loadConfig()
|
||
|
||
if (currentConfig.UI.IfToTray) {
|
||
// 如果启用了最小化到托盘,隐藏窗口并从任务栏移除
|
||
mainWindow?.hide()
|
||
mainWindow?.setSkipTaskbar(true)
|
||
|
||
// 更新托盘状态
|
||
updateTrayVisibility(currentConfig)
|
||
|
||
log.info('窗口已最小化到托盘,任务栏图标已隐藏')
|
||
}
|
||
})
|
||
|
||
// 窗口显示/隐藏事件,用于更新托盘状态
|
||
mainWindow.on('show', () => {
|
||
const currentConfig = loadConfig()
|
||
// 窗口显示时,恢复任务栏图标
|
||
mainWindow?.setSkipTaskbar(false)
|
||
updateTrayVisibility(currentConfig)
|
||
log.info('窗口已显示,任务栏图标已恢复')
|
||
})
|
||
|
||
mainWindow.on('hide', () => {
|
||
const currentConfig = loadConfig()
|
||
// 窗口隐藏时,根据配置决定是否隐藏任务栏图标
|
||
if (currentConfig.UI.IfToTray) {
|
||
mainWindow?.setSkipTaskbar(true)
|
||
log.info('窗口已隐藏,任务栏图标已隐藏')
|
||
}
|
||
updateTrayVisibility(currentConfig)
|
||
})
|
||
|
||
// 窗口移动和调整大小时保存状态
|
||
mainWindow.on('moved', saveWindowState)
|
||
mainWindow.on('resized', saveWindowState)
|
||
mainWindow.on('maximize', saveWindowState)
|
||
mainWindow.on('unmaximize', saveWindowState)
|
||
|
||
// 设置各个服务的主窗口引用
|
||
if (mainWindow) {
|
||
setDownloadMainWindow(mainWindow)
|
||
setPythonMainWindow(mainWindow)
|
||
setGitMainWindow(mainWindow)
|
||
log.info('主窗口创建完成,服务引用已设置')
|
||
}
|
||
|
||
// 根据配置初始化托盘
|
||
updateTrayVisibility(config)
|
||
}
|
||
|
||
// 保存窗口状态(带防抖)
|
||
function saveWindowState() {
|
||
if (!mainWindow) return
|
||
|
||
// 清除之前的定时器
|
||
if (saveWindowStateTimeout) {
|
||
clearTimeout(saveWindowStateTimeout)
|
||
}
|
||
|
||
// 设置新的定时器,500ms后保存
|
||
saveWindowStateTimeout = setTimeout(() => {
|
||
try {
|
||
const config = loadConfig()
|
||
const bounds = mainWindow!.getBounds()
|
||
const isMaximized = mainWindow!.isMaximized()
|
||
|
||
// 只有在窗口不是最大化状态时才保存位置和大小
|
||
if (!isMaximized) {
|
||
config.UI.size = `${bounds.width},${bounds.height}`
|
||
config.UI.location = `${bounds.x},${bounds.y}`
|
||
}
|
||
config.UI.maximized = isMaximized
|
||
|
||
saveConfig(config)
|
||
log.info('窗口状态已保存')
|
||
} catch (error) {
|
||
log.error('保存窗口状态失败:', error)
|
||
}
|
||
}, 500)
|
||
}
|
||
|
||
// IPC处理函数
|
||
ipcMain.handle('open-dev-tools', () => {
|
||
if (mainWindow) {
|
||
mainWindow.webContents.openDevTools({ mode: 'undocked' })
|
||
}
|
||
})
|
||
|
||
// 窗口控制
|
||
ipcMain.handle('window-minimize', () => {
|
||
if (mainWindow) {
|
||
mainWindow.minimize()
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('window-maximize', () => {
|
||
if (mainWindow) {
|
||
if (mainWindow.isMaximized()) {
|
||
mainWindow.unmaximize()
|
||
} else {
|
||
mainWindow.maximize()
|
||
}
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('window-close', () => {
|
||
if (mainWindow) {
|
||
mainWindow.close()
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('window-is-maximized', () => {
|
||
return mainWindow ? mainWindow.isMaximized() : false
|
||
})
|
||
|
||
ipcMain.handle('select-folder', async () => {
|
||
if (!mainWindow) return null
|
||
const result = await dialog.showOpenDialog(mainWindow, {
|
||
properties: ['openDirectory'],
|
||
title: '选择文件夹',
|
||
})
|
||
return result.canceled ? null : result.filePaths[0]
|
||
})
|
||
|
||
ipcMain.handle('select-file', async (event, filters = []) => {
|
||
if (!mainWindow) return []
|
||
const result = await dialog.showOpenDialog(mainWindow, {
|
||
properties: ['openFile'],
|
||
title: '选择文件',
|
||
filters: filters.length > 0 ? filters : [{ name: '所有文件', extensions: ['*'] }],
|
||
})
|
||
return result.canceled ? [] : result.filePaths
|
||
})
|
||
|
||
// 在系统默认浏览器中打开URL
|
||
ipcMain.handle('open-url', async (_event, url: string) => {
|
||
try {
|
||
await shell.openExternal(url)
|
||
return { success: true }
|
||
} catch (error) {
|
||
if (error instanceof Error) {
|
||
console.error('打开链接失败:', error.message)
|
||
return { success: false, error: error.message }
|
||
} else {
|
||
console.error('未知错误:', error)
|
||
return { success: false, error: String(error) }
|
||
}
|
||
}
|
||
})
|
||
|
||
// 打开文件
|
||
ipcMain.handle('open-file', async (_event, filePath: string) => {
|
||
try {
|
||
await shell.openPath(filePath)
|
||
} catch (error) {
|
||
console.error('打开文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
// 显示文件所在目录并选中文件
|
||
ipcMain.handle('show-item-in-folder', async (_event, filePath: string) => {
|
||
try {
|
||
shell.showItemInFolder(filePath)
|
||
} catch (error) {
|
||
console.error('显示文件所在目录失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
// 环境检查
|
||
ipcMain.handle('check-environment', async () => {
|
||
const appRoot = getAppRoot()
|
||
return checkEnvironment(appRoot)
|
||
})
|
||
|
||
// Python相关
|
||
ipcMain.handle('download-python', async (_event, mirror = 'tsinghua') => {
|
||
const appRoot = getAppRoot()
|
||
return downloadPython(appRoot, mirror)
|
||
})
|
||
|
||
ipcMain.handle('install-pip', async () => {
|
||
const appRoot = getAppRoot()
|
||
return installPipPackage(appRoot)
|
||
})
|
||
|
||
ipcMain.handle('install-dependencies', async (_event, mirror = 'tsinghua') => {
|
||
const appRoot = getAppRoot()
|
||
return installDependencies(appRoot, mirror)
|
||
})
|
||
|
||
ipcMain.handle('start-backend', async () => {
|
||
const appRoot = getAppRoot()
|
||
return startBackend(appRoot)
|
||
})
|
||
|
||
// Git相关
|
||
ipcMain.handle('download-git', async () => {
|
||
const appRoot = getAppRoot()
|
||
return downloadGit(appRoot)
|
||
})
|
||
|
||
ipcMain.handle(
|
||
'clone-backend',
|
||
async (_event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => {
|
||
const appRoot = getAppRoot()
|
||
return cloneBackend(appRoot, repoUrl)
|
||
}
|
||
)
|
||
|
||
ipcMain.handle(
|
||
'update-backend',
|
||
async (_event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => {
|
||
const appRoot = getAppRoot()
|
||
return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone
|
||
}
|
||
)
|
||
|
||
// 配置文件操作
|
||
ipcMain.handle('save-config', async (_event, config) => {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const configDir = path.join(appRoot, 'config')
|
||
const configPath = path.join(configDir, 'frontend_config.json')
|
||
|
||
// 确保config目录存在
|
||
if (!fs.existsSync(configDir)) {
|
||
fs.mkdirSync(configDir, { recursive: true })
|
||
}
|
||
|
||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8')
|
||
console.log(`配置已保存到: ${configPath}`)
|
||
|
||
// 如果是UI配置更新,需要更新托盘状态
|
||
if (config.UI) {
|
||
updateTrayVisibility(config)
|
||
}
|
||
} catch (error) {
|
||
console.error('保存配置文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
// 新增:实时更新托盘状态的IPC处理器
|
||
ipcMain.handle('update-tray-settings', async (_event, uiSettings) => {
|
||
try {
|
||
// 先更新配置文件
|
||
const currentConfig = loadConfig()
|
||
currentConfig.UI = { ...currentConfig.UI, ...uiSettings }
|
||
saveConfig(currentConfig)
|
||
|
||
// 立即更新托盘状态
|
||
updateTrayVisibility(currentConfig)
|
||
|
||
log.info('托盘设置已更新:', uiSettings)
|
||
return true
|
||
} catch (error) {
|
||
log.error('更新托盘设置失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('load-config', async () => {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const configPath = path.join(appRoot, 'config', 'frontend_config.json')
|
||
|
||
if (fs.existsSync(configPath)) {
|
||
const config = fs.readFileSync(configPath, 'utf8')
|
||
console.log(`从文件加载配置: ${configPath}`)
|
||
return JSON.parse(config)
|
||
}
|
||
|
||
return null
|
||
} catch (error) {
|
||
console.error('加载配置文件失败:', error)
|
||
return null
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('reset-config', async () => {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const configPath = path.join(appRoot, 'config', 'frontend_config.json')
|
||
|
||
if (fs.existsSync(configPath)) {
|
||
fs.unlinkSync(configPath)
|
||
console.log(`配置文件已删除: ${configPath}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('重置配置文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
// 日志文件操作
|
||
ipcMain.handle('get-log-path', async () => {
|
||
try {
|
||
return getLogPath()
|
||
} catch (error) {
|
||
log.error('获取日志路径失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('get-log-files', async (_event) => {
|
||
try {
|
||
return getLogFiles()
|
||
} catch (error) {
|
||
log.error('获取日志文件列表失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('get-logs', async (_event, lines?: number, fileName?: string) => {
|
||
try {
|
||
let logFilePath: string
|
||
|
||
if (fileName) {
|
||
// 如果指定了文件名,使用指定的文件
|
||
const appRoot = getAppRoot()
|
||
logFilePath = path.join(appRoot, 'logs', fileName)
|
||
} else {
|
||
// 否则使用当前日志文件
|
||
logFilePath = getLogPath()
|
||
}
|
||
|
||
if (!fs.existsSync(logFilePath)) {
|
||
return ''
|
||
}
|
||
|
||
const logs = fs.readFileSync(logFilePath, 'utf8')
|
||
|
||
if (lines && lines > 0) {
|
||
const logLines = logs.split('\n')
|
||
return logLines.slice(-lines).join('\n')
|
||
}
|
||
|
||
return logs
|
||
} catch (error) {
|
||
log.error('读取日志文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('clear-logs', async (_event, fileName?: string) => {
|
||
try {
|
||
let logFilePath: string
|
||
|
||
if (fileName) {
|
||
// 如果指定了文件名,清空指定的文件
|
||
const appRoot = getAppRoot()
|
||
logFilePath = path.join(appRoot, 'logs', fileName)
|
||
} else {
|
||
// 否则清空当前日志文件
|
||
logFilePath = getLogPath()
|
||
}
|
||
|
||
if (fs.existsSync(logFilePath)) {
|
||
fs.writeFileSync(logFilePath, '', 'utf8')
|
||
log.info(`日志文件已清空: ${fileName || '当前文件'}`)
|
||
}
|
||
} catch (error) {
|
||
log.error('清空日志文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('clean-old-logs', async (_event, daysToKeep = 7) => {
|
||
try {
|
||
cleanOldLogs(daysToKeep)
|
||
log.info(`已清理${daysToKeep}天前的旧日志文件`)
|
||
} catch (error) {
|
||
log.error('清理旧日志文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
// 保留原有的日志操作方法以兼容现有代码
|
||
ipcMain.handle('save-logs-to-file', async (_event, logs: string) => {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const logsDir = path.join(appRoot, 'logs')
|
||
|
||
// 确保logs目录存在
|
||
if (!fs.existsSync(logsDir)) {
|
||
fs.mkdirSync(logsDir, { recursive: true })
|
||
}
|
||
|
||
const logFilePath = path.join(logsDir, 'app.log')
|
||
fs.writeFileSync(logFilePath, logs, 'utf8')
|
||
log.info(`日志已保存到: ${logFilePath}`)
|
||
} catch (error) {
|
||
log.error('保存日志文件失败:', error)
|
||
throw error
|
||
}
|
||
})
|
||
|
||
ipcMain.handle('load-logs-from-file', async () => {
|
||
try {
|
||
const appRoot = getAppRoot()
|
||
const logFilePath = path.join(appRoot, 'logs', 'app.log')
|
||
|
||
if (fs.existsSync(logFilePath)) {
|
||
const logs = fs.readFileSync(logFilePath, 'utf8')
|
||
log.info(`从文件加载日志: ${logFilePath}`)
|
||
return logs
|
||
}
|
||
|
||
return null
|
||
} catch (error) {
|
||
log.error('加载日志文件失败:', error)
|
||
return null
|
||
}
|
||
})
|
||
|
||
// 管理员权限相关
|
||
ipcMain.handle('check-admin', () => {
|
||
return isRunningAsAdmin()
|
||
})
|
||
|
||
ipcMain.handle('restart-as-admin', () => {
|
||
restartAsAdmin()
|
||
})
|
||
|
||
// 应用生命周期
|
||
// 保证应用单例运行
|
||
const gotTheLock = app.requestSingleInstanceLock()
|
||
|
||
if (!gotTheLock) {
|
||
app.quit()
|
||
process.exit(0)
|
||
}
|
||
|
||
app.on('second-instance', () => {
|
||
if (mainWindow) {
|
||
if (mainWindow.isMinimized()) mainWindow.restore()
|
||
mainWindow.focus()
|
||
}
|
||
})
|
||
|
||
app.on('before-quit', async event => {
|
||
// 只处理一次,避免多重触发
|
||
if (!isQuitting) {
|
||
event.preventDefault()
|
||
isQuitting = true
|
||
|
||
log.info('应用准备退出')
|
||
|
||
// 清理托盘
|
||
destroyTray()
|
||
|
||
try {
|
||
await stopBackend()
|
||
log.info('后端服务已停止')
|
||
} catch (e) {
|
||
log.error('停止后端时出错:', e)
|
||
console.error('停止后端时出错:', e)
|
||
} finally {
|
||
log.info('应用退出')
|
||
app.exit(0)
|
||
}
|
||
}
|
||
})
|
||
|
||
app.whenReady().then(() => {
|
||
// 初始化日志系统
|
||
setupLogger()
|
||
|
||
// 清理7天前的旧日志
|
||
cleanOldLogs(7)
|
||
|
||
log.info('应用启动')
|
||
log.info(`应用版本: ${app.getVersion()}`)
|
||
log.info(`Electron版本: ${process.versions.electron}`)
|
||
log.info(`Node版本: ${process.versions.node}`)
|
||
log.info(`平台: ${process.platform}`)
|
||
|
||
// 检查管理员权限
|
||
if (!isRunningAsAdmin()) {
|
||
log.warn('应用未以管理员权限运行')
|
||
console.log('应用未以管理员权限运行')
|
||
// 在生产环境中,可以选择是否强制要求管理员权限
|
||
// 这里先创建窗口,让用户选择是否重新启动
|
||
} else {
|
||
log.info('应用以管理员权限运行')
|
||
}
|
||
|
||
createWindow()
|
||
})
|
||
|
||
app.on('window-all-closed', () => {
|
||
if (process.platform !== 'darwin') app.quit()
|
||
})
|
||
|
||
app.on('activate', () => {
|
||
if (mainWindow === null) createWindow()
|
||
})
|