style:格式化代码

This commit is contained in:
2025-08-15 14:21:16 +08:00
parent 9f849608db
commit a738f102a6
40 changed files with 1144 additions and 922 deletions

View File

@@ -4,7 +4,13 @@ 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 } from './services/pythonService'
import {
setMainWindow as setPythonMainWindow,
downloadPython,
installPipPackage,
installDependencies,
startBackend,
} from './services/pythonService'
import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService'
// 检查是否以管理员权限运行
@@ -32,16 +38,20 @@ 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'
})
spawn(
'powershell',
[
'-Command',
`Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs`,
],
{
detached: true,
stdio: 'ignore',
}
)
app.quit()
}
}
@@ -144,15 +154,21 @@ ipcMain.handle('download-git', async () => {
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(
'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(
'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) => {
@@ -271,4 +287,4 @@ app.on('window-all-closed', () => {
app.on('activate', () => {
if (mainWindow === null) createWindow()
})
})

View File

@@ -1,43 +1,43 @@
import { contextBridge, ipcRenderer } from 'electron'
window.addEventListener('DOMContentLoaded', () => {
console.log('Preload loaded')
console.log('Preload loaded')
})
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
selectFolder: () => ipcRenderer.invoke('select-folder'),
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters),
// 初始化相关API
checkEnvironment: () => ipcRenderer.invoke('check-environment'),
downloadPython: (mirror?: string) => ipcRenderer.invoke('download-python', mirror),
installPip: () => ipcRenderer.invoke('install-pip'),
downloadGit: () => ipcRenderer.invoke('download-git'),
installDependencies: (mirror?: string) => ipcRenderer.invoke('install-dependencies', mirror),
cloneBackend: (repoUrl?: string) => ipcRenderer.invoke('clone-backend', repoUrl),
updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl),
startBackend: () => ipcRenderer.invoke('start-backend'),
// 管理员权限相关
checkAdmin: () => ipcRenderer.invoke('check-admin'),
restartAsAdmin: () => ipcRenderer.invoke('restart-as-admin'),
// 配置文件操作
saveConfig: (config: any) => ipcRenderer.invoke('save-config', config),
loadConfig: () => ipcRenderer.invoke('load-config'),
resetConfig: () => ipcRenderer.invoke('reset-config'),
// 日志文件操作
saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs),
loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'),
// 监听下载进度
onDownloadProgress: (callback: (progress: any) => void) => {
ipcRenderer.on('download-progress', (_, progress) => callback(progress))
},
removeDownloadProgressListener: () => {
ipcRenderer.removeAllListeners('download-progress')
}
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
selectFolder: () => ipcRenderer.invoke('select-folder'),
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters),
// 初始化相关API
checkEnvironment: () => ipcRenderer.invoke('check-environment'),
downloadPython: (mirror?: string) => ipcRenderer.invoke('download-python', mirror),
installPip: () => ipcRenderer.invoke('install-pip'),
downloadGit: () => ipcRenderer.invoke('download-git'),
installDependencies: (mirror?: string) => ipcRenderer.invoke('install-dependencies', mirror),
cloneBackend: (repoUrl?: string) => ipcRenderer.invoke('clone-backend', repoUrl),
updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl),
startBackend: () => ipcRenderer.invoke('start-backend'),
// 管理员权限相关
checkAdmin: () => ipcRenderer.invoke('check-admin'),
restartAsAdmin: () => ipcRenderer.invoke('restart-as-admin'),
// 配置文件操作
saveConfig: (config: any) => ipcRenderer.invoke('save-config', config),
loadConfig: () => ipcRenderer.invoke('load-config'),
resetConfig: () => ipcRenderer.invoke('reset-config'),
// 日志文件操作
saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs),
loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'),
// 监听下载进度
onDownloadProgress: (callback: (progress: any) => void) => {
ipcRenderer.on('download-progress', (_, progress) => callback(progress))
},
removeDownloadProgressListener: () => {
ipcRenderer.removeAllListeners('download-progress')
},
})

View File

@@ -18,7 +18,6 @@ export function downloadFile(url: string, outputPath: string): Promise<void> {
// 创建HTTP客户端兼容https和http
const client = url.startsWith('https') ? https : http
client
.get(url, response => {
const totalSize = parseInt(response.headers['content-length'] || '0', 10)

View File

@@ -4,9 +4,7 @@ import { app } from 'electron'
// 获取应用根目录
export function getAppRoot(): string {
return process.env.NODE_ENV === 'development'
? process.cwd()
: path.dirname(app.getPath('exe'))
return process.env.NODE_ENV === 'development' ? process.cwd() : path.dirname(app.getPath('exe'))
}
// 检查环境
@@ -20,16 +18,17 @@ export function checkEnvironment(appRoot: string) {
const pythonExists = fs.existsSync(pythonPath)
const gitExists = fs.existsSync(gitPath)
const backendExists = fs.existsSync(backendPath)
// 检查依赖是否已安装简单检查是否存在site-packages目录
const sitePackagesPath = path.join(pythonPath, 'Lib', 'site-packages')
const dependenciesInstalled = fs.existsSync(sitePackagesPath) && fs.readdirSync(sitePackagesPath).length > 10
const dependenciesInstalled =
fs.existsSync(sitePackagesPath) && fs.readdirSync(sitePackagesPath).length > 10
return {
pythonExists,
gitExists,
backendExists,
dependenciesInstalled,
isInitialized: pythonExists && gitExists && backendExists && dependenciesInstalled
isInitialized: pythonExists && gitExists && backendExists && dependenciesInstalled,
}
}
}

View File

@@ -31,7 +31,6 @@ function copyDirSync(src: string, dest: string) {
}
}
// 获取Git环境变量配置
function getGitEnvironment(appRoot: string) {
const gitDir = path.join(appRoot, 'environment', 'git')
@@ -249,11 +248,26 @@ export async function cloneBackend(
})
}
await new Promise<void>((resolve, reject) => {
const proc = spawn(gitPath, ['clone', '--progress', '--verbose','--single-branch','--depth','1','--branch', 'feature/refactor-backend', repoUrl, tmpDir], {
stdio: 'pipe',
env: gitEnv,
cwd: appRoot,
})
const proc = spawn(
gitPath,
[
'clone',
'--progress',
'--verbose',
'--single-branch',
'--depth',
'1',
'--branch',
'feature/refactor-backend',
repoUrl,
tmpDir,
],
{
stdio: 'pipe',
env: gitEnv,
cwd: appRoot,
}
)
proc.stdout?.on('data', d => console.log('git clone:', d.toString()))
proc.stderr?.on('data', d => console.log('git clone err:', d.toString()))
proc.on('close', code =>

View File

@@ -16,8 +16,9 @@ const pythonMirrorUrls = {
official: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip',
tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
ustc: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
huawei: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip'
huawei:
'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip',
}
// 检查pip是否已安装
@@ -25,20 +26,20 @@ function isPipInstalled(pythonPath: string): boolean {
const scriptsPath = path.join(pythonPath, 'Scripts')
const pipExePath = path.join(scriptsPath, 'pip.exe')
const pip3ExePath = path.join(scriptsPath, 'pip3.exe')
console.log(`检查pip安装状态:`)
console.log(`Scripts目录: ${scriptsPath}`)
console.log(`pip.exe路径: ${pipExePath}`)
console.log(`pip3.exe路径: ${pip3ExePath}`)
const scriptsExists = fs.existsSync(scriptsPath)
const pipExists = fs.existsSync(pipExePath)
const pip3Exists = fs.existsSync(pip3ExePath)
console.log(`Scripts目录存在: ${scriptsExists}`)
console.log(`pip.exe存在: ${pipExists}`)
console.log(`pip3.exe存在: ${pip3Exists}`)
return scriptsExists && (pipExists || pip3Exists)
}
@@ -62,7 +63,7 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
}
console.log('pip未安装开始安装...')
const getPipPath = path.join(pythonPath, 'get-pip.py')
const getPipUrl = 'http://221.236.27.82:10197/d/AUTO_MAA/get-pip.py'
@@ -75,12 +76,13 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
try {
await downloadFile(getPipUrl, getPipPath)
console.log('get-pip.py下载完成')
// 检查下载的文件大小
const stats = fs.statSync(getPipPath)
console.log(`get-pip.py文件大小: ${stats.size} bytes`)
if (stats.size < 10000) { // 如果文件小于10KB可能是无效文件
if (stats.size < 10000) {
// 如果文件小于10KB可能是无效文件
throw new Error(`get-pip.py文件大小异常: ${stats.size} bytes可能下载失败`)
}
} catch (error) {
@@ -94,20 +96,20 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
const process = spawn(pythonExe, [getPipPath], {
cwd: pythonPath,
stdio: 'pipe'
stdio: 'pipe',
})
process.stdout?.on('data', (data) => {
process.stdout?.on('data', data => {
const output = data.toString()
console.log('pip安装输出:', output)
})
process.stderr?.on('data', (data) => {
process.stderr?.on('data', data => {
const errorOutput = data.toString()
console.log('pip安装错误输出:', errorOutput)
})
process.on('close', (code) => {
process.on('close', code => {
console.log(`pip安装完成退出码: ${code}`)
if (code === 0) {
console.log('pip安装成功')
@@ -117,7 +119,7 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
}
})
process.on('error', (error) => {
process.on('error', error => {
console.error('pip安装进程错误:', error)
reject(error)
})
@@ -128,20 +130,20 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
await new Promise<void>((resolve, reject) => {
const verifyProcess = spawn(pythonExe, ['-m', 'pip', '--version'], {
cwd: pythonPath,
stdio: 'pipe'
stdio: 'pipe',
})
verifyProcess.stdout?.on('data', (data) => {
verifyProcess.stdout?.on('data', data => {
const output = data.toString()
console.log('pip版本信息:', output)
})
verifyProcess.stderr?.on('data', (data) => {
verifyProcess.stderr?.on('data', data => {
const errorOutput = data.toString()
console.log('pip版本检查错误:', errorOutput)
})
verifyProcess.on('close', (code) => {
verifyProcess.on('close', code => {
if (code === 0) {
console.log('pip验证成功')
resolve()
@@ -150,7 +152,7 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
}
})
verifyProcess.on('error', (error) => {
verifyProcess.on('error', error => {
console.error('pip验证进程错误:', error)
reject(error)
})
@@ -171,11 +173,14 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
}
// 下载Python
export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<{ success: boolean; error?: string }> {
export async function downloadPython(
appRoot: string,
mirror = 'ustc'
): Promise<{ success: boolean; error?: string }> {
try {
const environmentPath = path.join(appRoot, 'environment')
const pythonPath = path.join(environmentPath, 'python')
// 确保environment目录存在
if (!fs.existsSync(environmentPath)) {
fs.mkdirSync(environmentPath, { recursive: true })
@@ -186,24 +191,30 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
type: 'python',
progress: 0,
status: 'downloading',
message: '开始下载Python...'
message: '开始下载Python...',
})
}
// 根据选择的镜像源获取下载链接
const pythonUrl = pythonMirrorUrls[mirror as keyof typeof pythonMirrorUrls] || pythonMirrorUrls.ustc
const pythonUrl =
pythonMirrorUrls[mirror as keyof typeof pythonMirrorUrls] || pythonMirrorUrls.ustc
const zipPath = path.join(environmentPath, 'python.zip')
await downloadFile(pythonUrl, zipPath)
// 检查下载的Python文件大小
const stats = fs.statSync(zipPath)
console.log(`Python压缩包大小: ${stats.size} bytes (${(stats.size / 1024 / 1024).toFixed(2)} MB)`)
console.log(
`Python压缩包大小: ${stats.size} bytes (${(stats.size / 1024 / 1024).toFixed(2)} MB)`
)
// Python 3.12.0嵌入式版本应该大约30MB如果小于5MB可能是无效文件
if (stats.size < 5 * 1024 * 1024) { // 5MB
if (stats.size < 5 * 1024 * 1024) {
// 5MB
fs.unlinkSync(zipPath) // 删除无效文件
throw new Error(`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件。请选择一个其他可用镜像源进行下载!`)
throw new Error(
`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件。请选择一个其他可用镜像源进行下载!`
)
}
if (mainWindow) {
@@ -211,19 +222,19 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
type: 'python',
progress: 100,
status: 'extracting',
message: '正在解压Python...'
message: '正在解压Python...',
})
}
// 解压Python到指定目录
console.log(`开始解压Python到: ${pythonPath}`)
// 确保Python目录存在
if (!fs.existsSync(pythonPath)) {
fs.mkdirSync(pythonPath, { recursive: true })
console.log(`创建Python目录: ${pythonPath}`)
}
const zip = new AdmZip(zipPath)
zip.extractAllTo(pythonPath, true)
console.log(`Python解压完成到: ${pythonPath}`)
@@ -241,14 +252,13 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
console.log('已启用 site-packages 支持')
}
// 安装pip
if (mainWindow) {
mainWindow.webContents.send('download-progress', {
type: 'python',
progress: 80,
status: 'installing',
message: '正在安装pip...'
message: '正在安装pip...',
})
}
@@ -259,7 +269,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
type: 'python',
progress: 100,
status: 'completed',
message: 'Python和pip安装完成'
message: 'Python和pip安装完成',
})
}
@@ -271,7 +281,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
type: 'python',
progress: 0,
status: 'error',
message: `Python下载失败: ${errorMessage}`
message: `Python下载失败: ${errorMessage}`,
})
}
return { success: false, error: errorMessage }
@@ -284,11 +294,17 @@ const pipMirrorUrls = {
tsinghua: 'https://pypi.tuna.tsinghua.edu.cn/simple/',
ustc: 'https://pypi.mirrors.ustc.edu.cn/simple/',
aliyun: 'https://mirrors.aliyun.com/pypi/simple/',
douban: 'https://pypi.douban.com/simple/'
douban: 'https://pypi.douban.com/simple/',
}
// 安装Python依赖
export async function installDependencies(appRoot: string, mirror = 'tsinghua'): Promise<{ success: boolean; error?: string }> {
export async function installDependencies(
appRoot: string,
mirror = 'tsinghua'
): Promise<{
success: boolean
error?: string
}> {
try {
const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe')
const backendPath = path.join(appRoot)
@@ -307,17 +323,18 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
type: 'dependencies',
progress: 0,
status: 'downloading',
message: '正在安装Python依赖包...'
message: '正在安装Python依赖包...',
})
}
// 获取pip镜像源URL
const pipMirrorUrl = pipMirrorUrls[mirror as keyof typeof pipMirrorUrls] || pipMirrorUrls.tsinghua
const pipMirrorUrl =
pipMirrorUrls[mirror as keyof typeof pipMirrorUrls] || pipMirrorUrls.tsinghua
// 使用Scripts文件夹中的pip.exe
const pythonDir = path.join(appRoot, 'environment', 'python')
const pipExePath = path.join(pythonDir, 'Scripts', 'pip.exe')
console.log(`开始安装Python依赖`)
console.log(`Python目录: ${pythonDir}`)
console.log(`pip.exe路径: ${pipExePath}`)
@@ -331,17 +348,24 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
// 安装依赖 - 直接使用pip.exe而不是python -m pip
await new Promise<void>((resolve, reject) => {
const process = spawn(pipExePath, [
'install',
'-r', requirementsPath,
'-i', pipMirrorUrl,
'--trusted-host', new URL(pipMirrorUrl).hostname
], {
cwd: backendPath,
stdio: 'pipe'
})
const process = spawn(
pipExePath,
[
'install',
'-r',
requirementsPath,
'-i',
pipMirrorUrl,
'--trusted-host',
new URL(pipMirrorUrl).hostname,
],
{
cwd: backendPath,
stdio: 'pipe',
}
)
process.stdout?.on('data', (data) => {
process.stdout?.on('data', data => {
const output = data.toString()
console.log('Pip output:', output)
@@ -350,17 +374,17 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
type: 'dependencies',
progress: 50,
status: 'downloading',
message: '正在安装依赖包...'
message: '正在安装依赖包...',
})
}
})
process.stderr?.on('data', (data) => {
process.stderr?.on('data', data => {
const errorOutput = data.toString()
console.error('Pip error:', errorOutput)
})
process.on('close', (code) => {
process.on('close', code => {
console.log(`pip安装完成退出码: ${code}`)
if (code === 0) {
resolve()
@@ -369,7 +393,7 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
}
})
process.on('error', (error) => {
process.on('error', error => {
console.error('pip进程错误:', error)
reject(error)
})
@@ -380,7 +404,7 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
type: 'dependencies',
progress: 100,
status: 'completed',
message: 'Python依赖安装完成'
message: 'Python依赖安装完成',
})
}
@@ -392,7 +416,7 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
type: 'dependencies',
progress: 0,
status: 'error',
message: `依赖安装失败: ${errorMessage}`
message: `依赖安装失败: ${errorMessage}`,
})
}
return { success: false, error: errorMessage }
@@ -400,10 +424,12 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'):
}
// 导出pip安装函数
export async function installPipPackage(appRoot: string): Promise<{ success: boolean; error?: string }> {
export async function installPipPackage(
appRoot: string
): Promise<{ success: boolean; error?: string }> {
try {
const pythonPath = path.join(appRoot, 'environment', 'python')
if (!fs.existsSync(pythonPath)) {
throw new Error('Python环境不存在请先安装Python')
}
@@ -413,7 +439,7 @@ export async function installPipPackage(appRoot: string): Promise<{ success: boo
type: 'pip',
progress: 0,
status: 'installing',
message: '正在安装pip...'
message: '正在安装pip...',
})
}
@@ -424,7 +450,7 @@ export async function installPipPackage(appRoot: string): Promise<{ success: boo
type: 'pip',
progress: 100,
status: 'completed',
message: 'pip安装完成'
message: 'pip安装完成',
})
}
@@ -436,7 +462,7 @@ export async function installPipPackage(appRoot: string): Promise<{ success: boo
type: 'pip',
progress: 0,
status: 'error',
message: `pip安装失败: ${errorMessage}`
message: `pip安装失败: ${errorMessage}`,
})
}
return { success: false, error: errorMessage }
@@ -448,7 +474,7 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean;
try {
const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe')
const backendPath = path.join(appRoot)
const mainPyPath = path.join(backendPath,'main.py')
const mainPyPath = path.join(backendPath, 'main.py')
// 检查文件是否存在
if (!fs.existsSync(pythonPath)) {
@@ -466,22 +492,20 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean;
stdio: 'pipe',
env: {
...process.env,
PYTHONIOENCODING: 'utf-8' // 设置Python输出编码为UTF-8
}
PYTHONIOENCODING: 'utf-8', // 设置Python输出编码为UTF-8
},
})
// 等待后端启动
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('后端启动超时'))
}, 30000) // 30秒超时
backendProcess.stdout?.on('data', (data) => {
backendProcess.stdout?.on('data', data => {
const output = data.toString()
console.log('Backend output:', output)
// 检查是否包含启动成功的标志
if (output.includes('Uvicorn running') || output.includes('8000')) {
clearTimeout(timeout)
@@ -490,7 +514,7 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean;
})
// ✅ 重要:也要监听 stderr
backendProcess.stderr?.on('data', (data) => {
backendProcess.stderr?.on('data', data => {
const output = data.toString()
console.error('Backend error:', output) // 保留原有日志
@@ -501,11 +525,11 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean;
}
})
backendProcess.stderr?.on('data', (data) => {
backendProcess.stderr?.on('data', data => {
console.error('Backend error:', data.toString())
})
backendProcess.on('error', (error) => {
backendProcess.on('error', error => {
clearTimeout(timeout)
reject(error)
})
@@ -515,4 +539,4 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean;
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
}
}

View File

@@ -57,7 +57,13 @@
</a-layout-sider>
<!-- 主内容区 -->
<a-layout :style="{ marginLeft: collapsed ? '60px' : '180px', transition: 'margin-left 0.2s', height: '100vh' }">
<a-layout
:style="{
marginLeft: collapsed ? '60px' : '180px',
transition: 'margin-left 0.2s',
height: '100vh',
}"
>
<a-layout-content
class="content-area"
:style="{
@@ -103,9 +109,7 @@ const mainMenuItems = [
{ path: '/history', label: '历史记录', icon: HistoryOutlined },
]
const bottomMenuItems = [
{ path: '/settings', label: '设置', icon: SettingOutlined },
]
const bottomMenuItems = [{ path: '/settings', label: '设置', icon: SettingOutlined }]
// 自动同步选中项
const selectedKeys = computed(() => {
@@ -142,9 +146,11 @@ const toggleCollapse = () => {
border-radius: 6px;
cursor: pointer;
}
.logo:hover {
background-color: rgba(255, 255, 255, 0.5);
}
:deep(.ant-layout-sider-light) .logo:hover {
background-color: rgba(0, 0, 0, 0.04);
}
@@ -162,6 +168,7 @@ const toggleCollapse = () => {
opacity: 1;
transition: opacity 0.2s ease;
}
.logo-text.text-hidden {
opacity: 0;
}
@@ -172,16 +179,19 @@ const toggleCollapse = () => {
overflow: auto;
/* 修复滚动条显示问题 */
scrollbar-width: thin;
scrollbar-color: rgba(0,0,0,0.2) transparent;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
.main-menu-container::-webkit-scrollbar {
width: 6px;
}
.main-menu-container::-webkit-scrollbar-track {
background: transparent;
}
.main-menu-container::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.2);
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
@@ -190,6 +200,7 @@ const toggleCollapse = () => {
margin-top: auto;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
:deep(.ant-layout-sider-light .bottom-menu) {
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
@@ -207,6 +218,7 @@ const toggleCollapse = () => {
:deep(.ant-layout-sider-dark) .menu-text {
color: #fff;
}
:deep(.ant-layout-sider-light) .logo-text,
:deep(.ant-layout-sider-light) .menu-text {
color: rgba(0, 0, 0, 0.88);
@@ -244,6 +256,7 @@ const toggleCollapse = () => {
scrollbar-width: none;
-ms-overflow-style: none;
}
.content-area::-webkit-scrollbar {
display: none;
}
@@ -279,4 +292,4 @@ const toggleCollapse = () => {
.app-layout-collapsed .ant-menu-inline-collapsed .bottom-menu {
padding-bottom: 6px;
}
</style>
</style>

View File

@@ -2,18 +2,14 @@
<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 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>
<a-input-search
v-model:value="searchText"
placeholder="搜索日志..."
@@ -21,21 +17,21 @@
@search="filterLogs"
@change="filterLogs"
/>
<a-button @click="clearLogs" danger>
<template #icon>
<DeleteOutlined />
</template>
清空日志
</a-button>
<a-button @click="downloadLogs" type="primary">
<template #icon>
<DownloadOutlined />
</template>
导出日志
</a-button>
<a-button @click="toggleAutoScroll" :type="autoScroll ? 'primary' : 'default'">
<template #icon>
<VerticalAlignBottomOutlined />
@@ -43,19 +39,13 @@
自动滚动
</a-button>
</div>
<div class="log-stats">
总计: {{ filteredLogs.length }} 条日志
</div>
<div class="log-stats">总计: {{ filteredLogs.length }} 条日志</div>
</div>
<div
ref="logContainer"
class="log-container"
@scroll="handleScroll"
>
<div
v-for="(log, index) in filteredLogs"
<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 }]"
@@ -65,14 +55,12 @@
<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)"
>
<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>
<pre v-if="expandedData.has(index)" class="log-data-content">{{
JSON.stringify(log.data, null, 2)
}}</pre>
</div>
</div>
</div>
@@ -81,10 +69,10 @@
<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
import {
DeleteOutlined,
DownloadOutlined,
VerticalAlignBottomOutlined
import {
DeleteOutlined,
DownloadOutlined,
VerticalAlignBottomOutlined,
} from '@ant-design/icons-vue'
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
@@ -108,10 +96,11 @@ const filteredLogs = computed(() => {
// 按搜索文本过滤
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))
filtered = filtered.filter(
log =>
log.message.toLowerCase().includes(search) ||
log.component?.toLowerCase().includes(search) ||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
)
}
@@ -159,10 +148,10 @@ function scrollToBottom() {
function handleScroll() {
if (!logContainer.value) return
const { scrollTop, scrollHeight, clientHeight } = logContainer.value
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
if (!isAtBottom) {
autoScroll.value = false
}
@@ -176,11 +165,12 @@ onMounted(() => {
nextTick(() => {
scrollToBottom()
})
// 监听日志变化
unwatchLogs = logs.value && typeof logs.value === 'object' && 'length' in logs.value
? () => {} // 如果logs是响应式的Vue会自动处理
: null
unwatchLogs =
logs.value && typeof logs.value === 'object' && 'length' in logs.value
? () => {} // 如果logs是响应式的Vue会自动处理
: null
})
onUnmounted(() => {
@@ -334,17 +324,17 @@ onUnmounted(() => {
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 {

View File

@@ -83,7 +83,6 @@
row-key="id"
class="user-table"
>
<template #bodyCell="{ column, record: user }">
<template v-if="column.key === 'server'">
<div class="server-cell">
@@ -102,7 +101,10 @@
<template v-if="column.key === 'lastRun'">
<div class="last-run-cell">
<div v-if="!user.Data.LastAnnihilationDate && !user.Data.LastProxyDate" class="no-run-text">
<div
v-if="!user.Data.LastAnnihilationDate && !user.Data.LastProxyDate"
class="no-run-text"
>
尚未运行
</div>
<template v-else>
@@ -118,7 +120,6 @@
</div>
</template>
<template v-if="column.key === 'userAction'">
<a-space size="small">
<a-tooltip title="编辑用户配置">

View File

@@ -6,9 +6,7 @@
sub-title="为了正常安装和配置环境请以管理员权限运行此应用"
>
<template #extra>
<a-button type="primary" @click="handleRestartAsAdmin">
重新以管理员权限启动
</a-button>
<a-button type="primary" @click="handleRestartAsAdmin"> 重新以管理员权限启动 </a-button>
</template>
</a-result>
</div>
@@ -35,4 +33,4 @@ async function handleRestartAsAdmin() {
align-items: center;
min-height: 60vh;
}
</style>
</style>

View File

@@ -78,7 +78,7 @@ function handleForceEnterConfirm() {
// 事件处理
function handleSwitchToManual() {
aborted.value = true // 设置中断
aborted.value = true // 设置中断
props.onSwitchToManual()
}
@@ -117,19 +117,19 @@ async function startAutoProcess() {
let pipMirror = config.selectedPipMirror || 'tsinghua'
let pipResult = await window.electronAPI.installDependencies(pipMirror)
if (aborted.value) return
// 如果初始化时的镜像源不通,让用户重新选择
if (!pipResult.success) {
logger.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
// 切换到手动模式让用户重新选择镜像源
progressText.value = '依赖安装失败,需要重新配置镜像源'
progressStatus.value = 'exception'
setTimeout(() => {
progressText.value = '请点击下方按钮重新配置环境'
}, 2000)
return
}

View File

@@ -3,10 +3,10 @@
<h3>获取后端源码</h3>
<div class="install-section">
<p>{{ backendExists ? '更新最新的后端代码' : '获取后端源代码' }}</p>
<div class="mirror-grid">
<div
v-for="mirror in gitMirrors"
<div
v-for="mirror in gitMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedGitMirror === mirror.key }"
@@ -24,7 +24,7 @@
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testGitMirrorSpeed" :loading="testingGitSpeed" type="primary">
{{ testingGitSpeed ? '测速中...' : '开始测速' }}
@@ -54,7 +54,6 @@ import { GIT_MIRRORS } from '@/config/mirrors'
const gitMirrors = ref<Mirror[]>(GIT_MIRRORS)
const selectedGitMirror = ref('github')
const testingGitSpeed = ref(false)
@@ -81,17 +80,17 @@ async function saveMirrorConfig() {
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
const startTime = Date.now()
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
await fetch(url.replace('.git', ''), {
method: 'HEAD',
await fetch(url.replace('.git', ''), {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
signal: controller.signal,
})
clearTimeout(timeoutId)
return Date.now() - startTime
} catch (error) {
@@ -102,14 +101,14 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
async function testGitMirrorSpeed() {
testingGitSpeed.value = true
try {
const promises = gitMirrors.value.map(async (mirror) => {
const promises = gitMirrors.value.map(async mirror => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
await Promise.all(promises)
gitMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
const fastest = gitMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedGitMirror.value = fastest.key
@@ -131,14 +130,14 @@ function getSpeedClass(speed: number | null) {
defineExpose({
selectedGitMirror,
testGitMirrorSpeed,
gitMirrors
gitMirrors,
})
// 组件挂载时加载配置并自动开始测速
onMounted(async () => {
// 先加载配置
await loadMirrorConfig()
console.log('BackendStep 组件挂载,自动开始测速')
setTimeout(() => {
testGitMirrorSpeed()

View File

@@ -3,10 +3,10 @@
<h3>安装 Python 依赖包</h3>
<div class="install-section">
<p>通过 pip 安装项目所需的 Python 依赖包</p>
<div class="mirror-grid">
<div
v-for="mirror in pipMirrors"
<div
v-for="mirror in pipMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedPipMirror === mirror.key }"
@@ -24,7 +24,7 @@
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testPipMirrorSpeed" :loading="testingPipSpeed" type="primary">
{{ testingPipSpeed ? '测速中...' : '重新测速' }}
@@ -76,17 +76,17 @@ async function saveMirrorConfig() {
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
const startTime = Date.now()
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
await fetch(url, {
method: 'HEAD',
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
signal: controller.signal,
})
clearTimeout(timeoutId)
return Date.now() - startTime
} catch (error) {
@@ -97,14 +97,14 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
async function testPipMirrorSpeed() {
testingPipSpeed.value = true
try {
const promises = pipMirrors.value.map(async (mirror) => {
const promises = pipMirrors.value.map(async mirror => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
await Promise.all(promises)
pipMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
const fastest = pipMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedPipMirror.value = fastest.key
@@ -125,14 +125,14 @@ function getSpeedClass(speed: number | null) {
defineExpose({
selectedPipMirror,
testPipMirrorSpeed
testPipMirrorSpeed,
})
// 组件挂载时加载配置并自动开始测速
onMounted(async () => {
// 先加载配置
await loadMirrorConfig()
console.log('DependenciesStep 组件挂载,自动开始测速')
setTimeout(() => {
testPipMirrorSpeed()

View File

@@ -4,22 +4,22 @@
<div v-if="!gitInstalled" class="install-section">
<p>需要安装 Git 工具来获取源代码</p>
<div class="git-info">
<a-alert
message="Git 工具信息"
<a-alert
message="Git 工具信息"
description="将安装便携版 Git 工具,包含完整的版本控制功能,无需系统安装。"
type="info"
show-icon
type="info"
show-icon
/>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="Git已成功安装无需继续安装" />
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>-->
<!-- </div>-->
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>-->
<!-- </div>-->
</div>
</div>
</template>
@@ -43,13 +43,13 @@ async function handleForceReinstall() {
if (!deleteResult.success) {
throw new Error(`删除Git失败: ${deleteResult.error}`)
}
// 重新安装Git
const installResult = await window.electronAPI.downloadGit()
if (!installResult.success) {
throw new Error(`重新安装Git失败: ${installResult.error}`)
}
console.log('Git强制重新安装成功')
// 通知父组件更新状态
window.location.reload() // 简单的页面刷新来更新状态
@@ -62,7 +62,7 @@ async function handleForceReinstall() {
}
defineExpose({
handleForceReinstall
handleForceReinstall,
})
</script>

View File

@@ -3,22 +3,23 @@
<div class="header">
<h1>AUTO_MAA 初始化向导</h1>
<p>欢迎使用 AUTO_MAA让我们来配置您的运行环境</p>
<div class="header-actions">
<a-button size="large" type="primary" @click="handleSkipToHome">
跳转至首页仅开发用
</a-button>
<a-button size="large" type="default" @click="handleJumpToStep(6)" style="margin-left: 16px;">
<a-button
size="large"
type="default"
@click="handleJumpToStep(6)"
style="margin-left: 16px"
>
跳到启动服务第七步
</a-button>
</div>
</div>
<a-steps
:current="currentStep"
:status="stepStatus"
class="init-steps"
>
<a-steps :current="currentStep" :status="stepStatus" class="init-steps">
<a-step title="主题设置" description="选择您喜欢的主题" />
<a-step title="Python 环境" description="安装 Python 运行环境" />
<a-step title="pip 安装" description="安装 Python 包管理器" />
@@ -28,22 +29,26 @@
<a-step title="启动服务" description="启动后端服务" />
</a-steps>
<!-- &lt;!&ndash; 全局进度条 &ndash;&gt;-->
<!-- <div v-if="isProcessing" class="global-progress">-->
<!-- <a-progress -->
<!-- :percent="globalProgress" -->
<!-- :status="globalProgressStatus"-->
<!-- :show-info="true"-->
<!-- />-->
<!-- <div class="progress-text">{{ progressText }}</div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 全局进度条 &ndash;&gt;-->
<!-- <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">
<!-- 步骤 0: 主题设置 -->
<ThemeStep v-if="currentStep === 0" ref="themeStepRef" />
<!-- 步骤 1: Python 环境 -->
<PythonStep v-if="currentStep === 1" :python-installed="pythonInstalled" ref="pythonStepRef" />
<PythonStep
v-if="currentStep === 1"
:python-installed="pythonInstalled"
ref="pythonStepRef"
/>
<!-- 步骤 2: pip 安装 -->
<PipStep v-if="currentStep === 2" :pip-installed="pipInstalled" ref="pipStepRef" />
@@ -71,20 +76,19 @@
上一步
</a-button>
<a-button
<a-button
v-if="currentStep < 6"
size="large"
type="primary"
type="primary"
@click="handleNextStep"
:loading="isProcessing"
>
{{ getNextButtonText() }}
</a-button>
<!-- 第7步重新启动服务按钮 -->
<a-button
v-if="currentStep === 6"
<a-button
v-if="currentStep === 6"
type="default"
size="large"
@click="handleNextStep"
@@ -94,15 +98,15 @@
</a-button>
</div>
<!-- <div v-if="errorMessage" class="error-message">-->
<!-- <a-alert -->
<!-- :message="errorMessage" -->
<!-- type="error" -->
<!-- show-icon -->
<!-- closable-->
<!-- @close="errorMessage = ''"-->
<!-- />-->
<!-- </div>-->
<!-- <div v-if="errorMessage" class="error-message">-->
<!-- <a-alert -->
<!-- :message="errorMessage" -->
<!-- type="error" -->
<!-- show-icon -->
<!-- closable-->
<!-- @close="errorMessage = ''"-->
<!-- />-->
<!-- </div>-->
</div>
</template>
@@ -130,7 +134,7 @@ interface Props {
backendExists: boolean
dependenciesInstalled: boolean
serviceStarted: boolean
// 事件处理函数
onSkipToHome: () => void
onEnterApp: () => void
@@ -183,7 +187,7 @@ async function handleNextStep() {
console.log('nextStep 被调用,当前步骤:', currentStep.value)
isProcessing.value = true
errorMessage.value = ''
try {
switch (currentStep.value) {
case 0: // 主题设置
@@ -227,7 +231,7 @@ async function handleNextStep() {
await startBackendService()
break
}
if (currentStep.value < 6) {
currentStep.value++
// 进入新步骤时自动开始测速
@@ -244,14 +248,22 @@ async function handleNextStep() {
function getNextButtonText() {
switch (currentStep.value) {
case 0: return '下一步'
case 1: return props.pythonInstalled ? '下一步' : '安装 Python'
case 2: return props.pipInstalled ? '下一步' : '安装 pip'
case 3: return props.gitInstalled ? '下一步' : '安装 Git'
case 4: return props.backendExists ? '更新代码' : '获取代码'
case 5: return '安装依赖'
case 6: return '启动服务'
default: return '下一步'
case 0:
return '下一步'
case 1:
return props.pythonInstalled ? '下一步' : '安装 Python'
case 2:
return props.pipInstalled ? '下一步' : '安装 pip'
case 3:
return props.gitInstalled ? '下一步' : '安装 Git'
case 4:
return props.backendExists ? '更新代码' : '获取代码'
case 5:
return '安装依赖'
case 6:
return '启动服务'
default:
return '下一步'
}
}
@@ -368,13 +380,13 @@ async function autoStartBackendService() {
logger.info('自动启动后端服务')
isProcessing.value = true
errorMessage.value = ''
if (serviceStepRef.value) {
serviceStepRef.value.startingService = true
serviceStepRef.value.showServiceProgress = true
serviceStepRef.value.serviceStatus = '正在自动启动后端服务...'
}
try {
const result = await window.electronAPI.startBackend()
if (result.success) {
@@ -384,7 +396,7 @@ async function autoStartBackendService() {
}
stepStatus.value = 'finish'
logger.info('后端服务自动启动成功延迟1秒后自动进入主页')
// 延迟1秒后自动进入主页
setTimeout(() => {
handleEnterApp()
@@ -413,13 +425,13 @@ async function autoStartBackendService() {
// 手动启动后端服务(用户点击按钮时调用)
async function startBackendService() {
logger.info('手动重新启动后端服务')
if (serviceStepRef.value) {
serviceStepRef.value.startingService = true
serviceStepRef.value.showServiceProgress = true
serviceStepRef.value.serviceStatus = '正在重新启动后端服务...'
}
try {
const result = await window.electronAPI.startBackend()
if (result.success) {
@@ -429,7 +441,7 @@ async function startBackendService() {
}
stepStatus.value = 'finish'
logger.info('后端服务手动启动成功延迟1秒后自动进入主页')
// 延迟1秒后自动进入主页
setTimeout(() => {
handleEnterApp()
@@ -456,7 +468,7 @@ function handleDownloadProgress(progress: any) {
// 更新全局进度条
globalProgress.value = progress.progress
progressText.value = progress.message
if (progress.status === 'error') {
globalProgressStatus.value = 'exception'
} else if (progress.status === 'completed') {
@@ -464,7 +476,7 @@ function handleDownloadProgress(progress: any) {
} else {
globalProgressStatus.value = 'normal'
}
// 通知父组件
props.onProgressUpdate(progress)
}
@@ -472,11 +484,11 @@ function handleDownloadProgress(progress: any) {
// 暴露给父组件的方法
defineExpose({
currentStep,
handleDownloadProgress
handleDownloadProgress,
})
// 监听 errorMessage一旦有内容就弹窗
watch(errorMessage, (val) => {
watch(errorMessage, val => {
if (val) {
message.error(val)
// 弹窗后可选:自动清空 errorMessage
@@ -554,7 +566,7 @@ watch(errorMessage, (val) => {
flex-direction: column;
gap: 8px;
}
.step-actions {
flex-direction: column;
gap: 12px;

View File

@@ -3,24 +3,24 @@
<h3>安装 pip 包管理器</h3>
<div v-if="!pipInstalled" class="install-section">
<p>pip Python 的包管理工具用于安装和管理 Python </p>
<div class="pip-info">
<a-alert
message="pip 安装信息"
<a-alert
message="pip 安装信息"
description="将自动下载并安装 pip 包管理器,这是安装 Python 依赖包的必要工具。"
type="info"
show-icon
type="info"
show-icon
/>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="pip已成功安装无需继续安装" />
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>-->
<!-- </div>-->
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>-->
<!-- </div>-->
</div>
</div>
</template>
@@ -44,13 +44,13 @@ async function handleForceReinstall() {
if (!deleteResult.success) {
throw new Error(`删除pip失败: ${deleteResult.error}`)
}
// 重新安装pip
const installResult = await window.electronAPI.installPip()
if (!installResult.success) {
throw new Error(`重新安装pip失败: ${installResult.error}`)
}
console.log('pip强制重新安装成功')
// 通知父组件更新状态
window.location.reload() // 简单的页面刷新来更新状态
@@ -63,7 +63,7 @@ async function handleForceReinstall() {
}
defineExpose({
handleForceReinstall
handleForceReinstall,
})
</script>

View File

@@ -1,42 +1,47 @@
<template>
<div class="step-panel">
<h3>Python 运行环境</h3>
<div v-if="!pythonInstalled" class="install-section">
<p>需要安装 Python 3.13.0 运行环境64位嵌入式版本</p>
<div class="step-panel">
<h3>Python 运行环境</h3>
<div v-if="!pythonInstalled" class="install-section">
<p>需要安装 Python 3.13.0 运行环境64位嵌入式版本</p>
<div class="mirror-grid">
<div v-for="mirror in pythonMirrors" :key="mirror.key" class="mirror-card"
:class="{ active: selectedPythonMirror === mirror.key }" @click="selectedPythonMirror = mirror.key">
<div class="mirror-header">
<h4>{{ mirror.name }}</h4>
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
<span v-else-if="testingSpeed">测试中...</span>
<span v-else-if="mirror.speed === 9999">超时</span>
<span v-else>{{ mirror.speed }}ms</span>
</div>
</div>
<div class="mirror-url">{{ mirror.url }}</div>
</div>
<div class="mirror-grid">
<div
v-for="mirror in pythonMirrors"
:key="mirror.key"
class="mirror-card"
:class="{ active: selectedPythonMirror === mirror.key }"
@click="selectedPythonMirror = mirror.key"
>
<div class="mirror-header">
<h4>{{ mirror.name }}</h4>
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
<span v-else-if="testingSpeed">测试中...</span>
<span v-else-if="mirror.speed === 9999">超时</span>
<span v-else>{{ mirror.speed }}ms</span>
</div>
</div>
<div class="mirror-url">{{ mirror.url }}</div>
</div>
</div>
<div class="test-actions">
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
{{ testingSpeed ? '测速中...' : '重新测速' }}
</a-button>
<span class="test-note">3秒无响应视为超时</span>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="Python已成功安装无需继续安装" />
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>-->
<!-- </div>-->
</div>
<div class="test-actions">
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
{{ testingSpeed ? '测速中...' : '重新测速' }}
</a-button>
<span class="test-note">3秒无响应视为超时</span>
</div>
</div>
<div v-else class="already-installed">
<a-result status="success" title="Python已成功安装无需继续安装" />
<!-- <div class="reinstall-section">-->
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
<!-- </a-button>-->
<!-- <p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>-->
<!-- </div>-->
</div>
</div>
</template>
<script setup lang="ts">
@@ -44,14 +49,14 @@ import { ref, onMounted } from 'vue'
import { getConfig, saveConfig } from '@/utils/config'
interface Mirror {
key: string
name: string
url: string
speed: number | null
key: string
name: string
url: string
speed: number | null
}
const props = defineProps<{
pythonInstalled: boolean
pythonInstalled: boolean
}>()
import { PYTHON_MIRRORS } from '@/config/mirrors'
@@ -84,239 +89,239 @@ async function saveMirrorConfig() {
}
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
const startTime = Date.now()
const startTime = Date.now()
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
})
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal,
})
clearTimeout(timeoutId)
return Date.now() - startTime
} catch (error) {
return 9999 // 超时或失败
}
clearTimeout(timeoutId)
return Date.now() - startTime
} catch (error) {
return 9999 // 超时或失败
}
}
async function testPythonMirrorSpeed() {
testingSpeed.value = true
try {
const promises = pythonMirrors.value.map(async (mirror) => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
testingSpeed.value = true
try {
const promises = pythonMirrors.value.map(async mirror => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
await Promise.all(promises)
await Promise.all(promises)
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedPythonMirror.value = fastest.key
await saveMirrorConfig() // 保存最快的镜像源选择
}
} finally {
testingSpeed.value = false
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
if (fastest) {
selectedPythonMirror.value = fastest.key
await saveMirrorConfig() // 保存最快的镜像源选择
}
} finally {
testingSpeed.value = false
}
}
function getSpeedClass(speed: number | null) {
if (speed === null) return 'speed-unknown'
if (speed === 9999) return 'speed-timeout'
if (speed < 500) return 'speed-fast'
if (speed < 1500) return 'speed-medium'
return 'speed-slow'
if (speed === null) return 'speed-unknown'
if (speed === 9999) return 'speed-timeout'
if (speed < 500) return 'speed-fast'
if (speed < 1500) return 'speed-medium'
return 'speed-slow'
}
// 强制重新安装Python
async function handleForceReinstall() {
reinstalling.value = true
try {
console.log('开始强制重新安装Python')
// 先删除现有Python目录
const deleteResult = await window.electronAPI.deletePython()
if (!deleteResult.success) {
throw new Error(`删除Python目录失败: ${deleteResult.error}`)
}
// 重新下载安装Python
const installResult = await window.electronAPI.downloadPython(selectedPythonMirror.value)
if (!installResult.success) {
throw new Error(`重新安装Python失败: ${installResult.error}`)
}
console.log('Python强制重新安装成功')
// 通知父组件更新状态
window.location.reload() // 简单的页面刷新来更新状态
} catch (error) {
console.error('Python强制重新安装失败:', error)
// 这里可以添加错误提示
} finally {
reinstalling.value = false
reinstalling.value = true
try {
console.log('开始强制重新安装Python')
// 先删除现有Python目录
const deleteResult = await window.electronAPI.deletePython()
if (!deleteResult.success) {
throw new Error(`删除Python目录失败: ${deleteResult.error}`)
}
// 重新下载安装Python
const installResult = await window.electronAPI.downloadPython(selectedPythonMirror.value)
if (!installResult.success) {
throw new Error(`重新安装Python失败: ${installResult.error}`)
}
console.log('Python强制重新安装成功')
// 通知父组件更新状态
window.location.reload() // 简单的页面刷新来更新状态
} catch (error) {
console.error('Python强制重新安装失败:', error)
// 这里可以添加错误提示
} finally {
reinstalling.value = false
}
}
defineExpose({
selectedPythonMirror,
testPythonMirrorSpeed,
handleForceReinstall
selectedPythonMirror,
testPythonMirrorSpeed,
handleForceReinstall,
})
// 组件挂载时加载配置并自动开始测速
onMounted(async () => {
// 先加载配置
await loadMirrorConfig()
if (!props.pythonInstalled) {
console.log('PythonStep 组件挂载,自动开始测速')
setTimeout(() => {
testPythonMirrorSpeed()
}, 200) // 延迟200ms确保组件完全渲染
}
// 先加载配置
await loadMirrorConfig()
if (!props.pythonInstalled) {
console.log('PythonStep 组件挂载,自动开始测速')
setTimeout(() => {
testPythonMirrorSpeed()
}, 200) // 延迟200ms确保组件完全渲染
}
})
</script>
<style scoped>
.step-panel {
padding: 20px;
background: var(--ant-color-bg-elevated);
border-radius: 8px;
border: 1px solid var(--ant-color-border);
padding: 20px;
background: var(--ant-color-bg-elevated);
border-radius: 8px;
border: 1px solid var(--ant-color-border);
}
.step-panel h3 {
font-size: 20px;
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 20px;
font-size: 20px;
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 20px;
}
.install-section {
display: flex;
flex-direction: column;
gap: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.install-section p {
color: var(--ant-color-text-secondary);
margin: 0;
color: var(--ant-color-text-secondary);
margin: 0;
}
.mirror-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.mirror-card {
padding: 16px;
border: 2px solid var(--ant-color-border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--ant-color-bg-container);
padding: 16px;
border: 2px solid var(--ant-color-border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--ant-color-bg-container);
}
.mirror-card:hover {
border-color: var(--ant-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-color: var(--ant-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.mirror-card.active {
border-color: var(--ant-color-primary);
background: var(--ant-color-primary-bg);
border-color: var(--ant-color-primary);
background: var(--ant-color-primary-bg);
}
.mirror-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.mirror-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--ant-color-text);
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--ant-color-text);
}
.speed-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.speed-badge.speed-unknown {
background: var(--ant-color-fill-tertiary);
color: var(--ant-color-text-tertiary);
background: var(--ant-color-fill-tertiary);
color: var(--ant-color-text-tertiary);
}
.speed-badge.speed-fast {
background: var(--ant-color-success-bg);
color: var(--ant-color-success);
background: var(--ant-color-success-bg);
color: var(--ant-color-success);
}
.speed-badge.speed-medium {
background: var(--ant-color-warning-bg);
color: var(--ant-color-warning);
background: var(--ant-color-warning-bg);
color: var(--ant-color-warning);
}
.speed-badge.speed-slow {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.speed-badge.speed-timeout {
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
background: var(--ant-color-error-bg);
color: var(--ant-color-error);
}
.mirror-url {
font-size: 12px;
color: var(--ant-color-text-tertiary);
word-break: break-all;
font-size: 12px;
color: var(--ant-color-text-tertiary);
word-break: break-all;
}
.test-actions {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
}
.test-note {
font-size: 12px;
color: var(--ant-color-text-tertiary);
font-size: 12px;
color: var(--ant-color-text-tertiary);
}
.already-installed {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 200px;
gap: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 200px;
gap: 20px;
}
.reinstall-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.reinstall-note {
font-size: 12px;
color: var(--ant-color-text-tertiary);
text-align: center;
margin: 0;
font-size: 12px;
color: var(--ant-color-text-tertiary);
text-align: center;
margin: 0;
}
</style>

View File

@@ -24,7 +24,7 @@ defineExpose({
startingService,
showServiceProgress,
serviceProgress,
serviceStatus
serviceStatus,
})
</script>

View File

@@ -13,8 +13,8 @@
<div class="setting-group">
<label>主题色彩</label>
<div class="color-picker">
<div
v-for="(color, key) in themeColors"
<div
v-for="(color, key) in themeColors"
:key="key"
class="color-option"
:class="{ active: selectedThemeColor === key }"
@@ -51,9 +51,9 @@ async function onThemeColorChange(color: ThemeColor) {
async function saveSettings() {
await saveThemeConfig(selectedThemeMode.value, selectedThemeColor.value)
console.log('主题设置已保存:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value
console.log('主题设置已保存:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value,
})
}
@@ -64,9 +64,9 @@ async function loadSettings() {
selectedThemeColor.value = config.themeColor
setThemeMode(selectedThemeMode.value)
setThemeColor(selectedThemeColor.value)
console.log('主题设置已加载:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value
console.log('主题设置已加载:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value,
})
} catch (error) {
console.warn('Failed to load theme settings:', error)
@@ -78,7 +78,7 @@ defineExpose({
loadSettings,
saveSettings,
selectedThemeMode,
selectedThemeColor
selectedThemeColor,
})
// 组件挂载时加载设置

View File

@@ -19,9 +19,7 @@
:scroll="{ x: 600 }"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ index + 1 }}个脚本
</template>
<template v-if="column.key === 'index'"> {{ index + 1 }}个脚本 </template>
<template v-else-if="column.key === 'script'">
{{ getScriptName(record.script) }}
</template>
@@ -31,7 +29,12 @@
<EditOutlined />
编辑
</a-button>
<a-popconfirm title="确定要删除这个队列项吗?" @confirm="deleteQueueItem(record.id)" ok-text="确定" cancel-text="取消">
<a-popconfirm
title="确定要删除这个队列项吗?"
@confirm="deleteQueueItem(record.id)"
ok-text="确定"
cancel-text="取消"
>
<a-button size="small" danger>
<DeleteOutlined />
删除
@@ -47,11 +50,22 @@
</div>
<!-- 队列项编辑弹窗 -->
<a-modal v-model:open="modalVisible" :title="editingQueueItem ? '编辑队列项' : '添加队列项'" @ok="saveQueueItem"
@cancel="cancelEdit" :confirm-loading="saving" width="600px">
<a-modal
v-model:open="modalVisible"
:title="editingQueueItem ? '编辑队列项' : '添加队列项'"
@ok="saveQueueItem"
@cancel="cancelEdit"
:confirm-loading="saving"
width="600px"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="关联脚本" name="script">
<a-select v-model:value="form.script" placeholder="请选择关联脚本" allow-clear :options="scriptOptions" />
<a-select
v-model:value="form.script"
placeholder="请选择关联脚本"
allow-clear
:options="scriptOptions"
/>
</a-form-item>
</a-form>
</a-modal>
@@ -66,7 +80,7 @@ import {
ReloadOutlined,
EditOutlined,
DeleteOutlined,
MoreOutlined
MoreOutlined,
} from '@ant-design/icons-vue'
import { Service } from '@/api'
import type { FormInstance } from 'ant-design-vue'
@@ -103,12 +117,12 @@ const getScriptName = (scriptId: string) => {
// 表单引用和数据
const formRef = ref<FormInstance>()
const form = reactive({
script: ''
script: '',
})
// 表单验证规则
const rules = {
script: [{ required: true, message: '请选择关联脚本', trigger: 'change' }]
script: [{ required: true, message: '请选择关联脚本', trigger: 'change' }],
}
// 表格列配置
@@ -135,10 +149,13 @@ const queueColumns = [
const queueItems = ref(props.queueItems)
// 监听props变化
watch(() => props.queueItems, (newQueueItems) => {
queueItems.value = newQueueItems
}, { deep: true })
watch(
() => props.queueItems,
newQueueItems => {
queueItems.value = newQueueItems
},
{ deep: true }
)
// 加载脚本选项
const loadOptions = async () => {
@@ -161,12 +178,11 @@ const loadOptions = async () => {
}
}
// 添加队列项
const addQueueItem = async () => {
editingQueueItem.value = null
Object.assign(form, {
script: ''
script: '',
})
// 确保在打开弹窗时加载脚本选项
@@ -178,7 +194,7 @@ const addQueueItem = async () => {
const editQueueItem = async (item: any) => {
editingQueueItem.value = item
Object.assign(form, {
script: item.script || ''
script: item.script || '',
})
// 确保在打开弹窗时加载脚本选项
@@ -199,9 +215,9 @@ const saveQueueItem = async () => {
queueItemId: editingQueueItem.value.id,
data: {
Info: {
ScriptId: form.script
}
}
ScriptId: form.script,
},
},
})
if (response.code === 200) {
@@ -214,7 +230,7 @@ const saveQueueItem = async () => {
// 添加队列项 - 先创建,再更新
// 1. 先创建队列项只传queueId
const createResponse = await Service.addItemApiQueueItemAddPost({
queueId: props.queueId
queueId: props.queueId,
})
// 2. 用返回的queueItemId更新队列项数据
@@ -224,9 +240,9 @@ const saveQueueItem = async () => {
queueItemId: createResponse.queueItemId,
data: {
Info: {
ScriptId: form.script
}
}
ScriptId: form.script,
},
},
})
if (updateResponse.code === 200) {
@@ -262,7 +278,7 @@ const deleteQueueItem = async (itemId: string) => {
try {
const response = await Service.deleteItemApiQueueItemDeletePost({
queueId: props.queueId,
queueItemId: itemId
queueItemId: itemId,
})
if (response.code === 200) {

View File

@@ -96,6 +96,6 @@ export function usePlanApi() {
createPlan,
updatePlan,
deletePlan,
reorderPlans
reorderPlans,
}
}
}

View File

@@ -14,11 +14,11 @@ export function useScriptApi() {
try {
const requestData: ScriptCreateIn = {
type: type === 'MAA' ? ScriptCreateIn.type.MAA : ScriptCreateIn.type.GENERAL
type: type === 'MAA' ? ScriptCreateIn.type.MAA : ScriptCreateIn.type.GENERAL,
}
const response = await Service.addScriptApiScriptsAddPost(requestData)
if (response.code !== 200) {
const errorMsg = response.message || '添加脚本失败'
message.error(errorMsg)
@@ -28,7 +28,7 @@ export function useScriptApi() {
return {
scriptId: response.scriptId,
message: response.message || '脚本添加成功',
data: response.data
data: response.data,
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : '添加脚本失败'
@@ -49,7 +49,7 @@ export function useScriptApi() {
try {
const response = await Service.getScriptsApiScriptsGetPost({})
if (response.code !== 200) {
const errorMsg = response.message || '获取脚本列表失败'
message.error(errorMsg)
@@ -84,7 +84,7 @@ export function useScriptApi() {
try {
const response = await Service.getScriptsApiScriptsGetPost({ scriptId })
if (response.code !== 200) {
const errorMsg = response.message || '获取脚本详情失败'
message.error(errorMsg)
@@ -105,7 +105,7 @@ export function useScriptApi() {
type: scriptType,
name: config?.Info?.Name || `${item.type}脚本`,
config,
createTime: new Date().toLocaleString()
createTime: new Date().toLocaleString(),
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : '获取脚本详情失败'
@@ -126,7 +126,7 @@ export function useScriptApi() {
try {
const response = await Service.deleteScriptApiScriptsDeletePost({ scriptId })
if (response.code !== 200) {
const errorMsg = response.message || '删除脚本失败'
message.error(errorMsg)
@@ -155,11 +155,11 @@ export function useScriptApi() {
// 创建数据副本并移除 SubConfigsInfo 字段
const { SubConfigsInfo, ...dataToSend } = data
const response = await Service.updateScriptApiScriptsUpdatePost({
scriptId,
data: dataToSend
const response = await Service.updateScriptApiScriptsUpdatePost({
scriptId,
data: dataToSend,
})
if (response.code !== 200) {
const errorMsg = response.message || '更新脚本失败'
message.error(errorMsg)
@@ -189,4 +189,4 @@ export function useScriptApi() {
deleteScript,
updateScript,
}
}
}

View File

@@ -14,7 +14,7 @@ export function useSettingsApi() {
try {
const response = await Service.getScriptsApiSettingGetPost()
// 根据code判断是否成功非200就是不成功
if (response.code !== 200) {
const errorMsg = response.message || '获取设置失败'
@@ -42,9 +42,9 @@ export function useSettingsApi() {
try {
const response = await Service.updateScriptApiSettingUpdatePost({
data: settings
data: settings,
})
// 根据code判断是否成功非200就是不成功
if (response.code !== 200) {
const errorMsg = response.message || '设置修改失败'
@@ -72,4 +72,4 @@ export function useSettingsApi() {
getSettings,
updateSettings,
}
}
}

View File

@@ -70,7 +70,7 @@ const updateTheme = () => {
const updateCSSVariables = () => {
const root = document.documentElement
const primaryColor = themeColors[themeColor.value]
if (isDark.value) {
// 深色模式变量
root.style.setProperty('--ant-color-primary', primaryColor)
@@ -109,43 +109,37 @@ const updateCSSVariables = () => {
// 颜色工具函数
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}
const rgbToHex = (r: number, g: number, b: number) => {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
}
const lightenColor = (hex: string, percent: number) => {
const rgb = hexToRgb(hex)
if (!rgb) return hex
const { r, g, b } = rgb
const amount = Math.round(2.55 * percent)
return rgbToHex(
Math.min(255, r + amount),
Math.min(255, g + amount),
Math.min(255, b + amount)
)
return rgbToHex(Math.min(255, r + amount), Math.min(255, g + amount), Math.min(255, b + amount))
}
const darkenColor = (hex: string, percent: number) => {
const rgb = hexToRgb(hex)
if (!rgb) return hex
const { r, g, b } = rgb
const amount = Math.round(2.55 * percent)
return rgbToHex(
Math.max(0, r - amount),
Math.max(0, g - amount),
Math.max(0, b - amount)
)
return rgbToHex(Math.max(0, r - amount), Math.max(0, g - amount), Math.max(0, b - amount))
}
// 监听系统主题变化

View File

@@ -14,11 +14,11 @@ export function useUserApi() {
try {
const requestData: UserInBase = {
scriptId
scriptId,
}
const response = await Service.addUserApiScriptsUserAddPost(requestData)
if (response.code !== 200) {
const errorMsg = response.message || '添加用户失败'
message.error(errorMsg)
@@ -39,7 +39,11 @@ export function useUserApi() {
}
// 更新用户
const updateUser = async (scriptId: string, userId: string, userData: Record<string, Record<string, any>>): Promise<boolean> => {
const updateUser = async (
scriptId: string,
userId: string,
userData: Record<string, Record<string, any>>
): Promise<boolean> => {
loading.value = true
error.value = null
@@ -47,11 +51,11 @@ export function useUserApi() {
const requestData: UserUpdateIn = {
scriptId,
userId,
data: userData
data: userData,
}
const response = await Service.updateUserApiScriptsUserUpdatePost(requestData)
if (response.code !== 200) {
const errorMsg = response.message || '更新用户失败'
message.error(errorMsg)
@@ -80,11 +84,11 @@ export function useUserApi() {
try {
const requestData: UserDeleteIn = {
scriptId,
userId
userId,
}
const response = await Service.deleteUserApiScriptsUserDeletePost(requestData)
if (response.code !== 200) {
const errorMsg = response.message || '删除用户失败'
message.error(errorMsg)
@@ -112,4 +116,4 @@ export function useUserApi() {
updateUser,
deleteUser,
}
}
}

View File

@@ -22,38 +22,38 @@ export const GIT_MIRRORS: MirrorConfig[] = [
key: 'github',
name: 'GitHub 官方',
url: 'https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghfast',
name: 'ghfast 镜像',
url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_cloudflare',
name: 'gh-proxy (Cloudflare加速)',
url: 'https://gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_hongkong',
name: 'gh-proxy (香港节点加速)',
url: 'https://hk.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_fastly',
name: 'gh-proxy (Fastly CDN加速)',
url: 'https://cdn.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_edgeone',
name: 'gh-proxy (EdgeOne加速',
url: 'https://edgeone.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
}
speed: null,
},
]
/**
@@ -64,32 +64,32 @@ export const PYTHON_MIRRORS: MirrorConfig[] = [
key: 'official',
name: 'Python 官方',
url: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'tsinghua',
name: '清华 TUNA 镜像',
url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'ustc',
name: '中科大镜像',
url: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'huawei',
name: '华为云镜像',
url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'aliyun',
name: '阿里云镜像',
url: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip',
speed: null
}
speed: null,
},
]
/**
@@ -100,32 +100,32 @@ export const PIP_MIRRORS: MirrorConfig[] = [
key: 'official',
name: 'PyPI 官方',
url: 'https://pypi.org/simple/',
speed: null
speed: null,
},
{
key: 'tsinghua',
name: '清华大学',
url: 'https://pypi.tuna.tsinghua.edu.cn/simple/',
speed: null
speed: null,
},
{
key: 'ustc',
name: '中科大',
url: 'https://pypi.mirrors.ustc.edu.cn/simple/',
speed: null
speed: null,
},
{
key: 'aliyun',
name: '阿里云',
url: 'https://mirrors.aliyun.com/pypi/simple/',
speed: null
speed: null,
},
{
key: 'douban',
name: '豆瓣',
url: 'https://pypi.douban.com/simple/',
speed: null
}
speed: null,
},
]
/**
@@ -137,7 +137,7 @@ export const API_ENDPOINTS = {
// WebSocket连接基础URL
websocket: 'ws://localhost:8000',
// 代理服务器示例
proxy: 'http://127.0.0.1:7890'
proxy: 'http://127.0.0.1:7890',
}
/**
@@ -146,9 +146,9 @@ export const API_ENDPOINTS = {
export const DOWNLOAD_LINKS = {
// get-pip.py 下载链接
getPip: 'http://221.236.27.82:10197/d/AUTO_MAA/get-pip.py',
// Git 客户端下载链接
git: 'http://221.236.27.82:10197/d/AUTO_MAA/git.zip'
git: 'http://221.236.27.82:10197/d/AUTO_MAA/git.zip',
}
/**
@@ -157,7 +157,7 @@ export const DOWNLOAD_LINKS = {
export const ALL_MIRRORS: MirrorCategory = {
git: GIT_MIRRORS,
python: PYTHON_MIRRORS,
pip: PIP_MIRRORS
pip: PIP_MIRRORS,
}
/**
@@ -213,4 +213,4 @@ export function getFastestMirror(type: keyof MirrorCategory): MirrorConfig | nul
const mirrors = getMirrorsByType(type)
const sortedMirrors = sortMirrorsBySpeed(mirrors)
return sortedMirrors.find(m => m.speed !== null && m.speed !== 9999) || null
}
}

View File

@@ -3,7 +3,6 @@ import type { RouteRecordRaw } from 'vue-router'
import { isAppInitialized } from '@/utils/config'
const routes: RouteRecordRaw[] = [
{
path: '/',
@@ -90,7 +89,6 @@ const router = createRouter({
routes,
})
// 添加路由守卫,确保在生产环境中也能正确进入初始化页面
router.beforeEach(async (to, from, next) => {
console.log('路由守卫:', { to: to.path, from: from.path })
@@ -118,5 +116,4 @@ router.beforeEach(async (to, from, next) => {
next()
})
export default router

View File

@@ -1,94 +1,94 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color-scheme: light dark;
/* 默认浅色模式 CSS 变量 */
--ant-color-primary: #1677ff;
--ant-color-primary-hover: #4096ff;
--ant-color-primary-bg: #1677ff1a;
--ant-color-text: rgba(0, 0, 0, 0.88);
--ant-color-text-secondary: rgba(0, 0, 0, 0.65);
--ant-color-text-tertiary: rgba(0, 0, 0, 0.45);
--ant-color-bg-container: #ffffff;
--ant-color-bg-layout: #f5f5f5;
--ant-color-bg-elevated: #ffffff;
--ant-color-border: #d9d9d9;
--ant-color-border-secondary: #303030;
--ant-color-error: #ff4d4f;
--ant-color-success: #52c41a;
--ant-color-warning: #faad14;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* 默认浅色模式 CSS 变量 */
--ant-color-primary: #1677ff;
--ant-color-primary-hover: #4096ff;
--ant-color-primary-bg: #1677ff1a;
--ant-color-text: rgba(0, 0, 0, 0.88);
--ant-color-text-secondary: rgba(0, 0, 0, 0.65);
--ant-color-text-tertiary: rgba(0, 0, 0, 0.45);
--ant-color-bg-container: #ffffff;
--ant-color-bg-layout: #f5f5f5;
--ant-color-bg-elevated: #ffffff;
--ant-color-border: #d9d9d9;
--ant-color-border-secondary: #303030;
--ant-color-error: #ff4d4f;
--ant-color-success: #52c41a;
--ant-color-warning: #faad14;
}
/* 深色模式默认变量 */
:root.dark {
--ant-color-primary: #1677ff;
--ant-color-primary-hover: #4096ff;
--ant-color-primary-bg: #1677ff1a;
--ant-color-text: rgba(255, 255, 255, 0.88);
--ant-color-text-secondary: rgba(255, 255, 255, 0.65);
--ant-color-text-tertiary: rgba(255, 255, 255, 0.45);
--ant-color-bg-container: #141414;
--ant-color-bg-layout: #000000;
--ant-color-bg-elevated: #1f1f1f;
--ant-color-border: #424242;
--ant-color-border-secondary: #303030;
--ant-color-error: #ff4d4f;
--ant-color-success: #52c41a;
--ant-color-warning: #faad14;
--ant-color-primary: #1677ff;
--ant-color-primary-hover: #4096ff;
--ant-color-primary-bg: #1677ff1a;
--ant-color-text: rgba(255, 255, 255, 0.88);
--ant-color-text-secondary: rgba(255, 255, 255, 0.65);
--ant-color-text-tertiary: rgba(255, 255, 255, 0.45);
--ant-color-bg-container: #141414;
--ant-color-bg-layout: #000000;
--ant-color-bg-elevated: #1f1f1f;
--ant-color-border: #424242;
--ant-color-border-secondary: #303030;
--ant-color-error: #ff4d4f;
--ant-color-success: #52c41a;
--ant-color-warning: #faad14;
}
* {
box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
background-color: var(--ant-color-bg-layout);
color: var(--ant-color-text);
margin: 0;
min-width: 320px;
min-height: 100vh;
background-color: var(--ant-color-bg-layout);
color: var(--ant-color-text);
}
#app {
width: 100%;
height: 100vh;
overflow: hidden;
width: 100%;
height: 100vh;
overflow: hidden;
}
/* 链接样式 */
a {
color: var(--ant-color-primary);
text-decoration: none;
transition: color 0.3s ease;
color: var(--ant-color-primary);
text-decoration: none;
transition: color 0.3s ease;
}
a:hover {
color: var(--ant-color-primary-hover);
color: var(--ant-color-primary-hover);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--ant-color-bg-layout);
background: var(--ant-color-bg-layout);
}
::-webkit-scrollbar-thumb {
background: var(--ant-color-border);
border-radius: 3px;
background: var(--ant-color-border);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--ant-color-border-secondary);
background: var(--ant-color-border-secondary);
}

View File

@@ -18,4 +18,4 @@ export interface MirrorSource {
name: string
url: string
speed: number | null
}
}

View File

@@ -1,4 +1,3 @@
// 设置相关类型定义
export interface SettingsData {
Function: {
@@ -66,4 +65,4 @@ export interface UpdateSettingsResponse {
code: number
status: string
message: string
}
}

View File

@@ -38,7 +38,7 @@ const DEFAULT_CONFIG: FrontendConfig = {
gitInstalled: false,
backendExists: false,
dependenciesInstalled: false,
pipInstalled: false
pipInstalled: false,
}
// 读取配置(内部使用,不触发保存)
@@ -83,7 +83,8 @@ export async function getConfig(): Promise<FrontendConfig> {
const config = await getConfigInternal()
// 如果是从localStorage迁移的配置保存到文件并清理localStorage
const hasLocalStorage = localStorage.getItem('app-config') || localStorage.getItem('theme-settings')
const hasLocalStorage =
localStorage.getItem('app-config') || localStorage.getItem('theme-settings')
if (hasLocalStorage) {
try {
await window.electronAPI.saveConfig(config)
@@ -155,9 +156,13 @@ export async function saveThemeConfig(themeMode: ThemeMode, themeColor: ThemeCol
}
// 保存镜像源设置
export async function saveMirrorConfig(gitMirror: string, pythonMirror?: string, pipMirror?: string): Promise<void> {
export async function saveMirrorConfig(
gitMirror: string,
pythonMirror?: string,
pipMirror?: string
): Promise<void> {
const config: Partial<FrontendConfig> = { selectedGitMirror: gitMirror }
if (pythonMirror) config.selectedPythonMirror = pythonMirror
if (pipMirror) config.selectedPipMirror = pipMirror
await saveConfig(config)
}
}

View File

@@ -34,7 +34,7 @@ class Logger {
level,
message,
data,
component
component,
}
// 添加到内存日志
@@ -48,7 +48,7 @@ class Logger {
// 输出到控制台
if (this.logToConsole) {
const consoleMessage = `[${logEntry.timestamp}] [${level.toUpperCase()}] ${component ? `[${component}] ` : ''}${message}`
switch (level) {
case 'debug':
console.debug(consoleMessage, data)
@@ -133,7 +133,7 @@ class Logger {
return `[${log.timestamp}] [${log.level.toUpperCase()}]${componentStr} ${log.message}${dataStr}`
})
.join('\n')
return logText
}
@@ -142,14 +142,14 @@ class Logger {
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)
}
@@ -188,5 +188,5 @@ export default {
install(app: any) {
app.config.globalProperties.$logger = logger
app.provide('logger', logger)
}
}
},
}

View File

@@ -3,16 +3,16 @@
* 提供动态获取和更新镜像源配置的功能
*/
import {
ALL_MIRRORS,
API_ENDPOINTS,
DOWNLOAD_LINKS,
type MirrorConfig,
import {
ALL_MIRRORS,
API_ENDPOINTS,
DOWNLOAD_LINKS,
type MirrorConfig,
type MirrorCategory,
getMirrorUrl,
updateMirrorSpeed,
sortMirrorsBySpeed,
getFastestMirror
getFastestMirror,
} from '@/config/mirrors'
/**
@@ -83,7 +83,7 @@ export class MirrorManager {
const response = await fetch(url, {
method: 'HEAD',
signal: controller.signal,
cache: 'no-cache'
cache: 'no-cache',
})
clearTimeout(timeoutId)
@@ -103,7 +103,7 @@ export class MirrorManager {
*/
async testAllMirrorSpeeds(type: keyof MirrorCategory): Promise<MirrorConfig[]> {
const mirrors = this.getMirrors(type)
const promises = mirrors.map(async (mirror) => {
const promises = mirrors.map(async mirror => {
const speed = await this.testMirrorSpeed(mirror.url)
this.updateMirrorSpeed(type, mirror.key, speed)
return { ...mirror, speed }
@@ -149,15 +149,15 @@ export class MirrorManager {
const response = await fetch(`${apiUrl}/api/mirrors`)
if (response.ok) {
const config = await response.json()
// 更新各类镜像源配置
if (config.git) this.updateMirrorConfig('git', config.git)
if (config.python) this.updateMirrorConfig('python', config.python)
if (config.pip) this.updateMirrorConfig('pip', config.pip)
// 更新API端点
if (config.apiEndpoints) this.updateApiEndpoints(config.apiEndpoints)
console.log('镜像源配置已从API更新')
}
} catch (error) {
@@ -172,7 +172,7 @@ export class MirrorManager {
return {
mirrors: this.mirrorConfigs,
apiEndpoints: this.apiEndpoints,
downloadLinks: this.downloadLinks
downloadLinks: this.downloadLinks,
}
}
}
@@ -182,6 +182,9 @@ export const mirrorManager = MirrorManager.getInstance()
// 导出便捷函数
export const getMirrors = (type: keyof MirrorCategory) => mirrorManager.getMirrors(type)
export const getMirrorUrlByManager = (type: keyof MirrorCategory, key: string) => mirrorManager.getMirrorUrl(type, key)
export const testMirrorSpeed = (url: string, timeout?: number) => mirrorManager.testMirrorSpeed(url, timeout)
export const testAllMirrorSpeeds = (type: keyof MirrorCategory) => mirrorManager.testAllMirrorSpeeds(type)
export const getMirrorUrlByManager = (type: keyof MirrorCategory, key: string) =>
mirrorManager.getMirrorUrl(type, key)
export const testMirrorSpeed = (url: string, timeout?: number) =>
mirrorManager.testMirrorSpeed(url, timeout)
export const testAllMirrorSpeeds = (type: keyof MirrorCategory) =>
mirrorManager.testAllMirrorSpeeds(type)

View File

@@ -485,4 +485,4 @@ onMounted(() => {
margin-right: 8px;
}
}
</style>
</style>

View File

@@ -4,14 +4,14 @@
<AdminCheck v-if="!isAdmin" />
<!-- 自动初始化模式 -->
<AutoMode
<AutoMode
v-if="autoMode"
:on-switch-to-manual="switchToManualMode"
:on-auto-complete="enterApp"
/>
<!-- 手动初始化模式 -->
<ManualMode
<ManualMode
v-else
ref="manualModeRef"
:python-installed="pythonInstalled"
@@ -38,7 +38,7 @@ import ManualMode from '@/components/initialization/ManualMode.vue'
import type { DownloadProgress } from '@/types/initialization'
const router = useRouter()
const logger = createComponentLogger('InitializationNew')
const logger = createComponentLogger('Initialization')
// 基础状态
const isAdmin = ref(true)
@@ -81,7 +81,7 @@ async function checkCriticalFiles() {
try {
logger.info('开始检查关键文件存在性')
console.log('🔍 正在调用 window.electronAPI.checkCriticalFiles()...')
// 检查API是否存在
if (!window.electronAPI.checkCriticalFiles) {
console.warn('⚠️ window.electronAPI.checkCriticalFiles 不存在,使用配置文件状态')
@@ -91,33 +91,33 @@ async function checkCriticalFiles() {
pythonExists: config.pythonInstalled || false,
pipExists: config.pipInstalled || false,
gitExists: config.gitInstalled || false,
mainPyExists: config.backendExists || false
mainPyExists: config.backendExists || false,
}
}
// 检查关键文件
const criticalFiles = await window.electronAPI.checkCriticalFiles()
console.log('🔍 electronAPI.checkCriticalFiles() 原始返回结果:', criticalFiles)
console.log('🔍 详细检查结果:')
console.log(' - pythonExists:', criticalFiles.pythonExists, typeof criticalFiles.pythonExists)
console.log(' - pipExists:', criticalFiles.pipExists, typeof criticalFiles.pipExists)
console.log(' - gitExists:', criticalFiles.gitExists, typeof criticalFiles.gitExists)
console.log(' - mainPyExists:', criticalFiles.mainPyExists, typeof criticalFiles.mainPyExists)
const result = {
pythonExists: criticalFiles.pythonExists,
pipExists: criticalFiles.pipExists,
pipExists: criticalFiles.pipExists,
gitExists: criticalFiles.gitExists,
mainPyExists: criticalFiles.mainPyExists
mainPyExists: criticalFiles.mainPyExists,
}
console.log('🔍 最终返回结果:', result)
return result
} catch (error) {
logger.error('检查关键文件失败', error)
console.error('❌ 检查关键文件失败,使用配置文件状态:', error)
// 如果检查失败,从配置文件读取状态
try {
const config = await getConfig()
@@ -125,13 +125,13 @@ async function checkCriticalFiles() {
pythonInstalled: config.pythonInstalled,
pipInstalled: config.pipInstalled,
gitInstalled: config.gitInstalled,
backendExists: config.backendExists
backendExists: config.backendExists,
})
return {
pythonExists: config.pythonInstalled || false,
pipExists: config.pipInstalled || false,
gitExists: config.gitInstalled || false,
mainPyExists: config.backendExists || false
mainPyExists: config.backendExists || false,
}
} catch (configError) {
console.error('❌ 读取配置文件也失败了:', configError)
@@ -139,7 +139,7 @@ async function checkCriticalFiles() {
pythonExists: false,
pipExists: false,
gitExists: false,
mainPyExists: false
mainPyExists: false,
}
}
}
@@ -149,46 +149,47 @@ async function checkCriticalFiles() {
async function checkEnvironment() {
try {
logger.info('开始检查环境状态')
// 只检查关键exe文件是否存在
const criticalFiles = await checkCriticalFiles()
console.log('关键文件检查结果:', criticalFiles)
// 直接根据exe文件存在性设置状态
pythonInstalled.value = criticalFiles.pythonExists
pipInstalled.value = criticalFiles.pipExists
gitInstalled.value = criticalFiles.gitExists
backendExists.value = criticalFiles.mainPyExists
// 检查配置文件中的依赖安装状态
const config = await getConfig()
dependenciesInstalled.value = config.dependenciesInstalled || false
console.log('📊 最终状态设置:')
console.log(' - pythonInstalled:', pythonInstalled.value)
console.log(' - pipInstalled:', pipInstalled.value)
console.log(' - gitInstalled:', gitInstalled.value)
console.log(' - backendExists:', backendExists.value)
console.log(' - dependenciesInstalled:', dependenciesInstalled.value)
// 检查是否第一次启动
const isFirst = config.isFirstLaunch
console.log('是否第一次启动:', isFirst)
// 检查所有关键exe文件是否都存在
const allExeFilesExist = criticalFiles.pythonExists &&
criticalFiles.pipExists &&
criticalFiles.gitExists &&
criticalFiles.mainPyExists
const allExeFilesExist =
criticalFiles.pythonExists &&
criticalFiles.pipExists &&
criticalFiles.gitExists &&
criticalFiles.mainPyExists
console.log('关键exe文件状态检查:')
console.log('- python.exe存在:', criticalFiles.pythonExists)
console.log('- pip.exe存在:', criticalFiles.pipExists)
console.log('- git.exe存在:', criticalFiles.gitExists)
console.log('- main.py存在:', criticalFiles.mainPyExists)
console.log('- 所有关键文件存在:', allExeFilesExist)
// 检查是否应该进入自动模式
console.log('自动模式判断条件:')
console.log('- 不是第一次启动:', !isFirst)
@@ -203,8 +204,15 @@ async function checkEnvironment() {
} else {
logger.info('需要进入手动模式进行配置')
console.log('进入手动模式')
console.log('原因: isFirst =', isFirst, ', config.init =', config.init, ', allExeFilesExist =', allExeFilesExist)
console.log(
'原因: isFirst =',
isFirst,
', config.init =',
config.init,
', allExeFilesExist =',
allExeFilesExist
)
// 如果关键文件缺失,重置初始化状态
if (!allExeFilesExist && config.init) {
console.log('检测到关键exe文件缺失重置初始化状态')
@@ -215,7 +223,7 @@ async function checkEnvironment() {
const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}`
logger.error('环境检查失败', error)
console.error('环境检查失败:', error)
// 检查失败时强制进入手动模式
autoMode.value = false
}
@@ -241,27 +249,27 @@ function handleProgressUpdate(progress: DownloadProgress) {
onMounted(async () => {
console.log('初始化页面 onMounted 开始')
// 测试配置系统
try {
console.log('测试配置系统...')
const testConfig = await getConfig()
console.log('当前配置:', testConfig)
// 测试保存配置
await saveConfig({ isFirstLaunch: false })
console.log('测试配置保存成功')
// 重新读取配置验证
const updatedConfig = await getConfig()
console.log('更新后的配置:', updatedConfig)
} catch (error) {
console.error('配置系统测试失败:', error)
}
// 检查管理员权限
await checkAdminPermission()
if (isAdmin.value) {
// 延迟检查环境,确保页面完全加载
setTimeout(async () => {
@@ -269,7 +277,7 @@ onMounted(async () => {
await checkEnvironment()
}, 100)
}
window.electronAPI.onDownloadProgress(handleProgressUpdate)
console.log('初始化页面 onMounted 完成')
})

View File

@@ -4,7 +4,7 @@
<h2>系统日志</h2>
<p>查看应用运行日志支持搜索过滤和导出功能</p>
</div>
<div class="logs-content">
<LogViewer />
</div>
@@ -52,4 +52,4 @@ onMounted(() => {
flex: 1;
min-height: 0;
}
</style>
</style>

View File

@@ -2,7 +2,7 @@
<div v-if="loading" class="loading-box">
<a-spin tip="加载中,请稍候..." size="large" />
</div>
<div v-else class="queue-content">
<!-- 队列头部 -->
<div class="queue-header">
@@ -27,7 +27,11 @@
<!-- 空状态 -->
<div v-if="!queueList.length || !currentQueueData" class="empty-state">
<div class="empty-content empty-content-fancy" @click="handleAddQueue" style="cursor: pointer">
<div
class="empty-content empty-content-fancy"
@click="handleAddQueue"
style="cursor: pointer"
>
<div class="empty-icon">
<PlusOutlined />
</div>
@@ -71,21 +75,21 @@
/>
</div>
</div>
<!-- <div class="section-controls">-->
<!-- <a-space>-->
<!-- <span class="status-label">状态</span>-->
<!-- <a-switch -->
<!-- v-model:checked="currentQueueEnabled" -->
<!-- @change="onQueueStatusChange"-->
<!-- checked-children="启用"-->
<!-- un-checked-children="禁用"-->
<!-- />-->
<!-- </a-space>-->
<!-- </div>-->
<!-- <div class="section-controls">-->
<!-- <a-space>-->
<!-- <span class="status-label">状态</span>-->
<!-- <a-switch -->
<!-- v-model:checked="currentQueueEnabled" -->
<!-- @change="onQueueStatusChange"-->
<!-- checked-children="启用"-->
<!-- un-checked-children="禁用"-->
<!-- />-->
<!-- </a-space>-->
<!-- </div>-->
</div>
<!-- 定时项组件 -->
<TimeSetManager
<TimeSetManager
v-if="activeQueueId && currentQueueData"
:queue-id="activeQueueId"
:time-sets="currentTimeSets"
@@ -93,7 +97,7 @@
/>
<!-- 队列项组件 -->
<QueueItemManager
<QueueItemManager
v-if="activeQueueId && currentQueueData"
:queue-id="activeQueueId"
:queue-items="currentQueueItems"
@@ -147,7 +151,7 @@ const fetchQueues = async () => {
if (response.code === 200) {
// 处理队列数据
console.log('API Response:', response) // 调试日志
if (response.index && response.index.length > 0) {
queueList.value = response.index.map((item: any, index: number) => {
try {
@@ -155,19 +159,19 @@ const fetchQueues = async () => {
const queueId = item.uid
const queueName = response.data[queueId]?.Info?.Name || `队列 ${index + 1}`
console.log('Queue ID:', queueId, 'Name:', queueName, 'Type:', typeof queueId) // 调试日志
return {
id: queueId,
name: queueName
return {
id: queueId,
name: queueName,
}
} catch (itemError) {
console.warn('解析队列项失败:', itemError, item)
return {
id: `queue_${index}`,
name: `队列 ${index + 1}`
name: `队列 ${index + 1}`,
}
}
})
// 如果有队列且没有选中的队列,默认选中第一个
if (queueList.value.length > 0 && !activeQueueId.value) {
activeQueueId.value = queueList.value[0].id
@@ -201,7 +205,7 @@ const fetchQueues = async () => {
// 加载队列数据
const loadQueueData = async (queueId: string) => {
if (!queueId) return
try {
const response = await Service.getQueuesApiQueueGetPost({})
currentQueueData.value = response.data
@@ -209,7 +213,7 @@ const loadQueueData = async (queueId: string) => {
// 根据API响应数据更新队列信息
if (response.data && response.data[queueId]) {
const queueData = response.data[queueId]
// 更新队列名称和状态
const currentQueue = queueList.value.find(queue => queue.id === queueId)
if (currentQueue) {
@@ -220,14 +224,14 @@ const loadQueueData = async (queueId: string) => {
// 使用nextTick确保DOM更新后再加载数据
await nextTick()
await new Promise(resolve => setTimeout(resolve, 50))
// 加载定时项和队列项数据 - 添加错误处理
try {
await refreshTimeSets()
} catch (timeError) {
console.error('刷新定时项失败:', timeError)
}
try {
await refreshQueueItems()
} catch (itemError) {
@@ -246,7 +250,7 @@ const refreshTimeSets = async () => {
currentTimeSets.value = []
return
}
try {
// 重新从API获取最新的队列数据
const response = await Service.getQueuesApiQueueGetPost({})
@@ -255,42 +259,42 @@ const refreshTimeSets = async () => {
currentTimeSets.value = []
return
}
// 更新缓存的队列数据
currentQueueData.value = response.data
// 从最新的队列数据中获取定时项信息
if (response.data && response.data[activeQueueId.value]) {
const queueData = response.data[activeQueueId.value]
const timeSets: any[] = []
// 检查是否有TimeSet配置
if (queueData?.SubConfigsInfo?.TimeSet) {
const timeSetConfig = queueData.SubConfigsInfo.TimeSet
// 遍历instances数组获取所有定时项ID
if (Array.isArray(timeSetConfig.instances)) {
timeSetConfig.instances.forEach((instance: any) => {
try {
const timeSetId = instance?.uid
if (!timeSetId) return
const timeSetData = timeSetConfig[timeSetId]
if (timeSetData?.Info) {
// 解析时间字符串 "HH:mm" - 修复字段名
const originalTimeString = timeSetData.Info.Set || timeSetData.Info.Time || '00:00'
const [hours = 0, minutes = 0] = originalTimeString.split(':').map(Number)
// 创建标准化的时间字符串
const validHours = Math.max(0, Math.min(23, hours))
const validMinutes = Math.max(0, Math.min(59, minutes))
const timeString = `${validHours.toString().padStart(2, '0')}:${validMinutes.toString().padStart(2, '0')}`
timeSets.push({
id: timeSetId,
time: timeString,
enabled: Boolean(timeSetData.Info.Enabled),
description: timeSetData.Info.Description || ''
description: timeSetData.Info.Description || '',
})
}
} catch (itemError) {
@@ -299,7 +303,7 @@ const refreshTimeSets = async () => {
})
}
}
// 使用nextTick确保数据更新不会导致渲染问题
await nextTick()
currentTimeSets.value = [...timeSets]
@@ -320,7 +324,7 @@ const refreshQueueItems = async () => {
currentQueueItems.value = []
return
}
try {
// 重新从API获取最新的队列数据
const response = await Service.getQueuesApiQueueGetPost({})
@@ -329,31 +333,31 @@ const refreshQueueItems = async () => {
currentQueueItems.value = []
return
}
// 更新缓存的队列数据
currentQueueData.value = response.data
// 从最新的队列数据中获取队列项信息
if (response.data && response.data[activeQueueId.value]) {
const queueData = response.data[activeQueueId.value]
const queueItems: any[] = []
// 检查是否有QueueItem配置
if (queueData?.SubConfigsInfo?.QueueItem) {
const queueItemConfig = queueData.SubConfigsInfo.QueueItem
// 遍历instances数组获取所有队列项ID
if (Array.isArray(queueItemConfig.instances)) {
queueItemConfig.instances.forEach((instance: any) => {
try {
const queueItemId = instance?.uid
if (!queueItemId) return
const queueItemData = queueItemConfig[queueItemId]
if (queueItemData?.Info) {
queueItems.push({
id: queueItemId,
script: queueItemData.Info.ScriptId || ''
script: queueItemData.Info.ScriptId || '',
})
}
} catch (itemError) {
@@ -362,7 +366,7 @@ const refreshQueueItems = async () => {
})
}
}
// 使用nextTick确保数据更新不会导致渲染问题
await nextTick()
currentQueueItems.value = [...queueItems]
@@ -382,7 +386,8 @@ const onQueueNameBlur = () => {
if (activeQueueId.value) {
const currentQueue = queueList.value.find(queue => queue.id === activeQueueId.value)
if (currentQueue) {
currentQueue.name = currentQueueName.value || `队列 ${queueList.value.indexOf(currentQueue) + 1}`
currentQueue.name =
currentQueueName.value || `队列 ${queueList.value.indexOf(currentQueue) + 1}`
}
}
}
@@ -409,7 +414,7 @@ const onTabEdit = async (targetKey: string | MouseEvent, action: 'add' | 'remove
const handleAddQueue = async () => {
try {
const response = await Service.addQueueApiQueueAddPost()
if (response.code === 200 && response.queueId) {
const defaultName = `队列 ${queueList.value.length + 1}`
const newQueue = {
@@ -438,7 +443,7 @@ const handleAddQueue = async () => {
const handleRemoveQueue = async (queueId: string) => {
try {
const response = await Service.deleteQueueApiQueueDeletePost({ queueId })
if (response.code === 200) {
const index = queueList.value.findIndex(queue => queue.id === queueId)
if (index > -1) {
@@ -465,12 +470,12 @@ const handleRemoveQueue = async (queueId: string) => {
// 队列切换
const onQueueChange = async (queueId: string) => {
if (!queueId) return
try {
// 清空当前数据,避免渲染问题
currentTimeSets.value = []
currentQueueItems.value = []
await loadQueueData(queueId)
} catch (error) {
console.error('队列切换失败:', error)
@@ -505,9 +510,9 @@ const saveQueueData = async () => {
const response = await Service.updateQueueApiQueueUpdatePost({
queueId: activeQueueId.value,
data: queueData
data: queueData,
})
if (response.code !== 200) {
throw new Error(response.message || '保存失败')
}
@@ -701,7 +706,9 @@ onMounted(async () => {
}
.empty-content-fancy {
transition: box-shadow 0.3s, transform 0.2s;
transition:
box-shadow 0.3s,
transform 0.2s;
border: none;
border-radius: 24px;
}

View File

@@ -154,28 +154,27 @@
<!-- 实时日志 (60%) -->
<a-col :span="14">
<a-card size="small" style="height: 100%" title="实时日志">
<div class="realtime-logs-panel">
<!-- <a-row justify="space-between" align="middle" style="margin-bottom: 8px">-->
<!-- &lt;!&ndash; 左侧标题 &ndash;&gt;-->
<!-- <a-col :span="12">-->
<!-- <div class="log-title">实时日志</div>-->
<!-- </a-col>-->
<!-- <a-row justify="space-between" align="middle" style="margin-bottom: 8px">-->
<!-- &lt;!&ndash; 左侧标题 &ndash;&gt;-->
<!-- <a-col :span="12">-->
<!-- <div class="log-title">实时日志</div>-->
<!-- </a-col>-->
<!-- &lt;!&ndash; 右侧清空按钮 &ndash;&gt;-->
<!-- <a-col :span="12" style="text-align: right">-->
<!-- <div class="clear-button">-->
<!-- <a-button-->
<!-- type="default"-->
<!-- size="small"-->
<!-- @click="clearTaskOutput(task.websocketId)"-->
<!-- :icon="h(ClearOutlined)"-->
<!-- >-->
<!-- 清空-->
<!-- </a-button>-->
<!-- </div>-->
<!-- </a-col>-->
<!-- </a-row>-->
<!-- &lt;!&ndash; 右侧清空按钮 &ndash;&gt;-->
<!-- <a-col :span="12" style="text-align: right">-->
<!-- <div class="clear-button">-->
<!-- <a-button-->
<!-- type="default"-->
<!-- size="small"-->
<!-- @click="clearTaskOutput(task.websocketId)"-->
<!-- :icon="h(ClearOutlined)"-->
<!-- >-->
<!-- 清空-->
<!-- </a-button>-->
<!-- </div>-->
<!-- </a-col>-->
<!-- </a-row>-->
<div
class="panel-content log-content"
:ref="el => setOutputRef(el as HTMLElement, task.websocketId)"
@@ -624,17 +623,17 @@ const handleWebSocketMessage = (task: RunningTask, data: any) => {
case 'Info':
// 通知信息
let level = 'info';
let content = '未知通知';
let level = 'info'
let content = '未知通知'
// 检查数据中是否有 Error 字段
if (data.data?.Error) {
// 如果是错误信息,设置为 error 级别
level = 'error';
content = data.data.Error; // 错误信息内容
level = 'error'
content = data.data.Error // 错误信息内容
} else {
// 如果没有 Error 字段,继续处理 val 或 message 字段
content = data.data?.val || data.data?.message || '未知通知';
content = data.data?.val || data.data?.message || '未知通知'
}
addTaskLog(task, content, level as any)
@@ -1023,4 +1022,4 @@ onUnmounted(() => {
.log-error .log-message {
color: var(--ant-color-error);
}
</style>
</style>

View File

@@ -184,7 +184,7 @@ const handleThemeColorChange = (value: SelectValue) => {
const openDevTools = () => {
if ((window as any).electronAPI) {
; (window as any).electronAPI.openDevTools()
;(window as any).electronAPI.openDevTools()
}
}
@@ -204,21 +204,31 @@ onMounted(() => {
<div class="setting-item">
<h4>主题模式</h4>
<p class="setting-description">选择应用程序的外观主题</p>
<Radio.Group :value="themeMode" @change="handleThemeModeChange" :options="themeModeOptions" />
<Radio.Group
:value="themeMode"
@change="handleThemeModeChange"
:options="themeModeOptions"
/>
</div>
<Divider />
<div class="setting-item">
<h4>主题色</h4>
<p class="setting-description">选择应用程序的主色调</p>
<Select :value="themeColor" @change="handleThemeColorChange" style="width: 200px">
<Select.Option v-for="option in themeColorOptions" :key="option.value" :value="option.value">
<Select.Option
v-for="option in themeColorOptions"
:key="option.value"
:value="option.value"
>
<div style="display: flex; align-items: center; gap: 8px">
<div :style="{
width: '16px',
height: '16px',
borderRadius: '50%',
backgroundColor: option.color,
}" />
<div
:style="{
width: '16px',
height: '16px',
borderRadius: '50%',
backgroundColor: option.color,
}"
/>
{{ option.label }}
</div>
</Select.Option>
@@ -235,9 +245,12 @@ onMounted(() => {
<div class="setting-item">
<h4>Boss键</h4>
<p class="setting-description">设置快速隐藏窗口的快捷键</p>
<Input v-model:value="settings.Function.BossKey"
@blur="handleSettingChange('Function', 'BossKey', settings.Function.BossKey)" placeholder="例如: Ctrl+H"
style="width: 300px" />
<Input
v-model:value="settings.Function.BossKey"
@blur="handleSettingChange('Function', 'BossKey', settings.Function.BossKey)"
placeholder="例如: Ctrl+H"
style="width: 300px"
/>
</div>
<Divider />
@@ -245,9 +258,12 @@ onMounted(() => {
<div class="setting-item">
<h4>历史记录保留时间</h4>
<p class="setting-description">设置历史记录的保留时间</p>
<Select v-model:value="settings.Function.HistoryRetentionTime"
@change="(value) => handleSettingChange('Function', 'HistoryRetentionTime', value)"
:options="historyRetentionOptions" style="width: 200px" />
<Select
v-model:value="settings.Function.HistoryRetentionTime"
@change="value => handleSettingChange('Function', 'HistoryRetentionTime', value)"
:options="historyRetentionOptions"
style="width: 200px"
/>
</div>
<Divider />
@@ -255,9 +271,12 @@ onMounted(() => {
<div class="setting-item">
<h4>主页图像模式</h4>
<p class="setting-description">选择主页显示的图像模式</p>
<Select v-model:value="settings.Function.HomeImageMode"
@change="(value) => handleSettingChange('Function', 'HomeImageMode', value)"
:options="homeImageModeOptions" style="width: 200px" />
<Select
v-model:value="settings.Function.HomeImageMode"
@change="value => handleSettingChange('Function', 'HomeImageMode', value)"
:options="homeImageModeOptions"
style="width: 200px"
/>
</div>
<Divider />
@@ -266,28 +285,40 @@ onMounted(() => {
<h4>功能开关</h4>
<Space direction="vertical" size="middle">
<div class="switch-item">
<Switch v-model:checked="settings.Function.IfAllowSleep"
@change="(checked) => handleSettingChange('Function', 'IfAllowSleep', checked)" />
<Switch
v-model:checked="settings.Function.IfAllowSleep"
@change="checked => handleSettingChange('Function', 'IfAllowSleep', checked)"
/>
<span class="switch-label">启动时阻止系统休眠</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Function.IfSilence"
@change="(checked) => handleSettingChange('Function', 'IfSilence', checked)" />
<Switch
v-model:checked="settings.Function.IfSilence"
@change="checked => handleSettingChange('Function', 'IfSilence', checked)"
/>
<span class="switch-label">静默模式</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Function.UnattendedMode"
@change="(checked) => handleSettingChange('Function', 'UnattendedMode', checked)" />
<Switch
v-model:checked="settings.Function.UnattendedMode"
@change="checked => handleSettingChange('Function', 'UnattendedMode', checked)"
/>
<span class="switch-label">无人值守模式</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Function.IfAgreeBilibili"
@change="(checked) => handleSettingChange('Function', 'IfAgreeBilibili', checked)" />
<Switch
v-model:checked="settings.Function.IfAgreeBilibili"
@change="checked => handleSettingChange('Function', 'IfAgreeBilibili', checked)"
/>
<span class="switch-label">托管Bilibili游戏隐私政策</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Function.IfSkipMumuSplashAds"
@change="(checked) => handleSettingChange('Function', 'IfSkipMumuSplashAds', checked)" />
<Switch
v-model:checked="settings.Function.IfSkipMumuSplashAds"
@change="
checked => handleSettingChange('Function', 'IfSkipMumuSplashAds', checked)
"
/>
<span class="switch-label">跳过MuMu模拟器启动广告</span>
</div>
</Space>
@@ -303,9 +334,12 @@ onMounted(() => {
<div class="setting-item">
<h4>任务结果推送时间</h4>
<p class="setting-description">设置何时推送任务执行结果</p>
<Select v-model:value="settings.Notify.SendTaskResultTime"
@change="(value) => handleSettingChange('Notify', 'SendTaskResultTime', value)"
:options="sendTaskResultTimeOptions" style="width: 200px" />
<Select
v-model:value="settings.Notify.SendTaskResultTime"
@change="value => handleSettingChange('Notify', 'SendTaskResultTime', value)"
:options="sendTaskResultTimeOptions"
style="width: 200px"
/>
</div>
<Divider />
@@ -314,18 +348,24 @@ onMounted(() => {
<h4>通知开关</h4>
<Space direction="vertical" size="middle">
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfSendStatistic"
@change="(checked) => handleSettingChange('Notify', 'IfSendStatistic', checked)" />
<Switch
v-model:checked="settings.Notify.IfSendStatistic"
@change="checked => handleSettingChange('Notify', 'IfSendStatistic', checked)"
/>
<span class="switch-label">发送统计信息</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfSendSixStar"
@change="(checked) => handleSettingChange('Notify', 'IfSendSixStar', checked)" />
<Switch
v-model:checked="settings.Notify.IfSendSixStar"
@change="checked => handleSettingChange('Notify', 'IfSendSixStar', checked)"
/>
<span class="switch-label">发送六星通知</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfPushPlyer"
@change="(checked) => handleSettingChange('Notify', 'IfPushPlyer', checked)" />
<Switch
v-model:checked="settings.Notify.IfPushPlyer"
@change="checked => handleSettingChange('Notify', 'IfPushPlyer', checked)"
/>
<span class="switch-label">启用PushPlus推送</span>
</div>
</Space>
@@ -337,33 +377,61 @@ onMounted(() => {
<h4>邮件通知</h4>
<Space direction="vertical" size="middle" style="width: 100%">
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfSendMail"
@change="(checked) => handleSettingChange('Notify', 'IfSendMail', checked)" />
<Switch
v-model:checked="settings.Notify.IfSendMail"
@change="checked => handleSettingChange('Notify', 'IfSendMail', checked)"
/>
<span class="switch-label">启用邮件通知</span>
</div>
<div class="input-group">
<label>SMTP服务器地址</label>
<Input v-model:value="settings.Notify.SMTPServerAddress"
@blur="handleSettingChange('Notify', 'SMTPServerAddress', settings.Notify.SMTPServerAddress)"
placeholder="例如: smtp.gmail.com" style="width: 300px" />
<Input
v-model:value="settings.Notify.SMTPServerAddress"
@blur="
handleSettingChange(
'Notify',
'SMTPServerAddress',
settings.Notify.SMTPServerAddress
)
"
placeholder="例如: smtp.gmail.com"
style="width: 300px"
/>
</div>
<div class="input-group">
<label>授权码</label>
<Input.Password v-model:value="settings.Notify.AuthorizationCode"
@blur="handleSettingChange('Notify', 'AuthorizationCode', settings.Notify.AuthorizationCode)"
placeholder="邮箱授权码" style="width: 300px" />
<Input.Password
v-model:value="settings.Notify.AuthorizationCode"
@blur="
handleSettingChange(
'Notify',
'AuthorizationCode',
settings.Notify.AuthorizationCode
)
"
placeholder="邮箱授权码"
style="width: 300px"
/>
</div>
<div class="input-group">
<label>发件人地址</label>
<Input v-model:value="settings.Notify.FromAddress"
@blur="handleSettingChange('Notify', 'FromAddress', settings.Notify.FromAddress)"
placeholder="发件人邮箱地址" style="width: 300px" />
<Input
v-model:value="settings.Notify.FromAddress"
@blur="
handleSettingChange('Notify', 'FromAddress', settings.Notify.FromAddress)
"
placeholder="发件人邮箱地址"
style="width: 300px"
/>
</div>
<div class="input-group">
<label>收件人地址</label>
<Input v-model:value="settings.Notify.ToAddress"
@blur="handleSettingChange('Notify', 'ToAddress', settings.Notify.ToAddress)" placeholder="收件人邮箱地址"
style="width: 300px" />
<Input
v-model:value="settings.Notify.ToAddress"
@blur="handleSettingChange('Notify', 'ToAddress', settings.Notify.ToAddress)"
placeholder="收件人邮箱地址"
style="width: 300px"
/>
</div>
</Space>
</div>
@@ -374,15 +442,22 @@ onMounted(() => {
<h4>Server酱通知</h4>
<Space direction="vertical" size="middle" style="width: 100%">
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfServerChan"
@change="(checked) => handleSettingChange('Notify', 'IfServerChan', checked)" />
<Switch
v-model:checked="settings.Notify.IfServerChan"
@change="checked => handleSettingChange('Notify', 'IfServerChan', checked)"
/>
<span class="switch-label">启用Server酱通知</span>
</div>
<div class="input-group">
<label>Server酱Key</label>
<Input v-model:value="settings.Notify.ServerChanKey"
@blur="handleSettingChange('Notify', 'ServerChanKey', settings.Notify.ServerChanKey)"
placeholder="Server酱推送Key" style="width: 300px" />
<Input
v-model:value="settings.Notify.ServerChanKey"
@blur="
handleSettingChange('Notify', 'ServerChanKey', settings.Notify.ServerChanKey)
"
placeholder="Server酱推送Key"
style="width: 300px"
/>
</div>
</Space>
</div>
@@ -393,15 +468,28 @@ onMounted(() => {
<h4>企业微信机器人</h4>
<Space direction="vertical" size="middle" style="width: 100%">
<div class="switch-item">
<Switch v-model:checked="settings.Notify.IfCompanyWebHookBot"
@change="(checked) => handleSettingChange('Notify', 'IfCompanyWebHookBot', checked)" />
<Switch
v-model:checked="settings.Notify.IfCompanyWebHookBot"
@change="
checked => handleSettingChange('Notify', 'IfCompanyWebHookBot', checked)
"
/>
<span class="switch-label">启用企业微信机器人</span>
</div>
<div class="input-group">
<label>Webhook URL</label>
<Input v-model:value="settings.Notify.CompanyWebHookBotUrl"
@blur="handleSettingChange('Notify', 'CompanyWebHookBotUrl', settings.Notify.CompanyWebHookBotUrl)"
placeholder="企业微信机器人Webhook地址" style="width: 400px" />
<Input
v-model:value="settings.Notify.CompanyWebHookBotUrl"
@blur="
handleSettingChange(
'Notify',
'CompanyWebHookBotUrl',
settings.Notify.CompanyWebHookBotUrl
)
"
placeholder="企业微信机器人Webhook地址"
style="width: 400px"
/>
</div>
</Space>
</div>
@@ -416,8 +504,10 @@ onMounted(() => {
<div class="setting-item">
<h4>自动更新</h4>
<p class="setting-description">是否启用自动更新功能</p>
<Switch v-model:checked="settings.Update.IfAutoUpdate"
@change="(checked) => handleSettingChange('Update', 'IfAutoUpdate', checked)" />
<Switch
v-model:checked="settings.Update.IfAutoUpdate"
@change="checked => handleSettingChange('Update', 'IfAutoUpdate', checked)"
/>
</div>
<Divider />
@@ -425,9 +515,12 @@ onMounted(() => {
<div class="setting-item">
<h4>更新类型</h4>
<p class="setting-description">选择更新版本类型</p>
<Select v-model:value="settings.Update.UpdateType"
@change="(value) => handleSettingChange('Update', 'UpdateType', value)" :options="updateTypeOptions"
style="width: 200px" />
<Select
v-model:value="settings.Update.UpdateType"
@change="value => handleSettingChange('Update', 'UpdateType', value)"
:options="updateTypeOptions"
style="width: 200px"
/>
</div>
<Divider />
@@ -435,9 +528,13 @@ onMounted(() => {
<div class="setting-item">
<h4>下载线程数</h4>
<p class="setting-description">设置下载时使用的线程数量 (1-32)</p>
<InputNumber v-model:value="settings.Update.ThreadNumb"
@change="(value) => handleSettingChange('Update', 'ThreadNumb', value)" :min="1" :max="32"
style="width: 120px" />
<InputNumber
v-model:value="settings.Update.ThreadNumb"
@change="value => handleSettingChange('Update', 'ThreadNumb', value)"
:min="1"
:max="32"
style="width: 120px"
/>
</div>
<Divider />
@@ -447,9 +544,14 @@ onMounted(() => {
<Space direction="vertical" size="middle" style="width: 100%">
<div class="input-group">
<label>代理地址</label>
<Input v-model:value="settings.Update.ProxyAddress"
@blur="handleSettingChange('Update', 'ProxyAddress', settings.Update.ProxyAddress)"
placeholder="例如: http://127.0.0.1:7890" style="width: 300px" />
<Input
v-model:value="settings.Update.ProxyAddress"
@blur="
handleSettingChange('Update', 'ProxyAddress', settings.Update.ProxyAddress)
"
placeholder="例如: http://127.0.0.1:7890"
style="width: 300px"
/>
</div>
</Space>
</div>
@@ -459,9 +561,14 @@ onMounted(() => {
<div class="setting-item">
<h4>Mirror酱 CDK</h4>
<p class="setting-description">设置Mirror酱CDK</p>
<Input v-model:value="settings.Update.MirrorChyanCDK"
@blur="handleSettingChange('Update', 'MirrorChyanCDK', settings.Update.MirrorChyanCDK)"
placeholder="镜像CDK" style="width: 300px" />
<Input
v-model:value="settings.Update.MirrorChyanCDK"
@blur="
handleSettingChange('Update', 'MirrorChyanCDK', settings.Update.MirrorChyanCDK)
"
placeholder="镜像CDK"
style="width: 300px"
/>
</div>
</Space>
</Card>
@@ -474,8 +581,10 @@ onMounted(() => {
<div class="setting-item">
<h4>开机自启</h4>
<p class="setting-description">是否在系统启动时自动启动应用</p>
<Switch v-model:checked="settings.Start.IfSelfStart"
@change="(checked) => handleSettingChange('Start', 'IfSelfStart', checked)" />
<Switch
v-model:checked="settings.Start.IfSelfStart"
@change="checked => handleSettingChange('Start', 'IfSelfStart', checked)"
/>
</div>
<Divider />
@@ -483,8 +592,10 @@ onMounted(() => {
<div class="setting-item">
<h4>启动后直接最小化</h4>
<p class="setting-description">启动后是否直接最小化到系统托盘</p>
<Switch v-model:checked="settings.Start.IfMinimizeDirectly"
@change="(checked) => handleSettingChange('Start', 'IfMinimizeDirectly', checked)" />
<Switch
v-model:checked="settings.Start.IfMinimizeDirectly"
@change="checked => handleSettingChange('Start', 'IfMinimizeDirectly', checked)"
/>
</div>
</Space>
</Card>
@@ -498,13 +609,17 @@ onMounted(() => {
<h4>系统托盘</h4>
<Space direction="vertical" size="middle">
<div class="switch-item">
<Switch v-model:checked="settings.UI.IfShowTray"
@change="(checked) => handleSettingChange('UI', 'IfShowTray', checked)" />
<Switch
v-model:checked="settings.UI.IfShowTray"
@change="checked => handleSettingChange('UI', 'IfShowTray', checked)"
/>
<span class="switch-label">显示系统托盘图标</span>
</div>
<div class="switch-item">
<Switch v-model:checked="settings.UI.IfToTray"
@change="(checked) => handleSettingChange('UI', 'IfToTray', checked)" />
<Switch
v-model:checked="settings.UI.IfToTray"
@change="checked => handleSettingChange('UI', 'IfToTray', checked)"
/>
<span class="switch-label">关闭时最小化到托盘</span>
</div>
</Space>
@@ -512,27 +627,27 @@ onMounted(() => {
<Divider />
<!-- <div class="setting-item">-->
<!-- <h4>窗口设置</h4>-->
<!-- <Space direction="vertical" size="middle" style="width: 100%">-->
<!-- <div class="input-group">-->
<!-- <label>窗口大小</label>-->
<!-- <Input v-model:value="settings.UI.size" @blur="handleSettingChange('UI', 'size', settings.UI.size)"-->
<!-- placeholder="例如: 1200x700" style="width: 200px" />-->
<!-- </div>-->
<!-- <div class="input-group">-->
<!-- <label>窗口位置</label>-->
<!-- <Input v-model:value="settings.UI.location"-->
<!-- @blur="handleSettingChange('UI', 'location', settings.UI.location)" placeholder="例如: 100x100"-->
<!-- style="width: 200px" />-->
<!-- </div>-->
<!-- <div class="switch-item">-->
<!-- <Switch v-model:checked="settings.UI.maximized"-->
<!-- @change="(checked) => handleSettingChange('UI', 'maximized', checked)" />-->
<!-- <span class="switch-label">启动时最大化窗口</span>-->
<!-- </div>-->
<!-- </Space>-->
<!-- </div>-->
<!-- <div class="setting-item">-->
<!-- <h4>窗口设置</h4>-->
<!-- <Space direction="vertical" size="middle" style="width: 100%">-->
<!-- <div class="input-group">-->
<!-- <label>窗口大小</label>-->
<!-- <Input v-model:value="settings.UI.size" @blur="handleSettingChange('UI', 'size', settings.UI.size)"-->
<!-- placeholder="例如: 1200x700" style="width: 200px" />-->
<!-- </div>-->
<!-- <div class="input-group">-->
<!-- <label>窗口位置</label>-->
<!-- <Input v-model:value="settings.UI.location"-->
<!-- @blur="handleSettingChange('UI', 'location', settings.UI.location)" placeholder="例如: 100x100"-->
<!-- style="width: 200px" />-->
<!-- </div>-->
<!-- <div class="switch-item">-->
<!-- <Switch v-model:checked="settings.UI.maximized"-->
<!-- @change="(checked) => handleSettingChange('UI', 'maximized', checked)" />-->
<!-- <span class="switch-label">启动时最大化窗口</span>-->
<!-- </div>-->
<!-- </Space>-->
<!-- </div>-->
</Space>
</Card>
</Tabs.TabPane>
@@ -544,8 +659,10 @@ onMounted(() => {
<div class="setting-item">
<h4>语音提示</h4>
<p class="setting-description">是否启用语音提示功能</p>
<Switch v-model:checked="settings.Voice.Enabled"
@change="(checked) => handleSettingChange('Voice', 'Enabled', checked)" />
<Switch
v-model:checked="settings.Voice.Enabled"
@change="checked => handleSettingChange('Voice', 'Enabled', checked)"
/>
</div>
<Divider />
@@ -553,9 +670,13 @@ onMounted(() => {
<div class="setting-item">
<h4>语音类型</h4>
<p class="setting-description">选择语音提示的详细程度</p>
<Select v-model:value="settings.Voice.Type"
@change="(value) => handleSettingChange('Voice', 'Type', value)" :options="voiceTypeOptions"
style="width: 200px" :disabled="!settings.Voice.Enabled" />
<Select
v-model:value="settings.Voice.Type"
@change="value => handleSettingChange('Voice', 'Type', value)"
:options="voiceTypeOptions"
style="width: 200px"
:disabled="!settings.Voice.Enabled"
/>
</div>
</Space>
</Card>
@@ -570,9 +691,9 @@ onMounted(() => {
<p class="setting-description">打开浏览器开发者工具进行调试</p>
<Button type="primary" @click="openDevTools">打开 F12 开发者工具</Button>
</div>
<Divider />
<div class="setting-item">
<h4>系统日志</h4>
<p class="setting-description">查看应用运行日志用于问题排查和调试</p>
@@ -646,5 +767,4 @@ onMounted(() => {
:deep(.ant-tabs-tab) {
color: v-bind(textSecondaryColor);
}
</style>

View File

@@ -9,7 +9,7 @@ export default defineConfig({
resolve: {
extensions: ['.js', '.ts', '.vue', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
}
'@': path.resolve(__dirname, 'src'),
},
},
})