feat(initialization): 新增配置文件操作和管理员权限检查
- 新增配置文件保存、加载和重置功能 - 添加管理员权限检查和重启为管理员的功能 - 实现 pip 包管理器安装功能 - 优化初始化流程,自动检测并安装依赖
This commit is contained in:
264
frontend/src/components/initialization/BackendStep.vue
Normal file
264
frontend/src/components/initialization/BackendStep.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>获取后端源码</h3>
|
||||
<div class="install-section">
|
||||
<p>{{ backendExists ? '更新最新的后端代码' : '获取后端源代码' }}</p>
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div
|
||||
v-for="mirror in gitMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card"
|
||||
:class="{ active: selectedGitMirror === mirror.key }"
|
||||
@click="selectedGitMirror = mirror.key"
|
||||
>
|
||||
<div class="mirror-header">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
|
||||
<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>
|
||||
</div>
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testGitMirrorSpeed" :loading="testingGitSpeed" type="primary">
|
||||
{{ testingGitSpeed ? '测速中...' : '开始测速' }}
|
||||
</a-button>
|
||||
<span class="test-note">3秒无响应视为超时</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getConfig, saveConfig } from '@/utils/config'
|
||||
|
||||
interface Mirror {
|
||||
key: string
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
backendExists: boolean
|
||||
}>()
|
||||
|
||||
const gitMirrors = ref<Mirror[]>([
|
||||
{ key: 'github', name: 'GitHub 官方', url: 'https://github.com/DLmaster361/AUTO_MAA.git', speed: null },
|
||||
{ key: 'ghfast', name: 'ghfast 镜像', url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git', speed: null }
|
||||
])
|
||||
|
||||
const selectedGitMirror = ref('github')
|
||||
const testingGitSpeed = ref(false)
|
||||
|
||||
// 加载配置中的镜像源选择
|
||||
async function loadMirrorConfig() {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
selectedGitMirror.value = config.selectedGitMirror
|
||||
console.log('Git镜像源配置已加载:', selectedGitMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('加载Git镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存镜像源选择
|
||||
async function saveMirrorConfig() {
|
||||
try {
|
||||
await saveConfig({ selectedGitMirror: selectedGitMirror.value })
|
||||
console.log('Git镜像源配置已保存:', selectedGitMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('保存Git镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url.replace('.git', ''), {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
return 9999 // 超时或失败
|
||||
}
|
||||
}
|
||||
|
||||
async function testGitMirrorSpeed() {
|
||||
testingGitSpeed.value = true
|
||||
try {
|
||||
const promises = gitMirrors.value.map(async (mirror) => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
gitMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
const fastest = gitMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedGitMirror.value = fastest.key
|
||||
await saveMirrorConfig() // 保存最快的镜像源选择
|
||||
}
|
||||
} finally {
|
||||
testingGitSpeed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getSpeedClass(speed: number | null) {
|
||||
if (speed === null) return 'speed-unknown'
|
||||
if (speed === 9999) return 'speed-timeout'
|
||||
if (speed < 500) return 'speed-fast'
|
||||
if (speed < 1500) return 'speed-medium'
|
||||
return 'speed-slow'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
selectedGitMirror,
|
||||
testGitMirrorSpeed,
|
||||
gitMirrors
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
console.log('BackendStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testGitMirrorSpeed()
|
||||
}, 200) // 延迟200ms确保组件完全渲染
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mirror-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-card {
|
||||
padding: 16px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.mirror-card:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mirror-card.active {
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
}
|
||||
|
||||
.mirror-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mirror-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.speed-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speed-badge.speed-unknown {
|
||||
background: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.speed-badge.speed-fast {
|
||||
background: var(--ant-color-success-bg);
|
||||
color: var(--ant-color-success);
|
||||
}
|
||||
|
||||
.speed-badge.speed-medium {
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
.speed-badge.speed-slow {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.speed-badge.speed-timeout {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.mirror-url {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.test-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-note {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
</style>
|
||||
263
frontend/src/components/initialization/DependenciesStep.vue
Normal file
263
frontend/src/components/initialization/DependenciesStep.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>安装 Python 依赖包</h3>
|
||||
<div class="install-section">
|
||||
<p>通过 pip 安装项目所需的 Python 依赖包</p>
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div
|
||||
v-for="mirror in pipMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card"
|
||||
:class="{ active: selectedPipMirror === mirror.key }"
|
||||
@click="selectedPipMirror = mirror.key"
|
||||
>
|
||||
<div class="mirror-header">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
|
||||
<span v-if="mirror.speed === null && !testingPipSpeed">未测试</span>
|
||||
<span v-else-if="testingPipSpeed">测试中...</span>
|
||||
<span v-else-if="mirror.speed === 9999">超时</span>
|
||||
<span v-else>{{ mirror.speed }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testPipMirrorSpeed" :loading="testingPipSpeed" type="primary">
|
||||
{{ testingPipSpeed ? '测速中...' : '重新测速' }}
|
||||
</a-button>
|
||||
<span class="test-note">3秒无响应视为超时</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getConfig, saveConfig } from '@/utils/config'
|
||||
|
||||
interface Mirror {
|
||||
key: string
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
}
|
||||
|
||||
const pipMirrors = ref<Mirror[]>([
|
||||
{ key: 'official', name: 'PyPI 官方', url: 'https://pypi.org/simple/', speed: null },
|
||||
{ key: 'tsinghua', name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple/', speed: null },
|
||||
{ key: 'aliyun', name: '阿里云', url: 'https://mirrors.aliyun.com/pypi/simple/', speed: null },
|
||||
{ key: 'douban', name: '豆瓣', url: 'https://pypi.douban.com/simple/', speed: null },
|
||||
{ key: 'ustc', name: '中科大', url: 'https://pypi.mirrors.ustc.edu.cn/simple/', speed: null },
|
||||
{ key: 'huawei', name: '华中科技大学', url: 'https://pypi.hustunique.com/simple/', speed: null }
|
||||
])
|
||||
|
||||
const selectedPipMirror = ref('tsinghua')
|
||||
const testingPipSpeed = ref(false)
|
||||
|
||||
// 加载配置中的镜像源选择
|
||||
async function loadMirrorConfig() {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
selectedPipMirror.value = config.selectedPipMirror
|
||||
console.log('pip镜像源配置已加载:', selectedPipMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('加载pip镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存镜像源选择
|
||||
async function saveMirrorConfig() {
|
||||
try {
|
||||
await saveConfig({ selectedPipMirror: selectedPipMirror.value })
|
||||
console.log('pip镜像源配置已保存:', selectedPipMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('保存pip镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
return 9999 // 超时或失败
|
||||
}
|
||||
}
|
||||
|
||||
async function testPipMirrorSpeed() {
|
||||
testingPipSpeed.value = true
|
||||
try {
|
||||
const promises = pipMirrors.value.map(async (mirror) => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
pipMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
const fastest = pipMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedPipMirror.value = fastest.key
|
||||
await saveMirrorConfig() // 保存最快的镜像源选择
|
||||
}
|
||||
} finally {
|
||||
testingPipSpeed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getSpeedClass(speed: number | null) {
|
||||
if (speed === null) return 'speed-unknown'
|
||||
if (speed === 9999) return 'speed-timeout'
|
||||
if (speed < 500) return 'speed-fast'
|
||||
if (speed < 1500) return 'speed-medium'
|
||||
return 'speed-slow'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
selectedPipMirror,
|
||||
testPipMirrorSpeed
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
console.log('DependenciesStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testPipMirrorSpeed()
|
||||
}, 200) // 延迟200ms确保组件完全渲染
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mirror-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-card {
|
||||
padding: 16px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.mirror-card:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mirror-card.active {
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
}
|
||||
|
||||
.mirror-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mirror-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.speed-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speed-badge.speed-unknown {
|
||||
background: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.speed-badge.speed-fast {
|
||||
background: var(--ant-color-success-bg);
|
||||
color: var(--ant-color-success);
|
||||
}
|
||||
|
||||
.speed-badge.speed-medium {
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
.speed-badge.speed-slow {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.speed-badge.speed-timeout {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.mirror-url {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.test-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-note {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
</style>
|
||||
63
frontend/src/components/initialization/GitStep.vue
Normal file
63
frontend/src/components/initialization/GitStep.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>Git 版本控制工具</h3>
|
||||
<div v-if="!gitInstalled" class="install-section">
|
||||
<p>需要安装 Git 工具来获取源代码</p>
|
||||
<div class="git-info">
|
||||
<a-alert
|
||||
message="Git 工具信息"
|
||||
description="将安装便携版 Git 工具,包含完整的版本控制功能,无需系统安装。"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="Git 工具已安装" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
gitInstalled: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.git-info {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.already-installed {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
64
frontend/src/components/initialization/PipStep.vue
Normal file
64
frontend/src/components/initialization/PipStep.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>安装 pip 包管理器</h3>
|
||||
<div v-if="!pipInstalled" class="install-section">
|
||||
<p>pip 是 Python 的包管理工具,用于安装和管理 Python 包</p>
|
||||
|
||||
<div class="pip-info">
|
||||
<a-alert
|
||||
message="pip 安装信息"
|
||||
description="将自动下载并安装 pip 包管理器,这是安装 Python 依赖包的必要工具。"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="pip 已安装" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
pipInstalled: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pip-info {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.already-installed {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
274
frontend/src/components/initialization/PythonStep.vue
Normal file
274
frontend/src/components/initialization/PythonStep.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>Python 运行环境</h3>
|
||||
<div v-if="!pythonInstalled" class="install-section">
|
||||
<p>需要安装 Python 3.13.0 运行环境(64位嵌入式版本)</p>
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div v-for="mirror in pythonMirrors" :key="mirror.key" class="mirror-card"
|
||||
:class="{ active: selectedPythonMirror === mirror.key }" @click="selectedPythonMirror = mirror.key">
|
||||
<div class="mirror-header">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
|
||||
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
|
||||
<span v-else-if="testingSpeed">测试中...</span>
|
||||
<span v-else-if="mirror.speed === 9999">超时</span>
|
||||
<span v-else>{{ mirror.speed }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
|
||||
{{ testingSpeed ? '测速中...' : '重新测速' }}
|
||||
</a-button>
|
||||
<span class="test-note">3秒无响应视为超时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="Python 环境已安装" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getConfig, saveConfig } from '@/utils/config'
|
||||
|
||||
interface Mirror {
|
||||
key: string
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
pythonInstalled: boolean
|
||||
}>()
|
||||
|
||||
const pythonMirrors = ref<Mirror[]>([
|
||||
{ key: 'official', name: 'Python 官方', url: 'https://www.python.org/ftp/python/3.13.0/', speed: null },
|
||||
{ key: 'tsinghua', name: '清华 TUNA 镜像', url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/', speed: null },
|
||||
{ key: 'ustc', name: '中科大镜像', url: 'https://mirrors.ustc.edu.cn/python/3.13.0/', speed: null },
|
||||
{ key: 'huawei', name: '华为云镜像', url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/', speed: null },
|
||||
{ key: 'aliyun', name: '阿里云镜像', url: 'https://mirrors.aliyun.com/python-release/windows/', speed: null }
|
||||
])
|
||||
|
||||
const selectedPythonMirror = ref('tsinghua')
|
||||
const testingSpeed = ref(false)
|
||||
|
||||
// 加载配置中的镜像源选择
|
||||
async function loadMirrorConfig() {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
selectedPythonMirror.value = config.selectedPythonMirror
|
||||
console.log('Python镜像源配置已加载:', selectedPythonMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('加载Python镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存镜像源选择
|
||||
async function saveMirrorConfig() {
|
||||
try {
|
||||
await saveConfig({ selectedPythonMirror: selectedPythonMirror.value })
|
||||
console.log('Python镜像源配置已保存:', selectedPythonMirror.value)
|
||||
} catch (error) {
|
||||
console.warn('保存Python镜像源配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
return 9999 // 超时或失败
|
||||
}
|
||||
}
|
||||
|
||||
async function testPythonMirrorSpeed() {
|
||||
testingSpeed.value = true
|
||||
try {
|
||||
const promises = pythonMirrors.value.map(async (mirror) => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedPythonMirror.value = fastest.key
|
||||
await saveMirrorConfig() // 保存最快的镜像源选择
|
||||
}
|
||||
} finally {
|
||||
testingSpeed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getSpeedClass(speed: number | null) {
|
||||
if (speed === null) return 'speed-unknown'
|
||||
if (speed === 9999) return 'speed-timeout'
|
||||
if (speed < 500) return 'speed-fast'
|
||||
if (speed < 1500) return 'speed-medium'
|
||||
return 'speed-slow'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
selectedPythonMirror,
|
||||
testPythonMirrorSpeed
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
if (!props.pythonInstalled) {
|
||||
console.log('PythonStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testPythonMirrorSpeed()
|
||||
}, 200) // 延迟200ms确保组件完全渲染
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mirror-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-card {
|
||||
padding: 16px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.mirror-card:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mirror-card.active {
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
}
|
||||
|
||||
.mirror-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mirror-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.speed-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speed-badge.speed-unknown {
|
||||
background: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.speed-badge.speed-fast {
|
||||
background: var(--ant-color-success-bg);
|
||||
color: var(--ant-color-success);
|
||||
}
|
||||
|
||||
.speed-badge.speed-medium {
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
.speed-badge.speed-slow {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.speed-badge.speed-timeout {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.mirror-url {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.test-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-note {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.already-installed {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
63
frontend/src/components/initialization/ServiceStep.vue
Normal file
63
frontend/src/components/initialization/ServiceStep.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>启动后端服务</h3>
|
||||
<div class="service-status">
|
||||
<a-spin :spinning="startingService">
|
||||
<div class="status-info">
|
||||
<p>{{ serviceStatus }}</p>
|
||||
<a-progress v-if="showServiceProgress" :percent="serviceProgress" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const startingService = ref(false)
|
||||
const showServiceProgress = ref(false)
|
||||
const serviceProgress = ref(0)
|
||||
const serviceStatus = ref('准备启动后端服务...')
|
||||
|
||||
defineExpose({
|
||||
startingService,
|
||||
showServiceProgress,
|
||||
serviceProgress,
|
||||
serviceStatus
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.service-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-info p {
|
||||
font-size: 16px;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
145
frontend/src/components/initialization/ThemeStep.vue
Normal file
145
frontend/src/components/initialization/ThemeStep.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>选择您的主题偏好</h3>
|
||||
<div class="theme-settings">
|
||||
<div class="setting-group">
|
||||
<label>主题模式</label>
|
||||
<a-radio-group v-model:value="selectedThemeMode" @change="onThemeModeChange">
|
||||
<a-radio-button value="light">浅色模式</a-radio-button>
|
||||
<a-radio-button value="dark">深色模式</a-radio-button>
|
||||
<a-radio-button value="system">跟随系统</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label>主题色彩</label>
|
||||
<div class="color-picker">
|
||||
<div
|
||||
v-for="(color, key) in themeColors"
|
||||
:key="key"
|
||||
class="color-option"
|
||||
:class="{ active: selectedThemeColor === key }"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="onThemeColorChange(key)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { getConfig, saveThemeConfig } from '@/utils/config'
|
||||
import type { ThemeMode, ThemeColor } from '@/composables/useTheme'
|
||||
|
||||
const { themeColors, setThemeMode, setThemeColor } = useTheme()
|
||||
|
||||
const selectedThemeMode = ref<ThemeMode>('system')
|
||||
const selectedThemeColor = ref<ThemeColor>('blue')
|
||||
|
||||
async function onThemeModeChange() {
|
||||
setThemeMode(selectedThemeMode.value)
|
||||
await saveSettings()
|
||||
}
|
||||
|
||||
async function onThemeColorChange(color: ThemeColor) {
|
||||
selectedThemeColor.value = color
|
||||
setThemeColor(color)
|
||||
await saveSettings()
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
await saveThemeConfig(selectedThemeMode.value, selectedThemeColor.value)
|
||||
console.log('主题设置已保存:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value
|
||||
})
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
selectedThemeMode.value = config.themeMode
|
||||
selectedThemeColor.value = config.themeColor
|
||||
setThemeMode(selectedThemeMode.value)
|
||||
setThemeColor(selectedThemeColor.value)
|
||||
console.log('主题设置已加载:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('Failed to load theme settings:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
loadSettings,
|
||||
saveSettings,
|
||||
selectedThemeMode,
|
||||
selectedThemeColor
|
||||
})
|
||||
|
||||
// 组件挂载时加载设置
|
||||
onMounted(async () => {
|
||||
await loadSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.theme-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.setting-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.setting-group label {
|
||||
font-weight: 500;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-option:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.color-option.active {
|
||||
border-color: var(--ant-color-text);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/initialization',
|
||||
name: 'Initialization',
|
||||
component: () => import('../views/Initialization.vue'),
|
||||
component: () => import('../views/InitializationNew.vue'),
|
||||
meta: { title: '初始化' },
|
||||
},
|
||||
{
|
||||
@@ -87,16 +87,18 @@ const router = createRouter({
|
||||
routes,
|
||||
})
|
||||
|
||||
import { isAppInitialized } from '@/utils/config'
|
||||
|
||||
// 添加路由守卫,确保在生产环境中也能正确进入初始化页面
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
console.log('路由守卫:', { to: to.path, from: from.path })
|
||||
|
||||
// 如果访问的不是初始化页面,且没有初始化标记,则重定向到初始化页面
|
||||
if (to.path !== '/initialization') {
|
||||
const isInitialized = localStorage.getItem('app-initialized')
|
||||
console.log('检查初始化状态:', isInitialized)
|
||||
const initialized = await isAppInitialized()
|
||||
console.log('检查初始化状态:', initialized)
|
||||
|
||||
if (!isInitialized) {
|
||||
if (!initialized) {
|
||||
console.log('应用未初始化,重定向到初始化页面')
|
||||
next('/initialization')
|
||||
return
|
||||
|
||||
163
frontend/src/utils/config.ts
Normal file
163
frontend/src/utils/config.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { createComponentLogger } from './logger'
|
||||
import type { ThemeMode, ThemeColor } from '@/composables/useTheme'
|
||||
|
||||
const logger = createComponentLogger('Config')
|
||||
|
||||
export interface FrontendConfig {
|
||||
// 基础配置
|
||||
isFirstLaunch: boolean
|
||||
init: boolean
|
||||
lastUpdateCheck?: string
|
||||
|
||||
// 主题设置
|
||||
themeMode: ThemeMode
|
||||
themeColor: ThemeColor
|
||||
|
||||
// 镜像源设置
|
||||
selectedGitMirror: string
|
||||
selectedPythonMirror: string
|
||||
selectedPipMirror: string
|
||||
|
||||
// 安装状态
|
||||
pythonInstalled?: boolean
|
||||
gitInstalled?: boolean
|
||||
backendExists?: boolean
|
||||
dependenciesInstalled?: boolean
|
||||
pipInstalled?: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: FrontendConfig = {
|
||||
isFirstLaunch: true,
|
||||
init: false,
|
||||
themeMode: 'system',
|
||||
themeColor: 'blue',
|
||||
selectedGitMirror: 'github',
|
||||
selectedPythonMirror: 'tsinghua',
|
||||
selectedPipMirror: 'tsinghua',
|
||||
pythonInstalled: false,
|
||||
gitInstalled: false,
|
||||
backendExists: false,
|
||||
dependenciesInstalled: false,
|
||||
pipInstalled: false
|
||||
}
|
||||
|
||||
// 读取配置(内部使用,不触发保存)
|
||||
async function getConfigInternal(): Promise<FrontendConfig> {
|
||||
try {
|
||||
// 优先从文件读取配置
|
||||
const fileConfig = await window.electronAPI.loadConfig()
|
||||
if (fileConfig) {
|
||||
console.log('从文件加载配置:', fileConfig)
|
||||
return { ...DEFAULT_CONFIG, ...fileConfig }
|
||||
}
|
||||
|
||||
// 如果文件不存在,尝试从localStorage迁移
|
||||
const localConfig = localStorage.getItem('app-config')
|
||||
const themeConfig = localStorage.getItem('theme-settings')
|
||||
|
||||
let config = { ...DEFAULT_CONFIG }
|
||||
|
||||
if (localConfig) {
|
||||
const parsed = JSON.parse(localConfig)
|
||||
config = { ...config, ...parsed }
|
||||
console.log('从localStorage迁移配置:', parsed)
|
||||
}
|
||||
|
||||
if (themeConfig) {
|
||||
const parsed = JSON.parse(themeConfig)
|
||||
config.themeMode = parsed.themeMode || 'system'
|
||||
config.themeColor = parsed.themeColor || 'blue'
|
||||
console.log('从localStorage迁移主题配置:', parsed)
|
||||
}
|
||||
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error('读取配置失败:', error)
|
||||
logger.error('读取配置失败', error)
|
||||
return { ...DEFAULT_CONFIG }
|
||||
}
|
||||
}
|
||||
|
||||
// 读取配置(公共接口)
|
||||
export async function getConfig(): Promise<FrontendConfig> {
|
||||
const config = await getConfigInternal()
|
||||
|
||||
// 如果是从localStorage迁移的配置,保存到文件并清理localStorage
|
||||
const hasLocalStorage = localStorage.getItem('app-config') || localStorage.getItem('theme-settings')
|
||||
if (hasLocalStorage) {
|
||||
try {
|
||||
await window.electronAPI.saveConfig(config)
|
||||
localStorage.removeItem('app-config')
|
||||
localStorage.removeItem('theme-settings')
|
||||
localStorage.removeItem('app-initialized')
|
||||
console.log('配置已从localStorage迁移到文件')
|
||||
} catch (error) {
|
||||
console.error('迁移配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
export async function saveConfig(config: Partial<FrontendConfig>): Promise<void> {
|
||||
try {
|
||||
console.log('开始保存配置:', config)
|
||||
const currentConfig = await getConfigInternal() // 使用内部函数避免递归
|
||||
const newConfig = { ...currentConfig, ...config }
|
||||
console.log('合并后的配置:', newConfig)
|
||||
await window.electronAPI.saveConfig(newConfig)
|
||||
console.log('配置保存成功')
|
||||
logger.info('配置已保存', newConfig)
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
logger.error('保存配置失败', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 重置配置
|
||||
export async function resetConfig(): Promise<void> {
|
||||
try {
|
||||
await window.electronAPI.resetConfig()
|
||||
localStorage.removeItem('app-config')
|
||||
localStorage.removeItem('theme-settings')
|
||||
localStorage.removeItem('app-initialized')
|
||||
logger.info('配置已重置')
|
||||
} catch (error) {
|
||||
logger.error('重置配置失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已初始化
|
||||
export async function isAppInitialized(): Promise<boolean> {
|
||||
const config = await getConfig()
|
||||
console.log('isAppInitialized 检查配置:', config)
|
||||
console.log('init 字段值:', config.init)
|
||||
console.log('init === true:', config.init === true)
|
||||
return config.init === true
|
||||
}
|
||||
|
||||
// 检查是否第一次启动
|
||||
export async function isFirstLaunch(): Promise<boolean> {
|
||||
const config = await getConfig()
|
||||
return config.isFirstLaunch === true
|
||||
}
|
||||
|
||||
// 设置初始化完成
|
||||
export async function setInitialized(value: boolean = true): Promise<void> {
|
||||
await saveConfig({ init: value, isFirstLaunch: false })
|
||||
}
|
||||
|
||||
// 保存主题设置
|
||||
export async function saveThemeConfig(themeMode: ThemeMode, themeColor: ThemeColor): Promise<void> {
|
||||
await saveConfig({ themeMode, themeColor })
|
||||
}
|
||||
|
||||
// 保存镜像源设置
|
||||
export async function saveMirrorConfig(gitMirror: string, pythonMirror?: string, pipMirror?: string): Promise<void> {
|
||||
const config: Partial<FrontendConfig> = { selectedGitMirror: gitMirror }
|
||||
if (pythonMirror) config.selectedPythonMirror = pythonMirror
|
||||
if (pipMirror) config.selectedPipMirror = pipMirror
|
||||
await saveConfig(config)
|
||||
}
|
||||
806
frontend/src/views/InitializationNew.vue
Normal file
806
frontend/src/views/InitializationNew.vue
Normal file
@@ -0,0 +1,806 @@
|
||||
<template>
|
||||
<div class="initialization-container">
|
||||
<!-- 管理员权限检查 -->
|
||||
<div v-if="!isAdmin" class="admin-check">
|
||||
<a-result
|
||||
status="warning"
|
||||
title="需要管理员权限"
|
||||
sub-title="为了正常安装和配置环境,请以管理员权限运行此应用"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="restartAsAdmin">
|
||||
重新以管理员权限启动
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
|
||||
<!-- 自动初始化模式 -->
|
||||
<div v-else-if="autoMode" class="auto-mode">
|
||||
<div class="header">
|
||||
<h1>AUTO MAA 自动初始化</h1>
|
||||
<p>检测到环境已配置,正在自动启动...</p>
|
||||
</div>
|
||||
|
||||
<div class="auto-progress">
|
||||
<a-spin size="large" />
|
||||
<div class="progress-text">{{ autoProgressText }}</div>
|
||||
<a-progress :percent="autoProgress" :status="autoProgressStatus" />
|
||||
</div>
|
||||
|
||||
<div class="auto-actions">
|
||||
<a-button @click="switchToManualMode">切换到手动模式</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 手动初始化模式 -->
|
||||
<div v-else class="manual-mode">
|
||||
<div class="header">
|
||||
<h1>AUTO MAA 初始化向导</h1>
|
||||
<p>欢迎使用 AUTO MAA,让我们来配置您的运行环境</p>
|
||||
|
||||
<div class="header-actions">
|
||||
<a-button size="large" type="primary" @click="skipToHome">
|
||||
跳转至首页(仅开发用)
|
||||
</a-button>
|
||||
<a-button size="large" type="default" @click="jumpToStep(6)" style="margin-left: 16px;">
|
||||
跳到启动服务(第七步)
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 包管理器" />
|
||||
<a-step title="Git 工具" description="安装 Git 版本控制工具" />
|
||||
<a-step title="源码获取" description="获取最新的后端代码" />
|
||||
<a-step title="依赖安装" description="安装 Python 依赖包" />
|
||||
<a-step title="启动服务" description="启动后端服务" />
|
||||
</a-steps>
|
||||
|
||||
<!-- 全局进度条 -->
|
||||
<div v-if="isProcessing" class="global-progress">
|
||||
<a-progress
|
||||
:percent="globalProgress"
|
||||
:status="globalProgressStatus"
|
||||
:show-info="true"
|
||||
/>
|
||||
<div class="progress-text">{{ progressText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="step-content">
|
||||
<!-- 步骤 0: 主题设置 -->
|
||||
<ThemeStep v-if="currentStep === 0" ref="themeStepRef" />
|
||||
|
||||
<!-- 步骤 1: Python 环境 -->
|
||||
<PythonStep v-if="currentStep === 1" :python-installed="pythonInstalled" ref="pythonStepRef" />
|
||||
|
||||
<!-- 步骤 2: pip 安装 -->
|
||||
<PipStep v-if="currentStep === 2" :pip-installed="pipInstalled" />
|
||||
|
||||
<!-- 步骤 3: Git 工具 -->
|
||||
<GitStep v-if="currentStep === 3" :git-installed="gitInstalled" />
|
||||
|
||||
<!-- 步骤 4: 源码获取 -->
|
||||
<BackendStep v-if="currentStep === 4" :backend-exists="backendExists" ref="backendStepRef" />
|
||||
|
||||
<!-- 步骤 5: 依赖安装 -->
|
||||
<DependenciesStep v-if="currentStep === 5" ref="dependenciesStepRef" />
|
||||
|
||||
<!-- 步骤 6: 启动服务 -->
|
||||
<ServiceStep v-if="currentStep === 6" ref="serviceStepRef" />
|
||||
</div>
|
||||
|
||||
<div class="step-actions">
|
||||
<a-button
|
||||
v-if="currentStep > 0"
|
||||
@click="prevStep"
|
||||
:disabled="isProcessing"
|
||||
>
|
||||
上一步
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
v-if="currentStep < 6"
|
||||
type="primary"
|
||||
@click="nextStep"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
{{ getNextButtonText() }}
|
||||
</a-button>
|
||||
|
||||
<!-- 第7步启动服务按钮 -->
|
||||
<a-button
|
||||
v-if="currentStep === 6 && !serviceStarted"
|
||||
type="primary"
|
||||
@click="nextStep"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
启动服务
|
||||
</a-button>
|
||||
|
||||
<!-- 服务启动完成后的进入应用按钮 -->
|
||||
<a-button
|
||||
v-if="currentStep === 6 && serviceStarted"
|
||||
type="primary"
|
||||
@click="enterApp"
|
||||
>
|
||||
进入应用
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
<a-alert
|
||||
:message="errorMessage"
|
||||
type="error"
|
||||
show-icon
|
||||
closable
|
||||
@close="errorMessage = ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
import { getConfig, saveConfig, setInitialized } from '@/utils/config'
|
||||
import ThemeStep from '@/components/initialization/ThemeStep.vue'
|
||||
import PythonStep from '@/components/initialization/PythonStep.vue'
|
||||
import PipStep from '@/components/initialization/PipStep.vue'
|
||||
import GitStep from '@/components/initialization/GitStep.vue'
|
||||
import BackendStep from '@/components/initialization/BackendStep.vue'
|
||||
import DependenciesStep from '@/components/initialization/DependenciesStep.vue'
|
||||
import ServiceStep from '@/components/initialization/ServiceStep.vue'
|
||||
import type { DownloadProgress } from '@/types/initialization'
|
||||
|
||||
const router = useRouter()
|
||||
const logger = createComponentLogger('InitializationNew')
|
||||
|
||||
// 基础状态
|
||||
const currentStep = ref(0)
|
||||
const stepStatus = ref<'wait' | 'process' | 'finish' | 'error'>('process')
|
||||
const errorMessage = ref('')
|
||||
const isProcessing = ref(false)
|
||||
const isAdmin = ref(true)
|
||||
|
||||
// 模式控制
|
||||
const autoMode = ref(false)
|
||||
const autoProgress = ref(0)
|
||||
const autoProgressText = ref('')
|
||||
const autoProgressStatus = ref<'normal' | 'exception' | 'success'>('normal')
|
||||
|
||||
// 安装状态
|
||||
const pythonInstalled = ref(false)
|
||||
const pipInstalled = ref(false)
|
||||
const gitInstalled = ref(false)
|
||||
const backendExists = ref(false)
|
||||
const dependenciesInstalled = ref(false)
|
||||
const serviceStarted = ref(false)
|
||||
|
||||
// 全局进度条状态
|
||||
const globalProgress = ref(0)
|
||||
const globalProgressStatus = ref<'normal' | 'exception' | 'success'>('normal')
|
||||
const progressText = ref('')
|
||||
|
||||
// 组件引用
|
||||
const themeStepRef = ref()
|
||||
const pythonStepRef = ref()
|
||||
const backendStepRef = ref()
|
||||
const dependenciesStepRef = ref()
|
||||
const serviceStepRef = ref()
|
||||
|
||||
// 计算属性
|
||||
const allCompleted = computed(() =>
|
||||
pythonInstalled.value && pipInstalled.value && gitInstalled.value &&
|
||||
backendExists.value && dependenciesInstalled.value && serviceStarted.value
|
||||
)
|
||||
|
||||
// 基础功能函数
|
||||
function skipToHome() {
|
||||
router.push('/home')
|
||||
}
|
||||
|
||||
function jumpToStep(step: number) {
|
||||
currentStep.value = step
|
||||
}
|
||||
|
||||
function switchToManualMode() {
|
||||
autoMode.value = false
|
||||
}
|
||||
|
||||
async function restartAsAdmin() {
|
||||
try {
|
||||
await window.electronAPI.restartAsAdmin()
|
||||
} catch (error) {
|
||||
logger.error('重启为管理员失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤控制
|
||||
function prevStep() {
|
||||
if (currentStep.value > 0) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
async function nextStep() {
|
||||
console.log('nextStep 被调用,当前步骤:', currentStep.value)
|
||||
isProcessing.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
switch (currentStep.value) {
|
||||
case 0: // 主题设置
|
||||
console.log('执行主题设置')
|
||||
themeStepRef.value?.saveSettings()
|
||||
break
|
||||
case 1: // Python 环境
|
||||
console.log('执行Python环境安装')
|
||||
if (!pythonInstalled.value) {
|
||||
await installPython()
|
||||
}
|
||||
break
|
||||
case 2: // pip 安装
|
||||
console.log('执行pip安装')
|
||||
if (!pipInstalled.value) {
|
||||
await installPip()
|
||||
}
|
||||
break
|
||||
case 3: // Git 工具
|
||||
console.log('执行Git工具安装')
|
||||
if (!gitInstalled.value) {
|
||||
await installGit()
|
||||
}
|
||||
break
|
||||
case 4: // 源码获取
|
||||
console.log('执行源码获取')
|
||||
if (!backendExists.value) {
|
||||
await cloneBackend()
|
||||
} else {
|
||||
await updateBackend()
|
||||
}
|
||||
break
|
||||
case 5: // 依赖安装
|
||||
console.log('执行依赖安装')
|
||||
if (!dependenciesInstalled.value) {
|
||||
await installDependencies()
|
||||
}
|
||||
break
|
||||
case 6: // 启动服务
|
||||
console.log('执行启动服务')
|
||||
await startBackendService()
|
||||
break
|
||||
}
|
||||
|
||||
if (currentStep.value < 6) {
|
||||
currentStep.value++
|
||||
// 进入新步骤时自动开始测速
|
||||
await autoStartSpeedTest()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('nextStep 执行出错:', error)
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
stepStatus.value = 'error'
|
||||
} finally {
|
||||
isProcessing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getNextButtonText() {
|
||||
switch (currentStep.value) {
|
||||
case 0: return '下一步'
|
||||
case 1: return pythonInstalled.value ? '下一步' : '安装 Python'
|
||||
case 2: return pipInstalled.value ? '下一步' : '安装 pip'
|
||||
case 3: return gitInstalled.value ? '下一步' : '安装 Git'
|
||||
case 4: return backendExists.value ? '更新代码' : '获取代码'
|
||||
case 5: return '安装依赖'
|
||||
case 6: return '启动服务'
|
||||
default: return '下一步'
|
||||
}
|
||||
}
|
||||
|
||||
// 自动开始测速
|
||||
async function autoStartSpeedTest() {
|
||||
// 延迟一下确保组件已经挂载
|
||||
setTimeout(async () => {
|
||||
switch (currentStep.value) {
|
||||
case 1: // Python 环境
|
||||
if (!pythonInstalled.value && pythonStepRef.value?.testPythonMirrorSpeed) {
|
||||
console.log('自动开始Python镜像测速')
|
||||
await pythonStepRef.value.testPythonMirrorSpeed()
|
||||
}
|
||||
break
|
||||
case 4: // 源码获取
|
||||
if (backendStepRef.value?.testGitMirrorSpeed) {
|
||||
console.log('自动开始Git镜像测速')
|
||||
await backendStepRef.value.testGitMirrorSpeed()
|
||||
}
|
||||
break
|
||||
case 5: // 依赖安装
|
||||
if (!dependenciesInstalled.value && dependenciesStepRef.value?.testPipMirrorSpeed) {
|
||||
console.log('自动开始pip镜像测速')
|
||||
await dependenciesStepRef.value.testPipMirrorSpeed()
|
||||
}
|
||||
break
|
||||
}
|
||||
}, 500) // 延迟500ms确保组件完全加载
|
||||
}
|
||||
|
||||
// 安装函数
|
||||
async function installPython() {
|
||||
logger.info('开始安装Python')
|
||||
const mirror = pythonStepRef.value?.selectedPythonMirror || 'tsinghua'
|
||||
const result = await window.electronAPI.downloadPython(mirror)
|
||||
if (result.success) {
|
||||
logger.info('Python安装成功')
|
||||
pythonInstalled.value = true
|
||||
saveConfig({ pythonInstalled: true })
|
||||
} else {
|
||||
logger.error('Python安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function installPip() {
|
||||
logger.info('开始安装pip')
|
||||
const result = await window.electronAPI.installPip()
|
||||
if (result.success) {
|
||||
logger.info('pip安装成功')
|
||||
pipInstalled.value = true
|
||||
saveConfig({ pipInstalled: true })
|
||||
} else {
|
||||
logger.error('pip安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function installGit() {
|
||||
logger.info('开始安装Git工具')
|
||||
const result = await window.electronAPI.downloadGit()
|
||||
if (result.success) {
|
||||
logger.info('Git工具安装成功')
|
||||
gitInstalled.value = true
|
||||
saveConfig({ gitInstalled: true })
|
||||
} else {
|
||||
logger.error('Git工具安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function cloneBackend() {
|
||||
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
|
||||
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
|
||||
logger.info('开始克隆后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
const result = await window.electronAPI.cloneBackend(mirror?.url)
|
||||
if (result.success) {
|
||||
logger.info('后端代码克隆成功')
|
||||
backendExists.value = true
|
||||
saveConfig({ backendExists: true })
|
||||
} else {
|
||||
logger.error('后端代码克隆失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBackend() {
|
||||
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
|
||||
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
|
||||
logger.info('开始更新后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
const result = await window.electronAPI.updateBackend(mirror?.url)
|
||||
if (!result.success) {
|
||||
logger.error('后端代码更新失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
logger.info('后端代码更新成功')
|
||||
}
|
||||
|
||||
async function installDependencies() {
|
||||
logger.info('开始安装Python依赖')
|
||||
const mirror = dependenciesStepRef.value?.selectedPipMirror || 'tsinghua'
|
||||
const result = await window.electronAPI.installDependencies(mirror)
|
||||
if (result.success) {
|
||||
logger.info('Python依赖安装成功')
|
||||
dependenciesInstalled.value = true
|
||||
saveConfig({ dependenciesInstalled: true })
|
||||
} else {
|
||||
logger.error('Python依赖安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function startBackendService() {
|
||||
logger.info('开始启动后端服务')
|
||||
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.startingService = true
|
||||
serviceStepRef.value.showServiceProgress = true
|
||||
serviceStepRef.value.serviceStatus = '正在启动后端服务...'
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.startBackend()
|
||||
if (result.success) {
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.serviceProgress = 100
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动成功'
|
||||
}
|
||||
serviceStarted.value = true
|
||||
stepStatus.value = 'finish'
|
||||
logger.info('后端服务启动成功')
|
||||
} else {
|
||||
logger.error('后端服务启动失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动失败'
|
||||
}
|
||||
logger.error('后端服务启动异常', error)
|
||||
throw error
|
||||
} finally {
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.startingService = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 进入应用
|
||||
async function enterApp() {
|
||||
try {
|
||||
// 设置初始化完成标记
|
||||
await setInitialized(true)
|
||||
console.log('设置初始化完成标记,跳转到首页')
|
||||
router.push('/home')
|
||||
} catch (error) {
|
||||
console.error('进入应用失败:', error)
|
||||
errorMessage.value = '保存配置失败,请重试'
|
||||
}
|
||||
}
|
||||
|
||||
// 检查环境状态
|
||||
async function checkEnvironment() {
|
||||
try {
|
||||
logger.info('开始检查环境状态')
|
||||
const status = await window.electronAPI.checkEnvironment()
|
||||
|
||||
logger.info('环境检查结果', status)
|
||||
console.log('环境检查结果:', status)
|
||||
|
||||
pythonInstalled.value = status.pythonExists
|
||||
gitInstalled.value = status.gitExists
|
||||
backendExists.value = status.backendExists
|
||||
dependenciesInstalled.value = status.dependenciesInstalled
|
||||
|
||||
// 检查配置文件中的状态
|
||||
const config = await getConfig()
|
||||
pipInstalled.value = config.pipInstalled || false
|
||||
|
||||
// 更新配置文件中的状态,确保与实际环境一致
|
||||
const needsUpdate =
|
||||
config.pythonInstalled !== status.pythonExists ||
|
||||
config.gitInstalled !== status.gitExists ||
|
||||
config.backendExists !== status.backendExists ||
|
||||
config.dependenciesInstalled !== status.dependenciesInstalled
|
||||
|
||||
if (needsUpdate) {
|
||||
console.log('更新配置文件中的环境状态')
|
||||
await saveConfig({
|
||||
pythonInstalled: status.pythonExists,
|
||||
gitInstalled: status.gitExists,
|
||||
backendExists: status.backendExists,
|
||||
dependenciesInstalled: status.dependenciesInstalled
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否第一次启动
|
||||
const isFirst = config.isFirstLaunch
|
||||
console.log('是否第一次启动:', isFirst)
|
||||
|
||||
// 检查是否应该进入自动模式
|
||||
console.log('自动模式判断条件:')
|
||||
console.log('- 不是第一次启动:', !isFirst)
|
||||
console.log('- 配置显示已初始化:', config.init)
|
||||
console.log('- 环境检查结果:', status.isInitialized)
|
||||
|
||||
// 如果配置显示已初始化且不是第一次启动,进入自动模式
|
||||
// 不再依赖环境检查结果,因为配置文件更准确
|
||||
if (!isFirst && config.init) {
|
||||
logger.info('非首次启动且配置显示已初始化,进入自动模式')
|
||||
console.log('进入自动模式,开始自动启动流程')
|
||||
autoMode.value = true
|
||||
await autoStartProcess()
|
||||
} else {
|
||||
logger.info('首次启动或配置显示未初始化,进入手动模式')
|
||||
console.log('进入手动模式,当前步骤:', currentStep.value)
|
||||
console.log('原因: isFirst =', isFirst, ', config.init =', config.init)
|
||||
|
||||
// 如果是首次启动,从主题设置开始
|
||||
if (isFirst) {
|
||||
currentStep.value = 0
|
||||
console.log('首次启动,从主题设置开始')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
logger.error('环境检查失败', error)
|
||||
console.error('环境检查失败:', error)
|
||||
errorMessage.value = errorMsg
|
||||
}
|
||||
}
|
||||
|
||||
// 自动启动流程
|
||||
async function autoStartProcess() {
|
||||
try {
|
||||
// 获取配置中保存的镜像源设置
|
||||
const config = await getConfig()
|
||||
|
||||
autoProgressText.value = '检查Git仓库更新...'
|
||||
autoProgress.value = 20
|
||||
|
||||
// 检查Git仓库是否有更新
|
||||
const hasUpdate = await checkGitUpdate()
|
||||
|
||||
if (hasUpdate) {
|
||||
autoProgressText.value = '发现更新,正在更新代码...'
|
||||
autoProgress.value = 40
|
||||
|
||||
// 使用配置中保存的Git镜像源
|
||||
const gitMirrorUrl = getGitMirrorUrl(config.selectedGitMirror)
|
||||
const result = await window.electronAPI.updateBackend(gitMirrorUrl)
|
||||
if (!result.success) {
|
||||
throw new Error(`代码更新失败: ${result.error}`)
|
||||
}
|
||||
|
||||
autoProgressText.value = '更新依赖包...'
|
||||
autoProgress.value = 60
|
||||
|
||||
// 使用配置中保存的pip镜像源
|
||||
const pipResult = await window.electronAPI.installDependencies(config.selectedPipMirror)
|
||||
if (!pipResult.success) {
|
||||
throw new Error(`依赖更新失败: ${pipResult.error}`)
|
||||
}
|
||||
}
|
||||
|
||||
autoProgressText.value = '启动后端服务...'
|
||||
autoProgress.value = 80
|
||||
await startBackendService()
|
||||
|
||||
autoProgressText.value = '启动完成!'
|
||||
autoProgress.value = 100
|
||||
autoProgressStatus.value = 'success'
|
||||
|
||||
logger.info('自动启动流程完成,即将进入应用')
|
||||
|
||||
// 延迟2秒后自动进入应用
|
||||
setTimeout(() => {
|
||||
enterApp()
|
||||
}, 2000)
|
||||
|
||||
} catch (error) {
|
||||
logger.error('自动启动流程失败', error)
|
||||
autoProgressText.value = `自动启动失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
autoProgressStatus.value = 'exception'
|
||||
|
||||
// 5秒后提供切换到手动模式的选项
|
||||
setTimeout(() => {
|
||||
if (autoProgressStatus.value === 'exception') {
|
||||
autoProgressText.value = '自动启动失败,请点击下方按钮切换到手动模式'
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据镜像源key获取对应的URL
|
||||
function getGitMirrorUrl(mirrorKey: string): string {
|
||||
const mirrors = {
|
||||
github: 'https://github.com/DLmaster361/AUTO_MAA.git',
|
||||
ghfast: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git'
|
||||
}
|
||||
return mirrors[mirrorKey as keyof typeof mirrors] || mirrors.github
|
||||
}
|
||||
|
||||
// 检查Git更新(简化版本,实际可以调用Git API)
|
||||
async function checkGitUpdate(): Promise<boolean> {
|
||||
// 这里可以实现更复杂的Git更新检查逻辑
|
||||
// 暂时返回false,表示没有更新
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查管理员权限
|
||||
async function checkAdminPermission() {
|
||||
try {
|
||||
const adminStatus = await window.electronAPI.checkAdmin()
|
||||
isAdmin.value = adminStatus
|
||||
console.log('管理员权限检查结果:', adminStatus)
|
||||
} catch (error) {
|
||||
logger.error('检查管理员权限失败', error)
|
||||
isAdmin.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听下载进度
|
||||
function handleDownloadProgress(progress: DownloadProgress) {
|
||||
// 更新全局进度条
|
||||
globalProgress.value = progress.progress
|
||||
progressText.value = progress.message
|
||||
|
||||
if (progress.status === 'error') {
|
||||
globalProgressStatus.value = 'exception'
|
||||
} else if (progress.status === 'completed') {
|
||||
globalProgressStatus.value = 'success'
|
||||
} else {
|
||||
globalProgressStatus.value = 'normal'
|
||||
}
|
||||
|
||||
// 更新自动模式进度
|
||||
if (autoMode.value) {
|
||||
autoProgress.value = progress.progress
|
||||
autoProgressText.value = progress.message
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('初始化页面 onMounted 开始')
|
||||
|
||||
// 测试配置系统
|
||||
try {
|
||||
console.log('测试配置系统...')
|
||||
const testConfig = await getConfig()
|
||||
console.log('当前配置:', testConfig)
|
||||
|
||||
// 测试保存配置
|
||||
await saveConfig({ isFirstLaunch: false })
|
||||
console.log('测试配置保存成功')
|
||||
|
||||
// 重新读取配置验证
|
||||
const updatedConfig = await getConfig()
|
||||
console.log('更新后的配置:', updatedConfig)
|
||||
} catch (error) {
|
||||
console.error('配置系统测试失败:', error)
|
||||
}
|
||||
|
||||
// 检查管理员权限
|
||||
await checkAdminPermission()
|
||||
|
||||
if (isAdmin.value) {
|
||||
// 延迟检查环境,确保页面完全加载
|
||||
setTimeout(async () => {
|
||||
console.log('开始环境检查')
|
||||
await checkEnvironment()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
window.electronAPI.onDownloadProgress(handleDownloadProgress)
|
||||
console.log('初始化页面 onMounted 完成')
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.electronAPI.removeDownloadProgressListener()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.initialization-container {
|
||||
min-height: 100vh;
|
||||
background: var(--ant-color-bg-layout);
|
||||
padding: 50px 100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.admin-check {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.auto-mode {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.auto-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin: 40px 0;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 16px;
|
||||
color: var(--ant-color-text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auto-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.manual-mode .header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 16px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.init-steps {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
min-height: 300px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.step-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.global-progress {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-container);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.global-progress .progress-text {
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.initialization-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.step-actions {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user