feat(initialization): 新增配置文件操作和管理员权限检查

- 新增配置文件保存、加载和重置功能
- 添加管理员权限检查和重启为管理员的功能
- 实现 pip 包管理器安装功能
- 优化初始化流程,自动检测并安装依赖
This commit is contained in:
2025-08-07 20:18:53 +08:00
parent e7f898f357
commit 0171c3ca4d
15 changed files with 2397 additions and 8 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>