style:格式化代码

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

View File

@@ -4,7 +4,13 @@ import * as fs from 'fs'
import { spawn } from 'child_process'
import { getAppRoot, checkEnvironment } from './services/environmentService'
import { setMainWindow as setDownloadMainWindow } from './services/downloadService'
import { setMainWindow as setPythonMainWindow, downloadPython, installPipPackage, installDependencies, startBackend } from './services/pythonService'
import {
setMainWindow as setPythonMainWindow,
downloadPython,
installPipPackage,
installDependencies,
startBackend,
} from './services/pythonService'
import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService'
// 检查是否以管理员权限运行
@@ -34,13 +40,17 @@ function restartAsAdmin(): void {
const args = process.argv.slice(1)
// 使用PowerShell以管理员权限启动
spawn('powershell', [
spawn(
'powershell',
[
'-Command',
`Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs`
], {
`Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs`,
],
{
detached: true,
stdio: 'ignore'
})
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') => {
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') => {
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) => {

View File

@@ -39,5 +39,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
},
removeDownloadProgressListener: () => {
ipcRenderer.removeAllListeners('download-progress')
}
},
})

View File

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

View File

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

View File

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

View File

@@ -16,8 +16,9 @@ const pythonMirrorUrls = {
official: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip',
tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
ustc: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
huawei: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip'
huawei:
'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip',
}
// 检查pip是否已安装
@@ -80,7 +81,8 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
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,7 +173,10 @@ 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')
@@ -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,7 +222,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
type: 'python',
progress: 100,
status: 'extracting',
message: '正在解压Python...'
message: '正在解压Python...',
})
}
@@ -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,12 +323,13 @@ 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')
@@ -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, [
const process = spawn(
pipExePath,
[
'install',
'-r', requirementsPath,
'-i', pipMirrorUrl,
'--trusted-host', new URL(pipMirrorUrl).hostname
], {
'-r',
requirementsPath,
'-i',
pipMirrorUrl,
'--trusted-host',
new URL(pipMirrorUrl).hostname,
],
{
cwd: backendPath,
stdio: 'pipe'
})
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,7 +424,9 @@ 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')
@@ -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 }
@@ -466,19 +492,17 @@ 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)
@@ -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)
})

View File

@@ -57,7 +57,13 @@
</a-layout-sider>
<!-- 主内容区 -->
<a-layout :style="{ marginLeft: collapsed ? '60px' : '180px', transition: 'margin-left 0.2s', height: '100vh' }">
<a-layout
:style="{
marginLeft: collapsed ? '60px' : '180px',
transition: 'margin-left 0.2s',
height: '100vh',
}"
>
<a-layout-content
class="content-area"
:style="{
@@ -103,9 +109,7 @@ const mainMenuItems = [
{ path: '/history', label: '历史记录', icon: HistoryOutlined },
]
const bottomMenuItems = [
{ path: '/settings', label: '设置', icon: SettingOutlined },
]
const bottomMenuItems = [{ path: '/settings', label: '设置', icon: SettingOutlined }]
// 自动同步选中项
const selectedKeys = computed(() => {
@@ -142,9 +146,11 @@ const toggleCollapse = () => {
border-radius: 6px;
cursor: pointer;
}
.logo:hover {
background-color: rgba(255, 255, 255, 0.5);
}
:deep(.ant-layout-sider-light) .logo:hover {
background-color: rgba(0, 0, 0, 0.04);
}
@@ -162,6 +168,7 @@ const toggleCollapse = () => {
opacity: 1;
transition: opacity 0.2s ease;
}
.logo-text.text-hidden {
opacity: 0;
}
@@ -174,12 +181,15 @@ const toggleCollapse = () => {
scrollbar-width: thin;
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);
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;
}

View File

@@ -2,11 +2,7 @@
<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>
@@ -44,16 +40,10 @@
</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 ref="logContainer" class="log-container" @scroll="handleScroll">
<div
v-for="(log, index) in filteredLogs"
:key="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>
@@ -84,7 +72,7 @@ import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
import {
DeleteOutlined,
DownloadOutlined,
VerticalAlignBottomOutlined
VerticalAlignBottomOutlined,
} from '@ant-design/icons-vue'
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
@@ -108,7 +96,8 @@ const filteredLogs = computed(() => {
// 按搜索文本过滤
if (searchText.value) {
const search = searchText.value.toLowerCase()
filtered = filtered.filter(log =>
filtered = filtered.filter(
log =>
log.message.toLowerCase().includes(search) ||
log.component?.toLowerCase().includes(search) ||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
@@ -178,7 +167,8 @@ onMounted(() => {
})
// 监听日志变化
unwatchLogs = logs.value && typeof logs.value === 'object' && 'length' in logs.value
unwatchLogs =
logs.value && typeof logs.value === 'object' && 'length' in logs.value
? () => {} // 如果logs是响应式的Vue会自动处理
: null
})

View File

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

View File

@@ -6,9 +6,7 @@
sub-title="为了正常安装和配置环境请以管理员权限运行此应用"
>
<template #extra>
<a-button type="primary" @click="handleRestartAsAdmin">
重新以管理员权限启动
</a-button>
<a-button type="primary" @click="handleRestartAsAdmin"> 重新以管理员权限启动 </a-button>
</template>
</a-result>
</div>

View File

@@ -54,7 +54,6 @@ import { GIT_MIRRORS } from '@/config/mirrors'
const gitMirrors = ref<Mirror[]>(GIT_MIRRORS)
const selectedGitMirror = ref('github')
const testingGitSpeed = ref(false)
@@ -89,7 +88,7 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
await fetch(url.replace('.git', ''), {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
signal: controller.signal,
})
clearTimeout(timeoutId)
@@ -102,7 +101,7 @@ 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
})
@@ -131,7 +130,7 @@ function getSpeedClass(speed: number | null) {
defineExpose({
selectedGitMirror,
testGitMirrorSpeed,
gitMirrors
gitMirrors,
})
// 组件挂载时加载配置并自动开始测速

View File

@@ -84,7 +84,7 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
signal: controller.signal,
})
clearTimeout(timeoutId)
@@ -97,7 +97,7 @@ 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
})
@@ -125,7 +125,7 @@ function getSpeedClass(speed: number | null) {
defineExpose({
selectedPipMirror,
testPipMirrorSpeed
testPipMirrorSpeed,
})
// 组件挂载时加载配置并自动开始测速

View File

@@ -62,7 +62,7 @@ async function handleForceReinstall() {
}
defineExpose({
handleForceReinstall
handleForceReinstall,
})
</script>

View File

@@ -8,17 +8,18 @@
<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 包管理器" />
@@ -43,7 +44,11 @@
<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,7 +76,6 @@
上一步
</a-button>
<a-button
v-if="currentStep < 6"
size="large"
@@ -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 '下一步'
}
}
@@ -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

View File

@@ -63,7 +63,7 @@ async function handleForceReinstall() {
}
defineExpose({
handleForceReinstall
handleForceReinstall,
})
</script>

View File

@@ -5,8 +5,13 @@
<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
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)">
@@ -93,7 +98,7 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
signal: controller.signal,
})
clearTimeout(timeoutId)
@@ -106,7 +111,7 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
async function testPythonMirrorSpeed() {
testingSpeed.value = true
try {
const promises = pythonMirrors.value.map(async (mirror) => {
const promises = pythonMirrors.value.map(async mirror => {
mirror.speed = await testMirrorWithTimeout(mirror.url)
return mirror
})
@@ -164,7 +169,7 @@ async function handleForceReinstall() {
defineExpose({
selectedPythonMirror,
testPythonMirrorSpeed,
handleForceReinstall
handleForceReinstall,
})
// 组件挂载时加载配置并自动开始测速

View File

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

View File

@@ -53,7 +53,7 @@ async function saveSettings() {
await saveThemeConfig(selectedThemeMode.value, selectedThemeColor.value)
console.log('主题设置已保存:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value
themeColor: selectedThemeColor.value,
})
}
@@ -66,7 +66,7 @@ async function loadSettings() {
setThemeColor(selectedThemeColor.value)
console.log('主题设置已加载:', {
themeMode: selectedThemeMode.value,
themeColor: selectedThemeColor.value
themeColor: selectedThemeColor.value,
})
} catch (error) {
console.warn('Failed to load theme settings:', error)
@@ -78,7 +78,7 @@ defineExpose({
loadSettings,
saveSettings,
selectedThemeMode,
selectedThemeColor
selectedThemeColor,
})
// 组件挂载时加载设置

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ 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)
@@ -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 : '添加脚本失败'
@@ -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 : '获取脚本详情失败'
@@ -157,7 +157,7 @@ export function useScriptApi() {
const response = await Service.updateScriptApiScriptsUpdatePost({
scriptId,
data: dataToSend
data: dataToSend,
})
if (response.code !== 200) {

View File

@@ -42,7 +42,7 @@ export function useSettingsApi() {
try {
const response = await Service.updateScriptApiSettingUpdatePost({
data: settings
data: settings,
})
// 根据code判断是否成功非200就是不成功

View File

@@ -109,15 +109,17 @@ 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 ? {
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
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) => {
@@ -127,11 +129,7 @@ const lightenColor = (hex: string, percent: number) => {
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) => {
@@ -141,11 +139,7 @@ const darkenColor = (hex: string, percent: number) => {
const { r, g, b } = rgb
const amount = Math.round(2.55 * percent)
return rgbToHex(
Math.max(0, r - amount),
Math.max(0, g - amount),
Math.max(0, b - amount)
)
return rgbToHex(Math.max(0, r - amount), Math.max(0, g - amount), Math.max(0, b - amount))
}
// 监听系统主题变化

View File

@@ -14,7 +14,7 @@ export function useUserApi() {
try {
const requestData: UserInBase = {
scriptId
scriptId,
}
const response = await Service.addUserApiScriptsUserAddPost(requestData)
@@ -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,7 +51,7 @@ export function useUserApi() {
const requestData: UserUpdateIn = {
scriptId,
userId,
data: userData
data: userData,
}
const response = await Service.updateUserApiScriptsUserUpdatePost(requestData)
@@ -80,7 +84,7 @@ export function useUserApi() {
try {
const requestData: UserDeleteIn = {
scriptId,
userId
userId,
}
const response = await Service.deleteUserApiScriptsUserDeletePost(requestData)

View File

@@ -22,38 +22,38 @@ export const GIT_MIRRORS: MirrorConfig[] = [
key: 'github',
name: 'GitHub 官方',
url: 'https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghfast',
name: 'ghfast 镜像',
url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_cloudflare',
name: 'gh-proxy (Cloudflare加速)',
url: 'https://gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_hongkong',
name: 'gh-proxy (香港节点加速)',
url: 'https://hk.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_fastly',
name: 'gh-proxy (Fastly CDN加速)',
url: 'https://cdn.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
speed: null,
},
{
key: 'ghproxy_edgeone',
name: 'gh-proxy (EdgeOne加速',
url: 'https://edgeone.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
speed: null
}
speed: null,
},
]
/**
@@ -64,32 +64,32 @@ export const PYTHON_MIRRORS: MirrorConfig[] = [
key: 'official',
name: 'Python 官方',
url: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'tsinghua',
name: '清华 TUNA 镜像',
url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'ustc',
name: '中科大镜像',
url: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'huawei',
name: '华为云镜像',
url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
speed: null
speed: null,
},
{
key: 'aliyun',
name: '阿里云镜像',
url: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip',
speed: null
}
speed: null,
},
]
/**
@@ -100,32 +100,32 @@ export const PIP_MIRRORS: MirrorConfig[] = [
key: 'official',
name: 'PyPI 官方',
url: 'https://pypi.org/simple/',
speed: null
speed: null,
},
{
key: 'tsinghua',
name: '清华大学',
url: 'https://pypi.tuna.tsinghua.edu.cn/simple/',
speed: null
speed: null,
},
{
key: 'ustc',
name: '中科大',
url: 'https://pypi.mirrors.ustc.edu.cn/simple/',
speed: null
speed: null,
},
{
key: 'aliyun',
name: '阿里云',
url: 'https://mirrors.aliyun.com/pypi/simple/',
speed: null
speed: null,
},
{
key: 'douban',
name: '豆瓣',
url: 'https://pypi.douban.com/simple/',
speed: null
}
speed: null,
},
]
/**
@@ -137,7 +137,7 @@ export const API_ENDPOINTS = {
// WebSocket连接基础URL
websocket: 'ws://localhost:8000',
// 代理服务器示例
proxy: 'http://127.0.0.1:7890'
proxy: 'http://127.0.0.1:7890',
}
/**
@@ -148,7 +148,7 @@ export const DOWNLOAD_LINKS = {
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,
}
/**

View File

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

View File

@@ -1,4 +1,3 @@
// 设置相关类型定义
export interface SettingsData {
Function: {

View File

@@ -38,7 +38,7 @@ const DEFAULT_CONFIG: FrontendConfig = {
gitInstalled: false,
backendExists: false,
dependenciesInstalled: false,
pipInstalled: false
pipInstalled: false,
}
// 读取配置(内部使用,不触发保存)
@@ -83,7 +83,8 @@ export async function getConfig(): Promise<FrontendConfig> {
const config = await getConfigInternal()
// 如果是从localStorage迁移的配置保存到文件并清理localStorage
const hasLocalStorage = localStorage.getItem('app-config') || localStorage.getItem('theme-settings')
const hasLocalStorage =
localStorage.getItem('app-config') || localStorage.getItem('theme-settings')
if (hasLocalStorage) {
try {
await window.electronAPI.saveConfig(config)
@@ -155,7 +156,11 @@ 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

View File

@@ -34,7 +34,7 @@ class Logger {
level,
message,
data,
component
component,
}
// 添加到内存日志
@@ -188,5 +188,5 @@ export default {
install(app: any) {
app.config.globalProperties.$logger = logger
app.provide('logger', logger)
}
},
}

View File

@@ -12,7 +12,7 @@ import {
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 }
@@ -172,7 +172,7 @@ export class MirrorManager {
return {
mirrors: this.mirrorConfigs,
apiEndpoints: this.apiEndpoints,
downloadLinks: this.downloadLinks
downloadLinks: this.downloadLinks,
}
}
}
@@ -182,6 +182,9 @@ export const mirrorManager = MirrorManager.getInstance()
// 导出便捷函数
export const getMirrors = (type: keyof MirrorCategory) => mirrorManager.getMirrors(type)
export const getMirrorUrlByManager = (type: keyof MirrorCategory, key: string) => mirrorManager.getMirrorUrl(type, key)
export const testMirrorSpeed = (url: string, timeout?: number) => mirrorManager.testMirrorSpeed(url, timeout)
export const testAllMirrorSpeeds = (type: keyof MirrorCategory) => mirrorManager.testAllMirrorSpeeds(type)
export const getMirrorUrlByManager = (type: keyof MirrorCategory, key: string) =>
mirrorManager.getMirrorUrl(type, key)
export const testMirrorSpeed = (url: string, timeout?: number) =>
mirrorManager.testMirrorSpeed(url, timeout)
export const testAllMirrorSpeeds = (type: keyof MirrorCategory) =>
mirrorManager.testAllMirrorSpeeds(type)

View File

@@ -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)
@@ -91,7 +91,7 @@ async function checkCriticalFiles() {
pythonExists: config.pythonInstalled || false,
pipExists: config.pipInstalled || false,
gitExists: config.gitInstalled || false,
mainPyExists: config.backendExists || false
mainPyExists: config.backendExists || false,
}
}
@@ -109,7 +109,7 @@ async function checkCriticalFiles() {
pythonExists: criticalFiles.pythonExists,
pipExists: criticalFiles.pipExists,
gitExists: criticalFiles.gitExists,
mainPyExists: criticalFiles.mainPyExists
mainPyExists: criticalFiles.mainPyExists,
}
console.log('🔍 最终返回结果:', result)
@@ -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,
}
}
}
@@ -177,7 +177,8 @@ async function checkEnvironment() {
console.log('是否第一次启动:', isFirst)
// 检查所有关键exe文件是否都存在
const allExeFilesExist = criticalFiles.pythonExists &&
const allExeFilesExist =
criticalFiles.pythonExists &&
criticalFiles.pipExists &&
criticalFiles.gitExists &&
criticalFiles.mainPyExists
@@ -203,7 +204,14 @@ 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) {

View File

@@ -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>
@@ -157,13 +161,13 @@ const fetchQueues = async () => {
console.log('Queue ID:', queueId, 'Name:', queueName, 'Type:', typeof queueId) // 调试日志
return {
id: queueId,
name: queueName
name: queueName,
}
} catch (itemError) {
console.warn('解析队列项失败:', itemError, item)
return {
id: `queue_${index}`,
name: `队列 ${index + 1}`
name: `队列 ${index + 1}`,
}
}
})
@@ -290,7 +294,7 @@ const refreshTimeSets = async () => {
id: timeSetId,
time: timeString,
enabled: Boolean(timeSetData.Info.Enabled),
description: timeSetData.Info.Description || ''
description: timeSetData.Info.Description || '',
})
}
} catch (itemError) {
@@ -353,7 +357,7 @@ const refreshQueueItems = async () => {
if (queueItemData?.Info) {
queueItems.push({
id: queueItemId,
script: queueItemData.Info.ScriptId || ''
script: queueItemData.Info.ScriptId || '',
})
}
} catch (itemError) {
@@ -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}`
}
}
}
@@ -505,7 +510,7 @@ const saveQueueData = async () => {
const response = await Service.updateQueueApiQueueUpdatePost({
queueId: activeQueueId.value,
data: queueData
data: queueData,
})
if (response.code !== 200) {
@@ -701,7 +706,9 @@ onMounted(async () => {
}
.empty-content-fancy {
transition: box-shadow 0.3s, transform 0.2s;
transition:
box-shadow 0.3s,
transform 0.2s;
border: none;
border-radius: 24px;
}

View File

@@ -154,7 +154,6 @@
<!-- 实时日志 (60%) -->
<a-col :span="14">
<a-card size="small" style="height: 100%" title="实时日志">
<div class="realtime-logs-panel">
<!-- <a-row justify="space-between" align="middle" style="margin-bottom: 8px">-->
<!-- &lt;!&ndash; 左侧标题 &ndash;&gt;-->
@@ -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)

View File

@@ -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="{
<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>
@@ -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>
@@ -646,5 +767,4 @@ onMounted(() => {
:deep(.ant-tabs-tab) {
color: v-bind(textSecondaryColor);
}
</style>

View File

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