style:格式化代码
This commit is contained in:
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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="编辑用户配置">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- <!– 全局进度条 –>-->
|
||||
<!-- <div v-if="isProcessing" class="global-progress">-->
|
||||
<!-- <a-progress -->
|
||||
<!-- :percent="globalProgress" -->
|
||||
<!-- :status="globalProgressStatus"-->
|
||||
<!-- :show-info="true"-->
|
||||
<!-- />-->
|
||||
<!-- <div class="progress-text">{{ progressText }}</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <!– 全局进度条 –>-->
|
||||
<!-- <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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -24,7 +24,7 @@ defineExpose({
|
||||
startingService,
|
||||
showServiceProgress,
|
||||
serviceProgress,
|
||||
serviceStatus
|
||||
serviceStatus,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
// 组件挂载时加载设置
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -96,6 +96,6 @@ export function usePlanApi() {
|
||||
createPlan,
|
||||
updatePlan,
|
||||
deletePlan,
|
||||
reorderPlans
|
||||
reorderPlans,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
// 监听系统主题变化
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@ export interface MirrorSource {
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// 设置相关类型定义
|
||||
export interface SettingsData {
|
||||
Function: {
|
||||
@@ -66,4 +65,4 @@ export interface UpdateSettingsResponse {
|
||||
code: number
|
||||
status: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -485,4 +485,4 @@ onMounted(() => {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -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 完成')
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">-->
|
||||
<!-- <!– 左侧标题 –>-->
|
||||
<!-- <a-col :span="12">-->
|
||||
<!-- <div class="log-title">实时日志</div>-->
|
||||
<!-- </a-col>-->
|
||||
<!-- <a-row justify="space-between" align="middle" style="margin-bottom: 8px">-->
|
||||
<!-- <!– 左侧标题 –>-->
|
||||
<!-- <a-col :span="12">-->
|
||||
<!-- <div class="log-title">实时日志</div>-->
|
||||
<!-- </a-col>-->
|
||||
|
||||
<!-- <!– 右侧清空按钮 –>-->
|
||||
<!-- <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>-->
|
||||
<!-- <!– 右侧清空按钮 –>-->
|
||||
<!-- <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>
|
||||
|
||||
@@ -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>
|
||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.vue', '.json'],
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src')
|
||||
}
|
||||
}
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user