Merge branch 'feature/refactor' of github.com:DLmaster361/AUTO_MAA into feature/refactor

This commit is contained in:
DLmaster361
2025-09-02 19:26:41 +08:00
20 changed files with 778 additions and 744 deletions

View File

@@ -5,6 +5,7 @@ import { ConfigProvider } from 'ant-design-vue'
import { useTheme } from './composables/useTheme.ts'
import AppLayout from './components/AppLayout.vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
import { logger } from '@/utils/logger'
const route = useRoute()
const { antdTheme, initTheme } = useTheme()
@@ -13,7 +14,9 @@ const { antdTheme, initTheme } = useTheme()
const isInitializationPage = computed(() => route.name === 'Initialization')
onMounted(() => {
logger.info('App组件已挂载')
initTheme()
logger.info('主题初始化完成')
})
</script>

View File

@@ -3,57 +3,56 @@
/* tslint:disable */
/* eslint-disable */
export type GeneralConfig_Script = {
/**
* 脚本可执行文件路径
*/
ScriptPath?: (string | null);
/**
* 脚本启动附加命令参数
*/
Arguments?: (string | null);
/**
* 是否追踪脚本子进程
*/
IfTrackProcess?: (boolean | null);
/**
* 配置文件路径
*/
ConfigPath?: (string | null);
/**
* 配置文件类型: 单个文件, 文件夹
*/
ConfigPathMode?: ('File' | 'Folder' | null);
/**
* 更新配置时机, 从不, 仅成功时, 仅失败时, 任务结束时
*/
UpdateConfigMode?: ('Never' | 'Success' | 'Failure' | 'Always' | null);
/**
* 日志文件路径
*/
LogPath?: (string | null);
/**
* 日志文件名格式
*/
LogPathFormat?: (string | null);
/**
* 日志时间戳开始位置
*/
LogTimeStart?: (number | null);
/**
* 日志时间戳结束位置
*/
LogTimeEnd?: (number | null);
/**
* 日志时间戳格式
*/
LogTimeFormat?: (string | null);
/**
* 成功时日志
*/
SuccessLog?: (string | null);
/**
* 错误时日志
*/
ErrorLog?: (string | null);
};
/**
* 脚本可执行文件路径
*/
ScriptPath?: string | null
/**
* 脚本启动附加命令参数
*/
Arguments?: string | null
/**
* 是否追踪脚本子进程
*/
IfTrackProcess?: boolean | null
/**
* 配置文件路径
*/
ConfigPath?: string | null
/**
* 配置文件类型: 单个文件, 文件夹
*/
ConfigPathMode?: 'File' | 'Folder' | null
/**
* 更新配置时机, 从不, 仅成功时, 仅失败时, 任务结束时
*/
UpdateConfigMode?: 'Never' | 'Success' | 'Failure' | 'Always' | null
/**
* 日志文件路径
*/
LogPath?: string | null
/**
* 日志文件名格式
*/
LogPathFormat?: string | null
/**
* 日志时间戳开始位置
*/
LogTimeStart?: number | null
/**
* 日志时间戳结束位置
*/
LogTimeEnd?: number | null
/**
* 日志时间戳格式
*/
LogTimeFormat?: string | null
/**
* 成功时日志
*/
SuccessLog?: string | null
/**
* 错误时日志
*/
ErrorLog?: string | null
}

View File

@@ -1,344 +1,189 @@
<template>
<div class="log-viewer">
<div class="log-header">
<div class="log-controls">
<a-select v-model:value="selectedLevel" style="width: 120px" @change="filterLogs">
<a-select-option value="all">所有级别</a-select-option>
<a-select-option value="debug">Debug</a-select-option>
<a-select-option value="info">Info</a-select-option>
<a-select-option value="warn">Warn</a-select-option>
<a-select-option value="error">Error</a-select-option>
</a-select>
<div class="log-viewer">
<div class="log-controls">
<a-space wrap>
<a-button @click="refreshLogs" :loading="loading">
<template #icon>
<ReloadOutlined />
</template>
刷新日志
</a-button>
<a-input-search
v-model:value="searchText"
placeholder="搜索日志..."
style="width: 200px"
@search="filterLogs"
@change="filterLogs"
/>
<a-select v-model:value="logLines" @change="refreshLogs" style="width: 120px">
<a-select-option :value="100">最近100行</a-select-option>
<a-select-option :value="500">最近500行</a-select-option>
<a-select-option :value="1000">最近1000行</a-select-option>
<a-select-option :value="0">全部日志</a-select-option>
</a-select>
<a-button @click="clearLogs" danger>
<template #icon>
<DeleteOutlined />
</template>
清空日志
</a-button>
<a-button @click="clearLogs" :loading="clearing" type="primary" danger>
<template #icon>
<DeleteOutlined />
</template>
清空日志
</a-button>
<a-button @click="downloadLogs" type="primary">
<template #icon>
<DownloadOutlined />
</template>
导出日志
</a-button>
<a-button @click="cleanOldLogs" :loading="cleaning">
<template #icon>
<ClearOutlined />
</template>
清理旧日志
</a-button>
<a-button @click="toggleAutoScroll" :type="autoScroll ? 'primary' : 'default'">
<template #icon>
<VerticalAlignBottomOutlined />
</template>
自动滚动
</a-button>
</div>
<div class="log-stats">总计: {{ filteredLogs.length }} 条日志</div>
</div>
<div ref="logContainer" class="log-container" @scroll="handleScroll">
<div
v-for="(log, index) in filteredLogs"
:key="index"
class="log-entry"
:class="[`log-${log.level}`, { 'log-highlight': highlightedIndex === index }]"
>
<div class="log-timestamp">{{ log.timestamp }}</div>
<div class="log-level">{{ log.level.toUpperCase() }}</div>
<div v-if="log.component" class="log-component">[{{ log.component }}]</div>
<div class="log-message">{{ log.message }}</div>
<div v-if="log.data" class="log-data">
<a-button size="small" type="link" @click="toggleDataVisibility(index)">
{{ expandedData.has(index) ? '隐藏数据' : '显示数据' }}
</a-button>
<pre v-if="expandedData.has(index)" class="log-data-content">{{
JSON.stringify(log.data, null, 2)
}}</pre>
<a-button @click="openLogDirectory">
<template #icon>
<FolderOpenOutlined />
</template>
打开日志目录
</a-button>
</a-space>
</div>
<div class="log-info">
<a-space>
<span>日志文件: {{ logPath }}</span>
<span>总行数: {{ totalLines }}</span>
</a-space>
</div>
<div class="log-content">
<a-textarea v-model:value="logs" :rows="25" readonly class="log-textarea" placeholder="暂无日志内容" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import {
DeleteOutlined,
DownloadOutlined,
VerticalAlignBottomOutlined,
ReloadOutlined,
DeleteOutlined,
ClearOutlined,
FolderOpenOutlined
} from '@ant-design/icons-vue'
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
import { logger } from '@/utils/logger'
const logContainer = ref<HTMLElement>()
const selectedLevel = ref<LogLevel | 'all'>('all')
const searchText = ref('')
const autoScroll = ref(true)
const expandedData = ref(new Set<number>())
const highlightedIndex = ref(-1)
const logs = ref('')
const logPath = ref('')
const logLines = ref(500)
const totalLines = ref(0)
const loading = ref(false)
const clearing = ref(false)
const cleaning = ref(false)
const logs = logger.getLogs()
// 刷新日志
const refreshLogs = async () => {
loading.value = true
try {
const logContent = await logger.getLogs(logLines.value || undefined)
logs.value = logContent
totalLines.value = logContent.split('\n').filter(line => line.trim()).length
const filteredLogs = computed(() => {
let filtered = logs.value
// 按级别过滤
if (selectedLevel.value !== 'all') {
filtered = filtered.filter(log => log.level === selectedLevel.value)
}
// 按搜索文本过滤
if (searchText.value) {
const search = searchText.value.toLowerCase()
filtered = filtered.filter(
log =>
log.message.toLowerCase().includes(search) ||
log.component?.toLowerCase().includes(search) ||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
)
}
return filtered
})
function filterLogs() {
// 过滤逻辑已在computed中处理
nextTick(() => {
if (autoScroll.value) {
scrollToBottom()
// 自动滚动到底部
setTimeout(() => {
const textarea = document.querySelector('.log-textarea textarea') as HTMLTextAreaElement
if (textarea) {
textarea.scrollTop = textarea.scrollHeight
}
}, 100)
} catch (error) {
message.error('获取日志失败: ' + error)
logger.error('获取日志失败:', error)
} finally {
loading.value = false
}
})
}
function clearLogs() {
logger.clearLogs()
expandedData.value.clear()
// 清空日志
const clearLogs = async () => {
clearing.value = true
try {
await logger.clearLogs()
logs.value = ''
totalLines.value = 0
message.success('日志已清空')
} catch (error) {
message.error('清空日志失败: ' + error)
logger.error('清空日志失败:', error)
} finally {
clearing.value = false
}
}
function downloadLogs() {
logger.downloadLogs()
// 清理旧日志
const cleanOldLogs = async () => {
cleaning.value = true
try {
await logger.cleanOldLogs(7)
message.success('已清理7天前的旧日志文件')
} catch (error) {
message.error('清理旧日志失败: ' + error)
logger.error('清理旧日志失败:', error)
} finally {
cleaning.value = false
}
}
function toggleAutoScroll() {
autoScroll.value = !autoScroll.value
if (autoScroll.value) {
scrollToBottom()
}
// 打开日志目录
const openLogDirectory = async () => {
try {
const path = await logger.getLogPath()
// 获取日志目录路径
const logDir = path.substring(0, path.lastIndexOf('\\') || path.lastIndexOf('/'))
if (window.electronAPI?.openUrl) {
await window.electronAPI.openUrl(`file://${logDir}`)
}
} catch (error) {
message.error('打开日志目录失败: ' + error)
logger.error('打开日志目录失败:', error)
}
}
function toggleDataVisibility(index: number) {
if (expandedData.value.has(index)) {
expandedData.value.delete(index)
} else {
expandedData.value.add(index)
}
// 获取日志文件路径
const getLogPath = async () => {
try {
logPath.value = await logger.getLogPath()
} catch (error) {
logger.error('获取日志路径失败:', error)
}
}
function scrollToBottom() {
if (logContainer.value) {
logContainer.value.scrollTop = logContainer.value.scrollHeight
}
}
function handleScroll() {
if (!logContainer.value) return
const { scrollTop, scrollHeight, clientHeight } = logContainer.value
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
if (!isAtBottom) {
autoScroll.value = false
}
}
// 监听新日志添加
let unwatchLogs: (() => void) | null = null
onMounted(() => {
// 初始滚动到底部
nextTick(() => {
scrollToBottom()
})
// 监听日志变化
unwatchLogs =
logs.value && typeof logs.value === 'object' && 'length' in logs.value
? () => {} // 如果logs是响应式的Vue会自动处理
: null
})
onUnmounted(() => {
if (unwatchLogs) {
unwatchLogs()
}
})
// 监听日志变化,自动滚动
const prevLogsLength = ref(logs.value.length)
const checkForNewLogs = () => {
if (logs.value.length > prevLogsLength.value) {
prevLogsLength.value = logs.value.length
if (autoScroll.value) {
nextTick(() => {
scrollToBottom()
})
}
}
}
// 定期检查新日志
const logCheckInterval = setInterval(checkForNewLogs, 100)
onUnmounted(() => {
clearInterval(logCheckInterval)
getLogPath()
refreshLogs()
})
</script>
<style scoped>
.log-viewer {
height: 100%;
display: flex;
flex-direction: column;
background: var(--ant-color-bg-container);
border-radius: 8px;
overflow: hidden;
}
.log-header {
padding: 16px;
border-bottom: 1px solid var(--ant-color-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
padding: 16px;
}
.log-controls {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.log-stats {
font-size: 14px;
color: var(--ant-color-text-secondary);
.log-info {
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.log-container {
flex: 1;
overflow-y: auto;
padding: 8px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
.log-content {
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.log-entry {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
margin-bottom: 2px;
word-break: break-all;
.log-textarea :deep(.ant-input) {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
border: none;
resize: none;
}
.log-entry:hover {
}
.log-highlight {
background: var(--ant-color-primary-bg) !important;
}
.log-timestamp {
color: var(--ant-color-text-tertiary);
white-space: nowrap;
min-width: 140px;
}
.log-level {
font-weight: bold;
min-width: 50px;
text-align: center;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
}
.log-debug .log-level {
background: var(--ant-color-fill-secondary);
color: var(--ant-color-text-secondary);
}
.log-info .log-level {
background: var(--ant-color-info-bg);
color: var(--ant-color-info);
}
.log-warn .log-level {
background: var(--ant-color-warning-bg);
color: var(--ant-color-warning);
}
.log-error .log-level {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.log-component {
color: var(--ant-color-primary);
font-weight: 500;
white-space: nowrap;
}
.log-message {
flex: 1;
color: var(--ant-color-text);
}
.log-data {
margin-top: 4px;
width: 100%;
}
.log-data-content {
padding: 8px;
border-radius: 4px;
margin-top: 4px;
font-size: 11px;
overflow-x: auto;
}
@media (max-width: 768px) {
.log-header {
flex-direction: column;
align-items: stretch;
}
.log-controls {
justify-content: center;
}
.log-entry {
flex-direction: column;
align-items: stretch;
gap: 4px;
}
.log-timestamp,
.log-level,
.log-component {
min-width: auto;
}
.log-textarea :deep(.ant-input:focus) {
box-shadow: none;
}
</style>

View File

@@ -13,15 +13,13 @@
</template>
<script setup lang="ts">
import { createComponentLogger } from '@/utils/logger'
const logger = createComponentLogger('AdminCheck')
async function handleRestartAsAdmin() {
try {
await window.electronAPI.restartAsAdmin()
} catch (error) {
logger.error('重启为管理员失败', error)
console.error('重启为管理员失败', error)
}
}
</script>

View File

@@ -39,12 +39,11 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { createComponentLogger } from '@/utils/logger'
import { getConfig } from '@/utils/config'
import { getMirrorUrl } from '@/config/mirrors'
import router from '@/router'
const logger = createComponentLogger('AutoMode')
// Props
interface Props {
@@ -120,7 +119,7 @@ async function startAutoProcess() {
// 如果初始化时的镜像源不通,让用户重新选择
if (!pipResult.success) {
logger.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
console.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
// 切换到手动模式让用户重新选择镜像源
progressText.value = '依赖安装失败,需要重新配置镜像源'
@@ -142,14 +141,14 @@ async function startAutoProcess() {
progress.value = 100
progressStatus.value = 'success'
logger.info('自动启动流程完成,即将进入应用')
console.log('自动启动流程完成,即将进入应用')
// 延迟0.5秒后自动进入应用
setTimeout(() => {
props.onAutoComplete()
}, 500)
} catch (error) {
logger.error('自动启动流程失败', error)
console.error('自动启动流程失败', error)
progressText.value = `自动启动失败: ${error instanceof Error ? error.message : String(error)}`
progressStatus.value = 'exception'
@@ -169,7 +168,7 @@ async function checkGitUpdate(): Promise<boolean> {
const result = await window.electronAPI.checkGitUpdate()
return result.hasUpdate || false
} catch (error) {
logger.warn('检查Git更新失败:', error)
console.warn('检查Git更新失败:', error)
// 如果检查失败,假设有更新,这样会触发代码拉取和依赖安装
return true
}

View File

@@ -111,7 +111,6 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { notification } from 'ant-design-vue'
import { createComponentLogger } from '@/utils/logger'
import { saveConfig } from '@/utils/config'
import ThemeStep from './ThemeStep.vue'
import PythonStep from './PythonStep.vue'
@@ -121,7 +120,7 @@ import BackendStep from './BackendStep.vue'
import DependenciesStep from './DependenciesStep.vue'
import ServiceStep from './ServiceStep.vue'
const logger = createComponentLogger('ManualMode')
// Props
interface Props {
@@ -284,26 +283,26 @@ async function autoStartSpeedTest() {
// 安装函数
async function installPython() {
logger.info('开始安装Python')
console.log('开始安装Python')
const mirror = pythonStepRef.value?.selectedPythonMirror || 'tsinghua'
const result = await window.electronAPI.downloadPython(mirror)
if (result.success) {
logger.info('Python安装成功')
console.log('Python安装成功')
await saveConfig({ pythonInstalled: true })
} else {
logger.error('Python安装失败', result.error)
console.error('Python安装失败', result.error)
throw new Error(result.error)
}
}
async function installGit() {
logger.info('开始安装Git工具')
console.log('开始安装Git工具')
const result = await window.electronAPI.downloadGit()
if (result.success) {
logger.info('Git工具安装成功')
console.log('Git工具安装成功')
await saveConfig({ gitInstalled: true })
} else {
logger.error('Git工具安装失败', result.error)
console.error('Git工具安装失败', result.error)
throw new Error(result.error)
}
}
@@ -311,13 +310,13 @@ async function installGit() {
async function cloneBackend() {
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
logger.info('开始克隆后端代码', { mirror: mirror?.name, url: mirror?.url })
console.log('开始克隆后端代码', { mirror: mirror?.name, url: mirror?.url })
const result = await window.electronAPI.cloneBackend(mirror?.url)
if (result.success) {
logger.info('后端代码克隆成功')
console.log('后端代码克隆成功')
await saveConfig({ backendExists: true })
} else {
logger.error('后端代码克隆失败', result.error)
console.error('后端代码克隆失败', result.error)
throw new Error(result.error)
}
}
@@ -325,31 +324,31 @@ async function cloneBackend() {
async function updateBackend() {
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
logger.info('开始更新后端代码', { mirror: mirror?.name, url: mirror?.url })
console.log('开始更新后端代码', { mirror: mirror?.name, url: mirror?.url })
const result = await window.electronAPI.updateBackend(mirror?.url)
if (!result.success) {
logger.error('后端代码更新失败', result.error)
console.error('后端代码更新失败', result.error)
throw new Error(result.error)
}
logger.info('后端代码更新成功')
console.log('后端代码更新成功')
}
async function installDependencies() {
logger.info('开始安装Python依赖')
console.log('开始安装Python依赖')
const mirror = dependenciesStepRef.value?.selectedPipMirror || 'tsinghua'
const result = await window.electronAPI.installDependencies(mirror)
if (result.success) {
logger.info('Python依赖安装成功')
console.log('Python依赖安装成功')
await saveConfig({ dependenciesInstalled: true })
} else {
logger.error('Python依赖安装失败', result.error)
console.error('Python依赖安装失败', result.error)
throw new Error(result.error)
}
}
// 自动启动后端服务(进入第七步时调用)
async function autoStartBackendService() {
logger.info('自动启动后端服务')
console.log('自动启动后端服务')
isProcessing.value = true
errorMessage.value = ''
@@ -367,14 +366,14 @@ async function autoStartBackendService() {
serviceStepRef.value.serviceStatus = '后端服务启动成功,即将进入主页...'
}
stepStatus.value = 'finish'
logger.info('后端服务自动启动成功延迟1秒后自动进入主页')
console.log('后端服务自动启动成功延迟1秒后自动进入主页')
// 延迟1秒后自动进入主页
setTimeout(() => {
handleEnterApp()
}, 1000)
} else {
logger.error('后端服务自动启动失败', result.error)
console.error('后端服务自动启动失败', result.error)
if (serviceStepRef.value) {
serviceStepRef.value.serviceStatus = '后端服务启动失败,请点击重新启动'
}
@@ -384,7 +383,7 @@ async function autoStartBackendService() {
if (serviceStepRef.value) {
serviceStepRef.value.serviceStatus = '后端服务启动失败,请点击重新启动'
}
logger.error('后端服务自动启动异常', error)
console.error('后端服务自动启动异常', error)
errorMessage.value = error instanceof Error ? error.message : String(error)
} finally {
if (serviceStepRef.value) {
@@ -396,7 +395,7 @@ async function autoStartBackendService() {
// 手动启动后端服务(用户点击按钮时调用)
async function startBackendService() {
logger.info('手动重新启动后端服务')
console.log('手动重新启动后端服务')
if (serviceStepRef.value) {
serviceStepRef.value.startingService = true
@@ -412,21 +411,21 @@ async function startBackendService() {
serviceStepRef.value.serviceStatus = '后端服务启动成功,即将进入主页...'
}
stepStatus.value = 'finish'
logger.info('后端服务手动启动成功延迟1秒后自动进入主页')
console.log('后端服务手动启动成功延迟1秒后自动进入主页')
// 延迟1秒后自动进入主页
setTimeout(() => {
handleEnterApp()
}, 1000)
} else {
logger.error('后端服务手动启动失败', result.error)
console.error('后端服务手动启动失败', result.error)
throw new Error(result.error)
}
} catch (error) {
if (serviceStepRef.value) {
serviceStepRef.value.serviceStatus = '后端服务启动失败'
}
logger.error('后端服务手动启动异常', error)
console.error('后端服务手动启动异常', error)
throw error
} finally {
if (serviceStepRef.value) {

View File

@@ -2,7 +2,6 @@ import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index.ts'
import { OpenAPI } from '@/api'
import LoggerPlugin, { logger } from '@/utils/logger'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
@@ -10,6 +9,9 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
// 导入日志系统
import { logger } from '@/utils/logger'
// 配置dayjs中文本地化
dayjs.locale('zh-cn')
@@ -18,16 +20,23 @@ import { API_ENDPOINTS } from '@/config/mirrors'
// 配置API基础URL
OpenAPI.BASE = API_ENDPOINTS.local
// 记录应用启动
logger.info('前端应用开始初始化')
logger.info(`API基础URL: ${OpenAPI.BASE}`)
// 创建应用实例
const app = createApp(App)
// 注册插件
app.use(Antd)
app.use(router)
app.use(LoggerPlugin)
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
logger.error('Vue应用错误:', err, '组件信息:', info)
}
// 挂载应用
app.mount('#app')
// 记录应用启动日志
logger.info('应用启动', { version: '1.0.0', environment: process.env.NODE_ENV })
logger.info('前端应用初始化完成')

View File

@@ -76,12 +76,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('../views/Settings.vue'),
meta: { title: '设置' },
},
{
path: '/logs',
name: 'Logs',
component: () => import('../views/Logs.vue'),
meta: { title: '系统日志' },
},
]
const router = createRouter({

View File

@@ -1,28 +1,38 @@
export interface ElectronAPI {
openDevTools: () => Promise<void>
selectFolder: () => Promise<string | null>
selectFile: (filters?: any[]) => Promise<string | null>
selectFile: (filters?: any[]) => Promise<string[]>
openUrl: (url: string) => Promise<{ success: boolean; error?: string }>
// 初始化相关API
checkEnvironment: () => Promise<{
pythonExists: boolean
gitExists: boolean
backendExists: boolean
dependenciesInstalled: boolean
isInitialized: boolean
}>
downloadPython: (mirror?: string) => Promise<{ success: boolean; error?: string }>
downloadGit: () => Promise<{ success: boolean; error?: string }>
installDependencies: (mirror?: string) => Promise<{ success: boolean; error?: string }>
cloneBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
updateBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
startBackend: () => Promise<{ success: boolean; error?: string }>
checkEnvironment: () => Promise<any>
downloadPython: (mirror?: string) => Promise<any>
installPip: () => Promise<any>
downloadGit: () => Promise<any>
installDependencies: (mirror?: string) => Promise<any>
cloneBackend: (repoUrl?: string) => Promise<any>
updateBackend: (repoUrl?: string) => Promise<any>
startBackend: () => Promise<any>
// 管理员权限相关
checkAdmin: () => Promise<boolean>
restartAsAdmin: () => Promise<void>
// 配置文件操作
saveConfig: (config: any) => Promise<void>
loadConfig: () => Promise<any>
resetConfig: () => Promise<void>
// 日志文件操作
getLogPath: () => Promise<string>
getLogs: (lines?: number) => Promise<string>
clearLogs: () => Promise<void>
cleanOldLogs: (daysToKeep?: number) => Promise<void>
// 保留原有方法以兼容现有代码
saveLogsToFile: (logs: string) => Promise<void>
loadLogsFromFile: () => Promise<string | null>
// 监听下载进度
onDownloadProgress: (callback: (progress: any) => void) => void
removeDownloadProgressListener: () => void

View File

@@ -1,8 +1,5 @@
import { createComponentLogger } from './logger'
import type { ThemeMode, ThemeColor } from '@/composables/useTheme'
const logger = createComponentLogger('Config')
export interface FrontendConfig {
// 基础配置
isFirstLaunch: boolean
@@ -71,7 +68,6 @@ async function getConfigInternal(): Promise<FrontendConfig> {
return config
} catch (error) {
console.error('读取配置失败:', error)
logger.error('读取配置失败', error)
return { ...DEFAULT_CONFIG }
}
}
@@ -107,10 +103,8 @@ export async function saveConfig(config: Partial<FrontendConfig>): Promise<void>
console.log('合并后的配置:', newConfig)
await window.electronAPI.saveConfig(newConfig)
console.log('配置保存成功')
logger.info('配置已保存', newConfig)
} catch (error) {
console.error('保存配置失败:', error)
logger.error('保存配置失败', error)
throw error
}
}
@@ -122,9 +116,8 @@ export async function resetConfig(): Promise<void> {
localStorage.removeItem('app-config')
localStorage.removeItem('theme-settings')
localStorage.removeItem('app-initialized')
logger.info('配置已重置')
} catch (error) {
logger.error('重置配置失败', error)
console.error('重置配置失败:', error)
}
}

View File

@@ -1,171 +1,75 @@
import { ref } from 'vue'
// 渲染进程日志工具
interface ElectronAPI {
getLogPath: () => Promise<string>
getLogs: (lines?: number) => Promise<string>
clearLogs: () => Promise<void>
cleanOldLogs: (daysToKeep?: number) => Promise<void>
}
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
declare global {
interface Window {
electronAPI: ElectronAPI
}
}
export interface LogEntry {
timestamp: string
level: LogLevel
message: string
data?: any
component?: string
export enum LogLevel {
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR'
}
class Logger {
private logs = ref<LogEntry[]>([])
private maxLogs = 1000 // 最大日志条数
private logToConsole = true
private logToStorage = true
constructor() {
// 延迟加载日志等待electron API准备就绪
setTimeout(() => {
this.loadLogsFromStorage()
}, 100)
// 直接使用原生console主进程会自动处理日志记录
debug(message: string, ...args: any[]) {
console.debug(message, ...args)
}
private formatTimestamp(): string {
const now = new Date()
return now.toISOString().replace('T', ' ').substring(0, 19)
info(message: string, ...args: any[]) {
console.info(message, ...args)
}
private addLog(level: LogLevel, message: string, data?: any, component?: string) {
const logEntry: LogEntry = {
timestamp: this.formatTimestamp(),
level,
message,
data,
component,
warn(message: string, ...args: any[]) {
console.warn(message, ...args)
}
error(message: string, ...args: any[]) {
console.error(message, ...args)
}
// 获取日志文件路径
async getLogPath(): Promise<string> {
if (window.electronAPI) {
return await window.electronAPI.getLogPath()
}
throw new Error('Electron API not available')
}
// 添加到内存日志
this.logs.value.push(logEntry)
// 限制日志数量
if (this.logs.value.length > this.maxLogs) {
this.logs.value.shift()
// 获取日志内容
async getLogs(lines?: number): Promise<string> {
if (window.electronAPI) {
return await window.electronAPI.getLogs(lines)
}
// 输出到控制台
if (this.logToConsole) {
const consoleMessage = `[${logEntry.timestamp}] [${level.toUpperCase()}] ${component ? `[${component}] ` : ''}${message}`
switch (level) {
case 'debug':
console.debug(consoleMessage, data)
break
case 'info':
console.info(consoleMessage, data)
break
case 'warn':
console.warn(consoleMessage, data)
break
case 'error':
console.error(consoleMessage, data)
break
}
}
// 保存到本地存储
if (this.logToStorage) {
this.saveLogsToStorage()
}
}
private async saveLogsToStorage() {
try {
if (window.electronAPI && window.electronAPI.saveLogsToFile) {
const logsToSave = this.logs.value.slice(-500) // 只保存最近500条日志
await window.electronAPI.saveLogsToFile(JSON.stringify(logsToSave, null, 2))
}
} catch (error) {
console.error('保存日志到本地文件失败:', error)
}
}
private async loadLogsFromStorage() {
try {
if (window.electronAPI && window.electronAPI.loadLogsFromFile) {
const savedLogs = await window.electronAPI.loadLogsFromFile()
if (savedLogs) {
const parsedLogs = JSON.parse(savedLogs) as LogEntry[]
this.logs.value = parsedLogs
}
}
} catch (error) {
console.error('从本地文件加载日志失败:', error)
}
}
// 公共方法
debug(message: string, data?: any, component?: string) {
this.addLog('debug', message, data, component)
}
info(message: string, data?: any, component?: string) {
this.addLog('info', message, data, component)
}
warn(message: string, data?: any, component?: string) {
this.addLog('warn', message, data, component)
}
error(message: string, data?: any, component?: string) {
this.addLog('error', message, data, component)
}
// 获取日志
getLogs() {
return this.logs
throw new Error('Electron API not available')
}
// 清空日志
clearLogs() {
this.logs.value = []
localStorage.removeItem('app-logs')
async clearLogs(): Promise<void> {
if (window.electronAPI) {
await window.electronAPI.clearLogs()
console.info('日志已清空')
} else {
throw new Error('Electron API not available')
}
}
// 导出日志到文件
exportLogs(): string {
const logText = this.logs.value
.map(log => {
const dataStr = log.data ? ` | Data: ${JSON.stringify(log.data)}` : ''
const componentStr = log.component ? ` | Component: ${log.component}` : ''
return `[${log.timestamp}] [${log.level.toUpperCase()}]${componentStr} ${log.message}${dataStr}`
})
.join('\n')
return logText
}
// 下载日志文件
downloadLogs() {
const logText = this.exportLogs()
const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `auto-maa-logs-${new Date().toISOString().split('T')[0]}.txt`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
// 配置选项
setLogToConsole(enabled: boolean) {
this.logToConsole = enabled
}
setLogToStorage(enabled: boolean) {
this.logToStorage = enabled
}
setMaxLogs(max: number) {
this.maxLogs = max
if (this.logs.value.length > max) {
this.logs.value = this.logs.value.slice(-max)
// 清理旧日志
async cleanOldLogs(daysToKeep: number = 7): Promise<void> {
if (window.electronAPI) {
await window.electronAPI.cleanOldLogs(daysToKeep)
console.info(`已清理${daysToKeep}天前的旧日志`)
} else {
throw new Error('Electron API not available')
}
}
}
@@ -173,20 +77,13 @@ class Logger {
// 创建全局日志实例
export const logger = new Logger()
// 创建组件专用的日志器
export function createComponentLogger(componentName: string) {
return {
debug: (message: string, data?: any) => logger.debug(message, data, componentName),
info: (message: string, data?: any) => logger.info(message, data, componentName),
warn: (message: string, data?: any) => logger.warn(message, data, componentName),
error: (message: string, data?: any) => logger.error(message, data, componentName),
}
}
// 捕获未处理的错误直接使用console主进程会处理日志记录
window.addEventListener('error', (event) => {
console.error('未处理的错误:', event.error?.message || event.message, event.error?.stack)
})
// Vue插件
export default {
install(app: any) {
app.config.globalProperties.$logger = logger
app.provide('logger', logger)
},
}
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason)
})
export default logger

View File

@@ -29,7 +29,6 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { createComponentLogger } from '@/utils/logger'
import { getConfig, saveConfig, setInitialized } from '@/utils/config'
import AdminCheck from '@/components/initialization/AdminCheck.vue'
import AutoMode from '@/components/initialization/AutoMode.vue'
@@ -37,7 +36,6 @@ import ManualMode from '@/components/initialization/ManualMode.vue'
import type { DownloadProgress } from '@/types/initialization'
const router = useRouter()
const logger = createComponentLogger('Initialization')
// 基础状态
const isAdmin = ref(true)
@@ -77,7 +75,6 @@ async function enterApp() {
// 检查关键文件是否存在
async function checkCriticalFiles() {
try {
logger.info('开始检查关键文件存在性')
console.log('🔍 正在调用 window.electronAPI.checkCriticalFiles()...')
// 检查API是否存在
@@ -110,7 +107,6 @@ async function checkCriticalFiles() {
console.log('🔍 最终返回结果:', result)
return result
} catch (error) {
logger.error('检查关键文件失败', error)
console.error('❌ 检查关键文件失败,使用配置文件状态:', error)
// 如果检查失败,从配置文件读取状态
@@ -140,7 +136,6 @@ async function checkCriticalFiles() {
// 检查环境状态
async function checkEnvironment() {
try {
logger.info('开始检查环境状态')
// 只检查关键exe文件是否存在
const criticalFiles = await checkCriticalFiles()
@@ -186,11 +181,9 @@ async function checkEnvironment() {
// 只有在非首次启动、配置显示已初始化、且所有关键exe文件都存在时才进入自动模式
if (!isFirst && config.init && allExeFilesExist) {
logger.info('非首次启动、配置显示已初始化且所有关键文件存在,进入自动模式')
console.log('进入自动模式,开始自动启动流程')
autoMode.value = true
} else {
logger.info('需要进入手动模式进行配置')
console.log('进入手动模式')
console.log(
'原因: isFirst =',
@@ -209,7 +202,6 @@ async function checkEnvironment() {
}
} catch (error) {
const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}`
logger.error('环境检查失败', error)
console.error('环境检查失败:', error)
// 检查失败时强制进入手动模式
@@ -224,7 +216,7 @@ async function checkAdminPermission() {
isAdmin.value = adminStatus
console.log('管理员权限检查结果:', adminStatus)
} catch (error) {
logger.error('检查管理员权限失败', error)
console.error('检查管理员权限失败:', error)
isAdmin.value = false
}
}

View File

@@ -1,55 +0,0 @@
<template>
<div class="logs-page">
<div class="page-header">
<h2>系统日志</h2>
<p>查看应用运行日志支持搜索过滤和导出功能</p>
</div>
<div class="logs-content">
<LogViewer />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import LogViewer from '@/components/LogViewer.vue'
import { createComponentLogger } from '@/utils/logger'
const logger = createComponentLogger('LogsPage')
onMounted(() => {
logger.info('进入日志查看页面')
})
</script>
<style scoped>
.logs-page {
height: 100%;
display: flex;
flex-direction: column;
padding: 24px;
}
.page-header {
margin-bottom: 24px;
}
.page-header h2 {
margin: 0 0 8px 0;
color: var(--ant-color-text);
font-size: 24px;
font-weight: 600;
}
.page-header p {
margin: 0;
color: var(--ant-color-text-secondary);
font-size: 14px;
}
.logs-content {
flex: 1;
min-height: 0;
}
</style>

View File

@@ -687,11 +687,7 @@ onMounted(() => {
<Divider />
<div class="setting-item">
<h4>系统日志</h4>
<p class="setting-description">查看应用运行日志用于问题排查和调试</p>
<Button type="default" @click="$router.push('/logs')">查看系统日志</Button>
</div>
</Space>
</Card>
</Tabs.TabPane>