feat(log): 实现日志文件持久化和全局进度条
- 新增日志文件保存和加载功能 - 实现全局进度条组件 - 优化初始化界面布局 - 更新设置界面,增加系统日志查看按钮
This commit is contained in:
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const electron_1 = require("electron");
|
const electron_1 = require("electron");
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
|
const fs = __importStar(require("fs"));
|
||||||
const environmentService_1 = require("./services/environmentService");
|
const environmentService_1 = require("./services/environmentService");
|
||||||
const downloadService_1 = require("./services/downloadService");
|
const downloadService_1 = require("./services/downloadService");
|
||||||
const pythonService_1 = require("./services/pythonService");
|
const pythonService_1 = require("./services/pythonService");
|
||||||
@@ -129,6 +130,40 @@ electron_1.ipcMain.handle('update-backend', async (event, repoUrl = 'https://git
|
|||||||
const appRoot = (0, environmentService_1.getAppRoot)();
|
const appRoot = (0, environmentService_1.getAppRoot)();
|
||||||
return (0, gitService_1.cloneBackend)(appRoot, repoUrl); // 使用相同的逻辑,会自动判断是pull还是clone
|
return (0, gitService_1.cloneBackend)(appRoot, repoUrl); // 使用相同的逻辑,会自动判断是pull还是clone
|
||||||
});
|
});
|
||||||
|
// 日志文件操作
|
||||||
|
electron_1.ipcMain.handle('save-logs-to-file', async (event, logs) => {
|
||||||
|
try {
|
||||||
|
const appRoot = (0, environmentService_1.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');
|
||||||
|
console.log(`日志已保存到: ${logFilePath}`);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('保存日志文件失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron_1.ipcMain.handle('load-logs-from-file', async () => {
|
||||||
|
try {
|
||||||
|
const appRoot = (0, environmentService_1.getAppRoot)();
|
||||||
|
const logFilePath = path.join(appRoot, 'logs', 'app.log');
|
||||||
|
if (fs.existsSync(logFilePath)) {
|
||||||
|
const logs = fs.readFileSync(logFilePath, 'utf8');
|
||||||
|
console.log(`从文件加载日志: ${logFilePath}`);
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('加载日志文件失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
// 应用生命周期
|
// 应用生命周期
|
||||||
electron_1.app.whenReady().then(createWindow);
|
electron_1.app.whenReady().then(createWindow);
|
||||||
electron_1.app.on('window-all-closed', () => {
|
electron_1.app.on('window-all-closed', () => {
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
cloneBackend: (repoUrl) => electron_1.ipcRenderer.invoke('clone-backend', repoUrl),
|
cloneBackend: (repoUrl) => electron_1.ipcRenderer.invoke('clone-backend', repoUrl),
|
||||||
updateBackend: (repoUrl) => electron_1.ipcRenderer.invoke('update-backend', repoUrl),
|
updateBackend: (repoUrl) => electron_1.ipcRenderer.invoke('update-backend', repoUrl),
|
||||||
startBackend: () => electron_1.ipcRenderer.invoke('start-backend'),
|
startBackend: () => electron_1.ipcRenderer.invoke('start-backend'),
|
||||||
|
// 日志文件操作
|
||||||
|
saveLogsToFile: (logs) => electron_1.ipcRenderer.invoke('save-logs-to-file', logs),
|
||||||
|
loadLogsFromFile: () => electron_1.ipcRenderer.invoke('load-logs-from-file'),
|
||||||
// 监听下载进度
|
// 监听下载进度
|
||||||
onDownloadProgress: (callback) => {
|
onDownloadProgress: (callback) => {
|
||||||
electron_1.ipcRenderer.on('download-progress', (_, progress) => callback(progress));
|
electron_1.ipcRenderer.on('download-progress', (_, progress) => callback(progress));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
|
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
import { getAppRoot, checkEnvironment } from './services/environmentService'
|
import { getAppRoot, checkEnvironment } from './services/environmentService'
|
||||||
import { setMainWindow as setDownloadMainWindow } from './services/downloadService'
|
import { setMainWindow as setDownloadMainWindow } from './services/downloadService'
|
||||||
import { setMainWindow as setPythonMainWindow, downloadPython, installDependencies, startBackend } from './services/pythonService'
|
import { setMainWindow as setPythonMainWindow, downloadPython, installDependencies, startBackend } from './services/pythonService'
|
||||||
@@ -108,6 +109,44 @@ ipcMain.handle('update-backend', async (event, repoUrl = 'https://github.com/DLm
|
|||||||
return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone
|
return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 日志文件操作
|
||||||
|
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')
|
||||||
|
console.log(`日志已保存到: ${logFilePath}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.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')
|
||||||
|
console.log(`从文件加载日志: ${logFilePath}`)
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载日志文件失败:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 应用生命周期
|
// 应用生命周期
|
||||||
app.whenReady().then(createWindow)
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl),
|
updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl),
|
||||||
startBackend: () => ipcRenderer.invoke('start-backend'),
|
startBackend: () => ipcRenderer.invoke('start-backend'),
|
||||||
|
|
||||||
|
// 日志文件操作
|
||||||
|
saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs),
|
||||||
|
loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'),
|
||||||
|
|
||||||
// 监听下载进度
|
// 监听下载进度
|
||||||
onDownloadProgress: (callback: (progress: any) => void) => {
|
onDownloadProgress: (callback: (progress: any) => void) => {
|
||||||
ipcRenderer.on('download-progress', (_, progress) => callback(progress))
|
ipcRenderer.on('download-progress', (_, progress) => callback(progress))
|
||||||
|
|||||||
4
frontend/src/types/electron.d.ts
vendored
4
frontend/src/types/electron.d.ts
vendored
@@ -18,6 +18,10 @@ export interface ElectronAPI {
|
|||||||
updateBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
|
updateBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
|
||||||
startBackend: () => Promise<{ success: boolean; error?: string }>
|
startBackend: () => Promise<{ success: boolean; error?: string }>
|
||||||
|
|
||||||
|
// 日志文件操作
|
||||||
|
saveLogsToFile: (logs: string) => Promise<void>
|
||||||
|
loadLogsFromFile: () => Promise<string | null>
|
||||||
|
|
||||||
// 监听下载进度
|
// 监听下载进度
|
||||||
onDownloadProgress: (callback: (progress: any) => void) => void
|
onDownloadProgress: (callback: (progress: any) => void) => void
|
||||||
removeDownloadProgressListener: () => void
|
removeDownloadProgressListener: () => void
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class Logger {
|
|||||||
private logToStorage = true
|
private logToStorage = true
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// 延迟加载日志,等待electron API准备就绪
|
||||||
|
setTimeout(() => {
|
||||||
this.loadLogsFromStorage()
|
this.loadLogsFromStorage()
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatTimestamp(): string {
|
private formatTimestamp(): string {
|
||||||
@@ -68,24 +71,28 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveLogsToStorage() {
|
private async saveLogsToStorage() {
|
||||||
try {
|
try {
|
||||||
|
if (window.electronAPI && window.electronAPI.saveLogsToFile) {
|
||||||
const logsToSave = this.logs.value.slice(-500) // 只保存最近500条日志
|
const logsToSave = this.logs.value.slice(-500) // 只保存最近500条日志
|
||||||
localStorage.setItem('app-logs', JSON.stringify(logsToSave))
|
await window.electronAPI.saveLogsToFile(JSON.stringify(logsToSave, null, 2))
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存日志到本地存储失败:', error)
|
console.error('保存日志到本地文件失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadLogsFromStorage() {
|
private async loadLogsFromStorage() {
|
||||||
try {
|
try {
|
||||||
const savedLogs = localStorage.getItem('app-logs')
|
if (window.electronAPI && window.electronAPI.loadLogsFromFile) {
|
||||||
|
const savedLogs = await window.electronAPI.loadLogsFromFile()
|
||||||
if (savedLogs) {
|
if (savedLogs) {
|
||||||
const parsedLogs = JSON.parse(savedLogs) as LogEntry[]
|
const parsedLogs = JSON.parse(savedLogs) as LogEntry[]
|
||||||
this.logs.value = parsedLogs
|
this.logs.value = parsedLogs
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('从本地存储加载日志失败:', error)
|
console.error('从本地文件加载日志失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="initialization-container">
|
<div class="initialization-container">
|
||||||
<div class="initialization-content">
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>AUTO MAA 初始化向导</h1>
|
<h1>AUTO MAA 初始化向导</h1>
|
||||||
<p>欢迎使用 AUTO MAA,让我们来配置您的运行环境</p>
|
<p>欢迎使用 AUTO MAA,让我们来配置您的运行环境</p>
|
||||||
@@ -19,6 +18,16 @@
|
|||||||
<a-step title="启动服务" description="启动后端服务" />
|
<a-step title="启动服务" description="启动后端服务" />
|
||||||
</a-steps>
|
</a-steps>
|
||||||
|
|
||||||
|
<!-- 全局进度条 -->
|
||||||
|
<div v-if="isProcessing" class="global-progress">
|
||||||
|
<a-progress
|
||||||
|
:percent="globalProgress"
|
||||||
|
:status="globalProgressStatus"
|
||||||
|
:show-info="true"
|
||||||
|
/>
|
||||||
|
<div class="progress-text">{{ progressText }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="step-content">
|
<div class="step-content">
|
||||||
<!-- 步骤 0: 主题设置 -->
|
<!-- 步骤 0: 主题设置 -->
|
||||||
<div v-if="currentStep === 0" class="step-panel">
|
<div v-if="currentStep === 0" class="step-panel">
|
||||||
@@ -229,7 +238,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -280,7 +289,7 @@ const pipMirrors = ref([
|
|||||||
|
|
||||||
const gitMirrors = ref([
|
const gitMirrors = ref([
|
||||||
{ key: 'github', name: 'GitHub 官方', url: 'https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null },
|
{ key: 'github', name: 'GitHub 官方', url: 'https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null },
|
||||||
{ key: 'fastgit', name: 'FastGit 镜像', url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null }
|
{ key: 'ghfast', name: 'ghfast 镜像', url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git', speed: null as number | null }
|
||||||
])
|
])
|
||||||
|
|
||||||
// 选中的镜像源
|
// 选中的镜像源
|
||||||
@@ -299,6 +308,11 @@ const showServiceProgress = ref(false)
|
|||||||
const serviceProgress = ref(0)
|
const serviceProgress = ref(0)
|
||||||
const serviceStatus = ref('准备启动后端服务...')
|
const serviceStatus = ref('准备启动后端服务...')
|
||||||
|
|
||||||
|
// 全局进度条状态
|
||||||
|
const globalProgress = ref(0)
|
||||||
|
const globalProgressStatus = ref<'normal' | 'exception' | 'success'>('normal')
|
||||||
|
const progressText = ref('')
|
||||||
|
|
||||||
const allCompleted = computed(() =>
|
const allCompleted = computed(() =>
|
||||||
pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value
|
pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value
|
||||||
)
|
)
|
||||||
@@ -654,6 +668,19 @@ function getSpeedClass(speed: number | null) {
|
|||||||
|
|
||||||
// 监听下载进度
|
// 监听下载进度
|
||||||
function handleDownloadProgress(progress: DownloadProgress) {
|
function handleDownloadProgress(progress: DownloadProgress) {
|
||||||
|
// 更新全局进度条
|
||||||
|
globalProgress.value = progress.progress
|
||||||
|
progressText.value = progress.message
|
||||||
|
|
||||||
|
if (progress.status === 'error') {
|
||||||
|
globalProgressStatus.value = 'exception'
|
||||||
|
} else if (progress.status === 'completed') {
|
||||||
|
globalProgressStatus.value = 'success'
|
||||||
|
} else {
|
||||||
|
globalProgressStatus.value = 'normal'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新服务进度(如果是服务相关)
|
||||||
if (progress.type === 'service') {
|
if (progress.type === 'service') {
|
||||||
serviceProgress.value = progress.progress
|
serviceProgress.value = progress.progress
|
||||||
serviceStatus.value = progress.message
|
serviceStatus.value = progress.message
|
||||||
@@ -678,19 +705,9 @@ onUnmounted(() => {
|
|||||||
.initialization-container {
|
.initialization-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--ant-color-bg-layout);
|
background: var(--ant-color-bg-layout);
|
||||||
display: flex;
|
padding: 50px 100px;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.initialization-content {
|
margin: 0 auto;
|
||||||
background: var(--ant-color-bg-container);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 40px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
max-width: 900px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@@ -935,6 +952,58 @@ onUnmounted(() => {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局进度条样式 */
|
||||||
|
.global-progress {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--ant-color-bg-container);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--ant-color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态信息样式 */
|
||||||
|
.status-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: var(--ant-color-fill-quaternary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info.success {
|
||||||
|
background: var(--ant-color-success-bg);
|
||||||
|
border: 1px solid var(--ant-color-success-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.info {
|
||||||
|
color: var(--ant-color-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.success {
|
||||||
|
color: var(--ant-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.loading {
|
||||||
|
color: var(--ant-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-progress {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.initialization-content {
|
.initialization-content {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@@ -570,6 +570,14 @@ onMounted(() => {
|
|||||||
<p class="setting-description">打开浏览器开发者工具进行调试</p>
|
<p class="setting-description">打开浏览器开发者工具进行调试</p>
|
||||||
<Button type="primary" @click="openDevTools">打开 F12 开发者工具</Button>
|
<Button type="primary" @click="openDevTools">打开 F12 开发者工具</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h4>系统日志</h4>
|
||||||
|
<p class="setting-description">查看应用运行日志,用于问题排查和调试</p>
|
||||||
|
<Button type="default" @click="$router.push('/logs')">查看系统日志</Button>
|
||||||
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|||||||
Reference in New Issue
Block a user