Merge branch 'feature/refactor' of github.com:AUTO-MAS-Project/AUTO-MAS into feature/refactor
This commit is contained in:
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"vite\" \"yarn watch:main\" \"yarn electron-dev\"",
|
||||
|
||||
@@ -15,8 +15,20 @@
|
||||
</div>
|
||||
|
||||
<div class="auto-actions">
|
||||
<a-button @click="handleSwitchToManual" type="primary" size="large">重新配置环境</a-button>
|
||||
<a-button @click="handleForceEnter" type="default" size="large">强行进入应用</a-button>
|
||||
<a-button
|
||||
@click="handleSwitchToManual"
|
||||
type="primary"
|
||||
size="large"
|
||||
>
|
||||
重新配置环境
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="handleForceEnter"
|
||||
type="default"
|
||||
size="large"
|
||||
>
|
||||
强行进入应用
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,9 +53,11 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getConfig } from '@/utils/config'
|
||||
import { getMirrorUrl } from '@/config/mirrors'
|
||||
import { mirrorManager } from '@/utils/mirrorManager'
|
||||
import router from '@/router'
|
||||
import { useUpdateChecker } from '@/composables/useUpdateChecker'
|
||||
import { connectAfterBackendStart } from '@/composables/useWebSocket'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +83,11 @@ const aborted = ref(false)
|
||||
// 状态:控制弹窗显隐
|
||||
const forceEnterVisible = ref(false)
|
||||
|
||||
// 镜像源重试相关状态
|
||||
const currentMirrorIndex = ref(0)
|
||||
const availableMirrors = ref<any[]>([])
|
||||
const maxRetries = ref(3)
|
||||
|
||||
// 点击“强行进入应用”按钮,显示弹窗
|
||||
function handleForceEnter() {
|
||||
forceEnterVisible.value = true
|
||||
@@ -104,12 +123,17 @@ async function startAutoProcess() {
|
||||
progressText.value = '发现更新,正在更新代码...'
|
||||
progress.value = 40
|
||||
|
||||
// 使用配置中保存的Git镜像源
|
||||
const gitMirrorUrl = getGitMirrorUrl(config.selectedGitMirror)
|
||||
const result = await window.electronAPI.updateBackend(gitMirrorUrl)
|
||||
// 尝试更新代码,支持镜像源重试
|
||||
const updateSuccess = await tryUpdateBackendWithRetry(config)
|
||||
if (aborted.value) return
|
||||
if (!result.success) {
|
||||
throw new Error(`代码更新失败: ${result.error}`)
|
||||
if (!updateSuccess) {
|
||||
// 所有镜像源都失败了,显示重新配置按钮
|
||||
progressText.value = '代码更新失败,所有镜像源均无法访问'
|
||||
progressStatus.value = 'exception'
|
||||
setTimeout(() => {
|
||||
progressText.value = '请点击下方按钮重新配置环境'
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,23 +141,17 @@ async function startAutoProcess() {
|
||||
progressText.value = '检查并安装依赖包...'
|
||||
progress.value = 60
|
||||
|
||||
// 先尝试使用初始化时的镜像源
|
||||
let pipMirror = config.selectedPipMirror || 'tsinghua'
|
||||
let pipResult = await window.electronAPI.installDependencies(pipMirror)
|
||||
// 尝试安装依赖,支持镜像源重试
|
||||
const dependenciesSuccess = await tryInstallDependenciesWithRetry(config)
|
||||
if (aborted.value) return
|
||||
|
||||
// 如果初始化时的镜像源不通,让用户重新选择
|
||||
if (!pipResult.success) {
|
||||
console.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
|
||||
|
||||
// 切换到手动模式让用户重新选择镜像源
|
||||
progressText.value = '依赖安装失败,需要重新配置镜像源'
|
||||
if (!dependenciesSuccess) {
|
||||
// 所有PIP镜像源都失败了,显示重新配置按钮
|
||||
progressText.value = '依赖安装失败,所有PIP镜像源均无法访问'
|
||||
progressStatus.value = 'exception'
|
||||
|
||||
setTimeout(() => {
|
||||
progressText.value = '请点击下方按钮重新配置环境'
|
||||
}, 2000)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,6 +202,108 @@ function getGitMirrorUrl(mirrorKey: string): string {
|
||||
return getMirrorUrl('git', mirrorKey)
|
||||
}
|
||||
|
||||
// 尝试更新后端代码,支持镜像源重试
|
||||
async function tryUpdateBackendWithRetry(config: any): Promise<boolean> {
|
||||
// 获取所有Git镜像源
|
||||
const allGitMirrors = mirrorManager.getMirrors('git')
|
||||
|
||||
// 加载用户的自定义镜像源
|
||||
const customMirrors = config.customGitMirrors || []
|
||||
const combinedMirrors = [...allGitMirrors, ...customMirrors]
|
||||
|
||||
// 优先使用用户选择的镜像源
|
||||
const selectedMirror = combinedMirrors.find(m => m.key === config.selectedGitMirror)
|
||||
let mirrorsToTry = selectedMirror ? [selectedMirror] : []
|
||||
|
||||
// 添加其他镜像源作为备选
|
||||
const otherMirrors = combinedMirrors.filter(m => m.key !== config.selectedGitMirror)
|
||||
mirrorsToTry = [...mirrorsToTry, ...otherMirrors]
|
||||
|
||||
console.log('准备尝试的Git镜像源:', mirrorsToTry.map(m => m.name))
|
||||
|
||||
for (let i = 0; i < mirrorsToTry.length; i++) {
|
||||
if (aborted.value) return false
|
||||
|
||||
const mirror = mirrorsToTry[i]
|
||||
progressText.value = `正在使用 ${mirror.name} 更新代码... (${i + 1}/${mirrorsToTry.length})`
|
||||
|
||||
try {
|
||||
console.log(`尝试使用镜像源: ${mirror.name} (${mirror.url})`)
|
||||
const result = await window.electronAPI.updateBackend(mirror.url)
|
||||
|
||||
if (result.success) {
|
||||
console.log(`使用镜像源 ${mirror.name} 更新成功`)
|
||||
message.success(`使用 ${mirror.name} 更新代码成功`)
|
||||
return true
|
||||
} else {
|
||||
console.warn(`镜像源 ${mirror.name} 更新失败:`, result.error)
|
||||
if (i < mirrorsToTry.length - 1) {
|
||||
progressText.value = `${mirror.name} 失败,尝试下一个镜像源...`
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`镜像源 ${mirror.name} 更新异常:`, error)
|
||||
if (i < mirrorsToTry.length - 1) {
|
||||
progressText.value = `${mirror.name} 异常,尝试下一个镜像源...`
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error('所有Git镜像源都无法更新代码')
|
||||
return false
|
||||
}
|
||||
|
||||
// 尝试安装依赖,支持镜像源重试
|
||||
async function tryInstallDependenciesWithRetry(config: any): Promise<boolean> {
|
||||
// 获取所有PIP镜像源
|
||||
const allPipMirrors = mirrorManager.getMirrors('pip')
|
||||
|
||||
// 优先使用用户选择的镜像源
|
||||
const selectedMirror = allPipMirrors.find(m => m.key === config.selectedPipMirror)
|
||||
let mirrorsToTry = selectedMirror ? [selectedMirror] : []
|
||||
|
||||
// 添加其他镜像源作为备选
|
||||
const otherMirrors = allPipMirrors.filter(m => m.key !== config.selectedPipMirror)
|
||||
mirrorsToTry = [...mirrorsToTry, ...otherMirrors]
|
||||
|
||||
console.log('准备尝试的PIP镜像源:', mirrorsToTry.map(m => m.name))
|
||||
|
||||
for (let i = 0; i < mirrorsToTry.length; i++) {
|
||||
if (aborted.value) return false
|
||||
|
||||
const mirror = mirrorsToTry[i]
|
||||
progressText.value = `正在使用 ${mirror.name} 安装依赖... (${i + 1}/${mirrorsToTry.length})`
|
||||
|
||||
try {
|
||||
console.log(`尝试使用PIP镜像源: ${mirror.name} (${mirror.url})`)
|
||||
const result = await window.electronAPI.installDependencies(mirror.key)
|
||||
|
||||
if (result.success) {
|
||||
console.log(`使用PIP镜像源 ${mirror.name} 安装成功`)
|
||||
message.success(`使用 ${mirror.name} 安装依赖成功`)
|
||||
return true
|
||||
} else {
|
||||
console.warn(`PIP镜像源 ${mirror.name} 安装失败:`, result.error)
|
||||
if (i < mirrorsToTry.length - 1) {
|
||||
progressText.value = `${mirror.name} 失败,尝试下一个镜像源...`
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`PIP镜像源 ${mirror.name} 安装异常:`, error)
|
||||
if (i < mirrorsToTry.length - 1) {
|
||||
progressText.value = `${mirror.name} 异常,尝试下一个镜像源...`
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error('所有PIP镜像源都无法安装依赖')
|
||||
return false
|
||||
}
|
||||
|
||||
// 启动后端服务
|
||||
async function startBackendService() {
|
||||
const result = await window.electronAPI.startBackend()
|
||||
|
||||
@@ -67,6 +67,67 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义镜像源 -->
|
||||
<div class="mirror-section">
|
||||
<div class="section-header">
|
||||
<h4>自定义镜像源</h4>
|
||||
<a-tag color="blue">手动添加</a-tag>
|
||||
</div>
|
||||
<div class="custom-mirror-input">
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
v-model:value="customMirrorUrl"
|
||||
placeholder="输入镜像域名或完整Git地址,如:ghproxy.com 或 https://ghproxy.com/https://github.com/AUTO-MAS-Project/AUTO-MAS.git"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="addCustomMirror"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="addCustomMirror"
|
||||
:loading="addingCustomMirror"
|
||||
style="width: 100px"
|
||||
>
|
||||
添加
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
</div>
|
||||
|
||||
<!-- 显示自定义镜像源 -->
|
||||
<div v-if="customMirrors.length > 0" class="mirror-grid" style="margin-top: 16px;">
|
||||
<div
|
||||
v-for="mirror in customMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card custom-mirror"
|
||||
:class="{ active: selectedGitMirror === mirror.key }"
|
||||
@click="selectedGitMirror = mirror.key"
|
||||
>
|
||||
<div class="mirror-header">
|
||||
<div class="mirror-title">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<a-tag color="blue" size="small">自定义</a-tag>
|
||||
</div>
|
||||
<div class="mirror-actions">
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed ?? null)">
|
||||
<span v-if="mirror.speed === null && !testingGitSpeed">未测试</span>
|
||||
<span v-else-if="testingGitSpeed">测试中...</span>
|
||||
<span v-else-if="mirror.speed === 9999">超时</span>
|
||||
<span v-else>{{ mirror.speed }}ms</span>
|
||||
</div>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
@click.stop="removeCustomMirror(mirror.key)"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mirror-description">{{ mirror.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testGitMirrorSpeed" :loading="testingGitSpeed" type="primary">
|
||||
{{ testingGitSpeed ? '测速中...' : '重新测速' }}
|
||||
@@ -105,12 +166,26 @@ const sortedMirrorMirrors = computed(() => sortMirrorsBySpeedAndRecommendation(m
|
||||
const selectedGitMirror = ref('ghproxy_edgeone')
|
||||
const testingGitSpeed = ref(false)
|
||||
|
||||
// 自定义镜像源相关
|
||||
const customMirrorUrl = ref('')
|
||||
const customMirrors = ref<MirrorConfig[]>([])
|
||||
const addingCustomMirror = ref(false)
|
||||
|
||||
// 加载配置中的镜像源选择
|
||||
async function loadMirrorConfig() {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
selectedGitMirror.value = config.selectedGitMirror
|
||||
selectedGitMirror.value = config.selectedGitMirror || 'ghproxy_edgeone'
|
||||
|
||||
// 加载自定义镜像源
|
||||
if (config.customGitMirrors && Array.isArray(config.customGitMirrors)) {
|
||||
customMirrors.value = config.customGitMirrors
|
||||
// 将自定义镜像源添加到gitMirrors中
|
||||
gitMirrors.value = [...GIT_MIRRORS, ...customMirrors.value]
|
||||
}
|
||||
|
||||
console.log('Git镜像源配置已加载:', selectedGitMirror.value)
|
||||
console.log('自定义镜像源已加载:', customMirrors.value.length, '个')
|
||||
} catch (error) {
|
||||
console.warn('加载Git镜像源配置失败:', error)
|
||||
}
|
||||
@@ -119,8 +194,12 @@ async function loadMirrorConfig() {
|
||||
// 保存镜像源选择
|
||||
async function saveMirrorConfig() {
|
||||
try {
|
||||
await saveConfig({ selectedGitMirror: selectedGitMirror.value })
|
||||
await saveConfig({
|
||||
selectedGitMirror: selectedGitMirror.value,
|
||||
customGitMirrors: customMirrors.value
|
||||
})
|
||||
console.log('Git镜像源配置已保存:', selectedGitMirror.value)
|
||||
console.log('自定义镜像源已保存:', customMirrors.value.length, '个')
|
||||
} catch (error) {
|
||||
console.warn('保存Git镜像源配置失败:', error)
|
||||
}
|
||||
@@ -176,6 +255,135 @@ function getSpeedClass(speed: number | null) {
|
||||
return 'speed-slow'
|
||||
}
|
||||
|
||||
// 处理自定义镜像源URL
|
||||
function processCustomMirrorUrl(input: string): string {
|
||||
const trimmedInput = input.trim()
|
||||
|
||||
// 如果已经是完整的Git地址且以.git结尾,直接返回
|
||||
if (trimmedInput.includes('github.com/AUTO-MAS-Project/AUTO-MAS') && trimmedInput.endsWith('.git')) {
|
||||
return trimmedInput
|
||||
}
|
||||
|
||||
// 如果是完整的Git地址但没有.git结尾,添加.git
|
||||
if (trimmedInput.includes('github.com/AUTO-MAS-Project/AUTO-MAS')) {
|
||||
return trimmedInput.endsWith('.git') ? trimmedInput : trimmedInput + '.git'
|
||||
}
|
||||
|
||||
// 如果只是域名,拼接完整地址
|
||||
let domain = trimmedInput
|
||||
|
||||
// 移除协议前缀
|
||||
domain = domain.replace(/^https?:\/\//, '')
|
||||
|
||||
// 移除尾部斜杠
|
||||
domain = domain.replace(/\/$/, '')
|
||||
|
||||
// 拼接完整地址
|
||||
return `https://${domain}/https://github.com/AUTO-MAS-Project/AUTO-MAS.git`
|
||||
}
|
||||
|
||||
// 添加自定义镜像源
|
||||
async function addCustomMirror() {
|
||||
if (!customMirrorUrl.value.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
addingCustomMirror.value = true
|
||||
|
||||
try {
|
||||
const processedUrl = processCustomMirrorUrl(customMirrorUrl.value)
|
||||
|
||||
// 检查是否已存在
|
||||
const existingMirror = [...gitMirrors.value, ...customMirrors.value].find(
|
||||
m => m.url === processedUrl
|
||||
)
|
||||
|
||||
if (existingMirror) {
|
||||
console.warn('镜像源已存在:', processedUrl)
|
||||
customMirrorUrl.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 生成镜像源配置
|
||||
const customKey = `custom_${Date.now()}`
|
||||
const customName = extractDomainName(customMirrorUrl.value)
|
||||
|
||||
const newMirror: MirrorConfig = {
|
||||
key: customKey,
|
||||
name: `${customName} (自定义)`,
|
||||
url: processedUrl,
|
||||
speed: null,
|
||||
type: 'mirror',
|
||||
chinaConnectivity: 'good',
|
||||
description: `用户自定义的镜像源: ${customName}`
|
||||
}
|
||||
|
||||
// 添加到自定义镜像源列表
|
||||
customMirrors.value.push(newMirror)
|
||||
|
||||
// 更新完整的镜像源列表
|
||||
gitMirrors.value = [...GIT_MIRRORS, ...customMirrors.value]
|
||||
|
||||
// 自动选择新添加的镜像源
|
||||
selectedGitMirror.value = customKey
|
||||
|
||||
// 保存配置
|
||||
await saveMirrorConfig()
|
||||
|
||||
// 清空输入框
|
||||
customMirrorUrl.value = ''
|
||||
|
||||
console.log('自定义镜像源添加成功:', newMirror)
|
||||
|
||||
} catch (error) {
|
||||
console.error('添加自定义镜像源失败:', error)
|
||||
} finally {
|
||||
addingCustomMirror.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 提取域名作为显示名称
|
||||
function extractDomainName(url: string): string {
|
||||
try {
|
||||
// 移除协议前缀
|
||||
let domain = url.replace(/^https?:\/\//, '')
|
||||
|
||||
// 如果包含路径,只取域名部分
|
||||
domain = domain.split('/')[0]
|
||||
|
||||
// 移除端口号
|
||||
domain = domain.split(':')[0]
|
||||
|
||||
return domain || '自定义镜像'
|
||||
} catch {
|
||||
return '自定义镜像'
|
||||
}
|
||||
}
|
||||
|
||||
// 删除自定义镜像源
|
||||
async function removeCustomMirror(key: string) {
|
||||
try {
|
||||
// 从自定义镜像源列表中移除
|
||||
customMirrors.value = customMirrors.value.filter(m => m.key !== key)
|
||||
|
||||
// 更新完整的镜像源列表
|
||||
gitMirrors.value = [...GIT_MIRRORS, ...customMirrors.value]
|
||||
|
||||
// 如果当前选中的是被删除的镜像源,切换到默认镜像源
|
||||
if (selectedGitMirror.value === key) {
|
||||
selectedGitMirror.value = 'ghproxy_edgeone'
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
await saveMirrorConfig()
|
||||
|
||||
console.log('自定义镜像源删除成功:', key)
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除自定义镜像源失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
selectedGitMirror,
|
||||
testGitMirrorSpeed,
|
||||
@@ -334,4 +542,34 @@ onMounted(async () => {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.custom-mirror-input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.custom-mirror-help {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.custom-mirror-help code {
|
||||
background: var(--ant-color-fill-alter);
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mirror-card.custom-mirror {
|
||||
border-color: var(--ant-color-primary-border);
|
||||
}
|
||||
|
||||
.mirror-card.custom-mirror:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
.mirror-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,9 @@ import 'dayjs/locale/zh-cn'
|
||||
// 导入日志系统
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
// 导入镜像管理器
|
||||
import { mirrorManager } from '@/utils/mirrorManager'
|
||||
|
||||
// 配置dayjs中文本地化
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
@@ -24,6 +27,13 @@ OpenAPI.BASE = API_ENDPOINTS.local
|
||||
logger.info('前端应用开始初始化')
|
||||
logger.info(`API基础URL: ${OpenAPI.BASE}`)
|
||||
|
||||
// 初始化镜像管理器(异步)
|
||||
mirrorManager.initialize().then(() => {
|
||||
logger.info('镜像管理器初始化完成')
|
||||
}).catch((error) => {
|
||||
logger.error('镜像管理器初始化失败:', error)
|
||||
})
|
||||
|
||||
// 创建应用实例
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
@@ -94,6 +94,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/Logs.vue'),
|
||||
meta: { title: '日志查看' },
|
||||
},
|
||||
{
|
||||
path: '/mirror-test',
|
||||
name: 'MirrorTest',
|
||||
component: () => import('../views/MirrorTest.vue'),
|
||||
meta: { title: '镜像配置测试' },
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
187
frontend/src/utils/cloudConfigManager.ts
Normal file
187
frontend/src/utils/cloudConfigManager.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 云端配置管理器
|
||||
* 负责从云端拉取最新的镜像站配置,如果失败则使用本地兜底配置
|
||||
*/
|
||||
|
||||
import type { MirrorCategory, MirrorConfig } from '@/config/mirrors'
|
||||
|
||||
export interface CloudMirrorConfig {
|
||||
version: string
|
||||
lastUpdated: string
|
||||
mirrors: MirrorCategory
|
||||
apiEndpoints: Record<string, string>
|
||||
downloadLinks: Record<string, string>
|
||||
}
|
||||
|
||||
export class CloudConfigManager {
|
||||
private static instance: CloudConfigManager
|
||||
private cloudConfigUrl = 'https://download.auto-mas.top/d/AUTO_MAS/Server/mirrors.json'
|
||||
private fallbackConfig: CloudMirrorConfig | null = null
|
||||
private currentConfig: CloudMirrorConfig | null = null
|
||||
private fetchTimeout = 3000 // 3秒超时
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): CloudConfigManager {
|
||||
if (!CloudConfigManager.instance) {
|
||||
CloudConfigManager.instance = new CloudConfigManager()
|
||||
}
|
||||
return CloudConfigManager.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置兜底配置(本地配置)
|
||||
*/
|
||||
setFallbackConfig(config: CloudMirrorConfig): void {
|
||||
this.fallbackConfig = config
|
||||
}
|
||||
|
||||
/**
|
||||
* 从云端拉取最新配置
|
||||
*/
|
||||
async fetchCloudConfig(): Promise<CloudMirrorConfig | null> {
|
||||
try {
|
||||
console.log('正在从云端拉取镜像站配置...')
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.fetchTimeout)
|
||||
|
||||
const response = await fetch(this.cloudConfigUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Cache-Control': 'no-cache'
|
||||
},
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const config: CloudMirrorConfig = await response.json()
|
||||
|
||||
// 验证配置格式
|
||||
if (!this.validateConfig(config)) {
|
||||
throw new Error('云端配置格式不正确')
|
||||
}
|
||||
|
||||
this.currentConfig = config
|
||||
console.log('云端配置拉取成功:', config.version)
|
||||
return config
|
||||
|
||||
} catch (error) {
|
||||
console.warn('云端配置拉取失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置格式是否正确
|
||||
*/
|
||||
private validateConfig(config: any): config is CloudMirrorConfig {
|
||||
if (!config || typeof config !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查必需字段
|
||||
if (!config.version || !config.mirrors || !config.apiEndpoints || !config.downloadLinks) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查mirrors结构
|
||||
if (typeof config.mirrors !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查每个镜像类型的配置
|
||||
for (const [type, mirrors] of Object.entries(config.mirrors)) {
|
||||
if (!Array.isArray(mirrors)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查每个镜像配置
|
||||
for (const mirror of mirrors as any[]) {
|
||||
if (!mirror.key || !mirror.name || !mirror.url || !mirror.type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前有效配置(优先云端,兜底本地)
|
||||
*/
|
||||
getCurrentConfig(): CloudMirrorConfig | null {
|
||||
return this.currentConfig || this.fallbackConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置(启动时调用)
|
||||
*/
|
||||
async initializeConfig(fallbackConfig: CloudMirrorConfig): Promise<CloudMirrorConfig> {
|
||||
this.setFallbackConfig(fallbackConfig)
|
||||
|
||||
// 尝试拉取云端配置
|
||||
const cloudConfig = await this.fetchCloudConfig()
|
||||
|
||||
if (cloudConfig) {
|
||||
console.log('使用云端配置')
|
||||
return cloudConfig
|
||||
} else {
|
||||
console.log('使用本地兜底配置')
|
||||
return fallbackConfig
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动刷新配置
|
||||
*/
|
||||
async refreshConfig(): Promise<{ success: boolean; config?: CloudMirrorConfig; error?: string }> {
|
||||
try {
|
||||
const cloudConfig = await this.fetchCloudConfig()
|
||||
|
||||
if (cloudConfig) {
|
||||
return { success: true, config: cloudConfig }
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: '无法获取云端配置,继续使用当前配置',
|
||||
config: this.getCurrentConfig() || undefined
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '未知错误',
|
||||
config: this.getCurrentConfig() || undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置状态信息
|
||||
*/
|
||||
getConfigStatus(): {
|
||||
isUsingCloudConfig: boolean
|
||||
version?: string
|
||||
lastUpdated?: string
|
||||
source: 'cloud' | 'fallback'
|
||||
} {
|
||||
const config = this.getCurrentConfig()
|
||||
|
||||
return {
|
||||
isUsingCloudConfig: this.currentConfig !== null,
|
||||
version: config?.version,
|
||||
lastUpdated: config?.lastUpdated,
|
||||
source: this.currentConfig ? 'cloud' : 'fallback'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const cloudConfigManager = CloudConfigManager.getInstance()
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
sortMirrorsBySpeed,
|
||||
getFastestMirror,
|
||||
} from '@/config/mirrors'
|
||||
import { cloudConfigManager, type CloudMirrorConfig } from './cloudConfigManager'
|
||||
|
||||
/**
|
||||
* 镜像源管理器类
|
||||
@@ -23,6 +24,7 @@ export class MirrorManager {
|
||||
private mirrorConfigs: MirrorCategory = { ...ALL_MIRRORS }
|
||||
private apiEndpoints = { ...API_ENDPOINTS }
|
||||
private downloadLinks = { ...DOWNLOAD_LINKS }
|
||||
private isInitialized = false
|
||||
|
||||
private constructor() {}
|
||||
|
||||
@@ -36,6 +38,73 @@ export class MirrorManager {
|
||||
return MirrorManager.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化镜像管理器(从云端拉取配置)
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 准备兜底配置
|
||||
const fallbackConfig: CloudMirrorConfig = {
|
||||
version: '1.0.0-local',
|
||||
lastUpdated: new Date().toISOString(),
|
||||
mirrors: { ...ALL_MIRRORS },
|
||||
apiEndpoints: { ...API_ENDPOINTS },
|
||||
downloadLinks: { ...DOWNLOAD_LINKS }
|
||||
}
|
||||
|
||||
// 从云端初始化配置
|
||||
const config = await cloudConfigManager.initializeConfig(fallbackConfig)
|
||||
|
||||
// 更新本地配置
|
||||
this.mirrorConfigs = config.mirrors
|
||||
this.apiEndpoints = config.apiEndpoints
|
||||
this.downloadLinks = config.downloadLinks
|
||||
|
||||
this.isInitialized = true
|
||||
console.log('镜像管理器初始化完成')
|
||||
} catch (error) {
|
||||
console.error('镜像管理器初始化失败:', error)
|
||||
// 使用默认配置
|
||||
this.isInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动刷新云端配置
|
||||
*/
|
||||
async refreshCloudConfig(): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await cloudConfigManager.refreshConfig()
|
||||
|
||||
if (result.success && result.config) {
|
||||
// 更新本地配置
|
||||
this.mirrorConfigs = result.config.mirrors
|
||||
this.apiEndpoints = result.config.apiEndpoints
|
||||
this.downloadLinks = result.config.downloadLinks
|
||||
|
||||
return { success: true }
|
||||
} else {
|
||||
return { success: false, error: result.error }
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '刷新配置失败'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置状态
|
||||
*/
|
||||
getConfigStatus() {
|
||||
return cloudConfigManager.getConfigStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的镜像源列表
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@ import AdminCheck from '@/components/initialization/AdminCheck.vue'
|
||||
import AutoMode from '@/components/initialization/AutoMode.vue'
|
||||
import ManualMode from '@/components/initialization/ManualMode.vue'
|
||||
import type { DownloadProgress } from '@/types/initialization'
|
||||
import { mirrorManager } from '@/utils/mirrorManager'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -48,6 +49,12 @@ const backendExists = ref(false)
|
||||
const dependenciesInstalled = ref(false)
|
||||
const serviceStarted = ref(false)
|
||||
|
||||
// 镜像配置状态
|
||||
const mirrorConfigStatus = ref({
|
||||
source: 'fallback' as 'cloud' | 'fallback',
|
||||
version: ''
|
||||
})
|
||||
|
||||
// 组件引用
|
||||
const manualModeRef = ref()
|
||||
|
||||
@@ -230,6 +237,14 @@ function handleProgressUpdate(progress: DownloadProgress) {
|
||||
onMounted(async () => {
|
||||
console.log('初始化页面 onMounted 开始')
|
||||
|
||||
// 更新镜像配置状态
|
||||
const status = mirrorManager.getConfigStatus()
|
||||
mirrorConfigStatus.value = {
|
||||
source: status.source,
|
||||
version: status.version || ''
|
||||
}
|
||||
console.log('镜像配置状态:', mirrorConfigStatus.value)
|
||||
|
||||
// 测试配置系统
|
||||
try {
|
||||
console.log('测试配置系统...')
|
||||
|
||||
256
frontend/src/views/MirrorTest.vue
Normal file
256
frontend/src/views/MirrorTest.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="mirror-test-container">
|
||||
<div class="test-header">
|
||||
<h2>镜像配置测试页面</h2>
|
||||
<p>用于测试云端镜像配置拉取功能</p>
|
||||
</div>
|
||||
|
||||
<div class="test-content">
|
||||
<a-card title="配置状态" style="margin-bottom: 16px;">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="配置来源">
|
||||
<a-tag :color="configStatus.source === 'cloud' ? 'green' : 'orange'">
|
||||
{{ configStatus.source === 'cloud' ? '云端配置' : '本地兜底配置' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="配置版本" v-if="configStatus.version">
|
||||
{{ configStatus.version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="最后更新" v-if="configStatus.lastUpdated">
|
||||
{{ new Date(configStatus.lastUpdated).toLocaleString() }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card title="操作" style="margin-bottom: 16px;">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="refreshConfig" :loading="refreshing">
|
||||
刷新云端配置
|
||||
</a-button>
|
||||
<a-button @click="updateStatus">
|
||||
更新状态
|
||||
</a-button>
|
||||
<a-button @click="testCloudUrl">
|
||||
测试云端URL
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
|
||||
<a-card title="当前镜像配置" v-if="currentConfig">
|
||||
<a-tabs>
|
||||
<a-tab-pane key="git" tab="Git镜像">
|
||||
<a-table
|
||||
:dataSource="currentConfig.mirrors.git"
|
||||
:columns="mirrorColumns"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="python" tab="Python镜像">
|
||||
<a-table
|
||||
:dataSource="currentConfig.mirrors.python"
|
||||
:columns="mirrorColumns"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="pip" tab="PIP镜像">
|
||||
<a-table
|
||||
:dataSource="currentConfig.mirrors.pip"
|
||||
:columns="mirrorColumns"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
|
||||
<a-card title="测试日志" v-if="testLogs.length > 0">
|
||||
<div class="test-logs">
|
||||
<div
|
||||
v-for="(log, index) in testLogs"
|
||||
:key="index"
|
||||
:class="['log-item', log.type]"
|
||||
>
|
||||
<span class="log-time">{{ log.time }}</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { mirrorManager } from '@/utils/mirrorManager'
|
||||
import { cloudConfigManager, type CloudMirrorConfig } from '@/utils/cloudConfigManager'
|
||||
|
||||
interface TestLog {
|
||||
time: string
|
||||
message: string
|
||||
type: 'info' | 'success' | 'error' | 'warning'
|
||||
}
|
||||
|
||||
const configStatus = ref({
|
||||
isUsingCloudConfig: false,
|
||||
version: '',
|
||||
lastUpdated: '',
|
||||
source: 'fallback' as 'cloud' | 'fallback'
|
||||
})
|
||||
|
||||
const currentConfig = ref<CloudMirrorConfig | null>(null)
|
||||
const refreshing = ref(false)
|
||||
const testLogs = ref<TestLog[]>([])
|
||||
|
||||
const mirrorColumns = [
|
||||
{
|
||||
title: 'Key',
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '连通性',
|
||||
dataIndex: 'chinaConnectivity',
|
||||
key: 'chinaConnectivity',
|
||||
width: 100
|
||||
}
|
||||
]
|
||||
|
||||
const addLog = (message: string, type: TestLog['type'] = 'info') => {
|
||||
testLogs.value.unshift({
|
||||
time: new Date().toLocaleTimeString(),
|
||||
message,
|
||||
type
|
||||
})
|
||||
|
||||
// 限制日志数量
|
||||
if (testLogs.value.length > 50) {
|
||||
testLogs.value = testLogs.value.slice(0, 50)
|
||||
}
|
||||
}
|
||||
|
||||
const updateStatus = () => {
|
||||
const status = mirrorManager.getConfigStatus()
|
||||
configStatus.value = status
|
||||
currentConfig.value = cloudConfigManager.getCurrentConfig()
|
||||
addLog('状态已更新', 'info')
|
||||
}
|
||||
|
||||
const refreshConfig = async () => {
|
||||
refreshing.value = true
|
||||
addLog('开始刷新云端配置...', 'info')
|
||||
|
||||
try {
|
||||
const result = await mirrorManager.refreshCloudConfig()
|
||||
|
||||
if (result.success) {
|
||||
message.success('配置刷新成功')
|
||||
addLog('云端配置刷新成功', 'success')
|
||||
updateStatus()
|
||||
} else {
|
||||
message.warning(result.error || '刷新失败')
|
||||
addLog(`刷新失败: ${result.error}`, 'warning')
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : '未知错误'
|
||||
message.error('刷新配置失败')
|
||||
addLog(`刷新配置失败: ${errorMsg}`, 'error')
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testCloudUrl = async () => {
|
||||
addLog('测试云端URL连通性...', 'info')
|
||||
|
||||
try {
|
||||
const response = await fetch('https://download.auto-mas.top/d/AUTO_MAS/Server/mirrors.json', {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors'
|
||||
})
|
||||
addLog('云端URL连通性测试完成', 'success')
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : '连接失败'
|
||||
addLog(`云端URL连通性测试失败: ${errorMsg}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateStatus()
|
||||
addLog('镜像配置测试页面已加载', 'info')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mirror-test-container {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.test-header h2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.test-logs {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.log-item.success {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.log-item.error {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.log-item.warning {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@ import type { SelectValue } from 'ant-design-vue/es/select'
|
||||
import type { SettingsData } from '../types/settings'
|
||||
import { Service, type VersionOut } from '@/api'
|
||||
import UpdateModal from '@/components/UpdateModal.vue'
|
||||
import { mirrorManager } from '@/utils/mirrorManager'
|
||||
|
||||
const updateData = ref<Record<string, string[]>>({})
|
||||
|
||||
@@ -32,6 +33,15 @@ const activeKey = ref('basic')
|
||||
|
||||
const backendUpdateInfo = ref<VersionOut | null>(null)
|
||||
|
||||
// 镜像配置相关状态
|
||||
const mirrorConfigStatus = ref({
|
||||
isUsingCloudConfig: false,
|
||||
version: '',
|
||||
lastUpdated: '',
|
||||
source: 'fallback' as 'cloud' | 'fallback'
|
||||
})
|
||||
const refreshingConfig = ref(false)
|
||||
|
||||
const settings = reactive<SettingsData>({
|
||||
UI: {
|
||||
IfShowTray: false,
|
||||
@@ -257,6 +267,34 @@ const checkUpdate = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 镜像配置相关方法
|
||||
const updateMirrorConfigStatus = () => {
|
||||
const status = mirrorManager.getConfigStatus()
|
||||
mirrorConfigStatus.value = status
|
||||
}
|
||||
|
||||
const refreshMirrorConfig = async () => {
|
||||
refreshingConfig.value = true
|
||||
try {
|
||||
const result = await mirrorManager.refreshCloudConfig()
|
||||
if (result.success) {
|
||||
message.success('镜像配置刷新成功')
|
||||
updateMirrorConfigStatus()
|
||||
} else {
|
||||
message.warning(result.error || '刷新失败,继续使用当前配置')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新镜像配置失败:', error)
|
||||
message.error('刷新镜像配置失败')
|
||||
} finally {
|
||||
refreshingConfig.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goToMirrorTest = () => {
|
||||
router.push('/mirror-test')
|
||||
}
|
||||
|
||||
// 确认回调
|
||||
const onUpdateConfirmed = () => {
|
||||
updateVisible.value = false
|
||||
@@ -265,6 +303,7 @@ const onUpdateConfirmed = () => {
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
getBackendVersion()
|
||||
updateMirrorConfigStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1186,6 +1225,97 @@ onMounted(() => {
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 镜像配置 -->
|
||||
<a-tab-pane key="mirrors" tab="镜像配置">
|
||||
<div class="tab-content">
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<h3>镜像站配置</h3>
|
||||
<p class="section-description">
|
||||
管理下载站和加速站配置,支持从云端自动更新最新的镜像站列表
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<div class="form-item-vertical">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label">配置状态</span>
|
||||
</div>
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="配置来源">
|
||||
<a-tag :color="mirrorConfigStatus.source === 'cloud' ? 'green' : 'orange'">
|
||||
{{ mirrorConfigStatus.source === 'cloud' ? '云端配置' : '本地兜底配置' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="配置版本" v-if="mirrorConfigStatus.version">
|
||||
{{ mirrorConfigStatus.version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="最后更新" v-if="mirrorConfigStatus.lastUpdated">
|
||||
{{ new Date(mirrorConfigStatus.lastUpdated).toLocaleString() }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24" style="margin-top: 24px;">
|
||||
<a-col :span="24">
|
||||
<div class="form-item-vertical">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label">配置管理</span>
|
||||
</div>
|
||||
<a-space size="large">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="refreshMirrorConfig"
|
||||
:loading="refreshingConfig"
|
||||
size="large"
|
||||
>
|
||||
刷新云端配置
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="updateMirrorConfigStatus"
|
||||
size="large"
|
||||
>
|
||||
更新状态
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="goToMirrorTest"
|
||||
size="large"
|
||||
>
|
||||
测试页面
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24" style="margin-top: 24px;">
|
||||
<a-col :span="24">
|
||||
<div class="form-item-vertical">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label">说明</span>
|
||||
</div>
|
||||
<a-alert
|
||||
message="镜像配置说明"
|
||||
type="info"
|
||||
show-icon
|
||||
>
|
||||
<template #description>
|
||||
<ul style="margin: 8px 0; padding-left: 20px;">
|
||||
<li>应用启动时会自动尝试从云端拉取最新的镜像站配置</li>
|
||||
<li>可以手动点击"刷新云端配置"按钮获取最新配置</li>
|
||||
</ul>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 其他 -->
|
||||
<a-tab-pane key="others" tab="其他">
|
||||
<div class="tab-content">
|
||||
|
||||
Reference in New Issue
Block a user