style:格式化代码
This commit is contained in:
@@ -57,7 +57,13 @@
|
||||
</a-layout-sider>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<a-layout :style="{ marginLeft: collapsed ? '60px' : '180px', transition: 'margin-left 0.2s', height: '100vh' }">
|
||||
<a-layout
|
||||
:style="{
|
||||
marginLeft: collapsed ? '60px' : '180px',
|
||||
transition: 'margin-left 0.2s',
|
||||
height: '100vh',
|
||||
}"
|
||||
>
|
||||
<a-layout-content
|
||||
class="content-area"
|
||||
:style="{
|
||||
@@ -103,9 +109,7 @@ const mainMenuItems = [
|
||||
{ path: '/history', label: '历史记录', icon: HistoryOutlined },
|
||||
]
|
||||
|
||||
const bottomMenuItems = [
|
||||
{ path: '/settings', label: '设置', icon: SettingOutlined },
|
||||
]
|
||||
const bottomMenuItems = [{ path: '/settings', label: '设置', icon: SettingOutlined }]
|
||||
|
||||
// 自动同步选中项
|
||||
const selectedKeys = computed(() => {
|
||||
@@ -142,9 +146,11 @@ const toggleCollapse = () => {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
:deep(.ant-layout-sider-light) .logo:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
@@ -162,6 +168,7 @@ const toggleCollapse = () => {
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.logo-text.text-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -172,16 +179,19 @@ const toggleCollapse = () => {
|
||||
overflow: auto;
|
||||
/* 修复滚动条显示问题 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0,0,0,0.2) transparent;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
|
||||
.main-menu-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.main-menu-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.main-menu-container::-webkit-scrollbar-thumb {
|
||||
background: rgba(0,0,0,0.2);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -190,6 +200,7 @@ const toggleCollapse = () => {
|
||||
margin-top: auto;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
:deep(.ant-layout-sider-light .bottom-menu) {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
@@ -207,6 +218,7 @@ const toggleCollapse = () => {
|
||||
:deep(.ant-layout-sider-dark) .menu-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:deep(.ant-layout-sider-light) .logo-text,
|
||||
:deep(.ant-layout-sider-light) .menu-text {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
@@ -244,6 +256,7 @@ const toggleCollapse = () => {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.content-area::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@@ -279,4 +292,4 @@ const toggleCollapse = () => {
|
||||
.app-layout-collapsed .ant-menu-inline-collapsed .bottom-menu {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
<div class="log-viewer">
|
||||
<div class="log-header">
|
||||
<div class="log-controls">
|
||||
<a-select
|
||||
v-model:value="selectedLevel"
|
||||
style="width: 120px"
|
||||
@change="filterLogs"
|
||||
>
|
||||
<a-select v-model:value="selectedLevel" style="width: 120px" @change="filterLogs">
|
||||
<a-select-option value="all">所有级别</a-select-option>
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</a-select-option>
|
||||
<a-select-option value="warn">Warn</a-select-option>
|
||||
<a-select-option value="error">Error</a-select-option>
|
||||
</a-select>
|
||||
|
||||
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索日志..."
|
||||
@@ -21,21 +17,21 @@
|
||||
@search="filterLogs"
|
||||
@change="filterLogs"
|
||||
/>
|
||||
|
||||
|
||||
<a-button @click="clearLogs" danger>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
清空日志
|
||||
</a-button>
|
||||
|
||||
|
||||
<a-button @click="downloadLogs" type="primary">
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
导出日志
|
||||
</a-button>
|
||||
|
||||
|
||||
<a-button @click="toggleAutoScroll" :type="autoScroll ? 'primary' : 'default'">
|
||||
<template #icon>
|
||||
<VerticalAlignBottomOutlined />
|
||||
@@ -43,19 +39,13 @@
|
||||
自动滚动
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="log-stats">
|
||||
总计: {{ filteredLogs.length }} 条日志
|
||||
</div>
|
||||
|
||||
<div class="log-stats">总计: {{ filteredLogs.length }} 条日志</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="logContainer"
|
||||
class="log-container"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div
|
||||
v-for="(log, index) in filteredLogs"
|
||||
|
||||
<div ref="logContainer" class="log-container" @scroll="handleScroll">
|
||||
<div
|
||||
v-for="(log, index) in filteredLogs"
|
||||
:key="index"
|
||||
class="log-entry"
|
||||
:class="[`log-${log.level}`, { 'log-highlight': highlightedIndex === index }]"
|
||||
@@ -65,14 +55,12 @@
|
||||
<div v-if="log.component" class="log-component">[{{ log.component }}]</div>
|
||||
<div class="log-message">{{ log.message }}</div>
|
||||
<div v-if="log.data" class="log-data">
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="toggleDataVisibility(index)"
|
||||
>
|
||||
<a-button size="small" type="link" @click="toggleDataVisibility(index)">
|
||||
{{ expandedData.has(index) ? '隐藏数据' : '显示数据' }}
|
||||
</a-button>
|
||||
<pre v-if="expandedData.has(index)" class="log-data-content">{{ JSON.stringify(log.data, null, 2) }}</pre>
|
||||
<pre v-if="expandedData.has(index)" class="log-data-content">{{
|
||||
JSON.stringify(log.data, null, 2)
|
||||
}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,10 +69,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import {
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
VerticalAlignBottomOutlined
|
||||
import {
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
VerticalAlignBottomOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
|
||||
|
||||
@@ -108,10 +96,11 @@ const filteredLogs = computed(() => {
|
||||
// 按搜索文本过滤
|
||||
if (searchText.value) {
|
||||
const search = searchText.value.toLowerCase()
|
||||
filtered = filtered.filter(log =>
|
||||
log.message.toLowerCase().includes(search) ||
|
||||
log.component?.toLowerCase().includes(search) ||
|
||||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
|
||||
filtered = filtered.filter(
|
||||
log =>
|
||||
log.message.toLowerCase().includes(search) ||
|
||||
log.component?.toLowerCase().includes(search) ||
|
||||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,10 +148,10 @@ function scrollToBottom() {
|
||||
|
||||
function handleScroll() {
|
||||
if (!logContainer.value) return
|
||||
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = logContainer.value
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
|
||||
|
||||
|
||||
if (!isAtBottom) {
|
||||
autoScroll.value = false
|
||||
}
|
||||
@@ -176,11 +165,12 @@ onMounted(() => {
|
||||
nextTick(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
|
||||
// 监听日志变化
|
||||
unwatchLogs = logs.value && typeof logs.value === 'object' && 'length' in logs.value
|
||||
? () => {} // 如果logs是响应式的,Vue会自动处理
|
||||
: null
|
||||
unwatchLogs =
|
||||
logs.value && typeof logs.value === 'object' && 'length' in logs.value
|
||||
? () => {} // 如果logs是响应式的,Vue会自动处理
|
||||
: null
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -334,17 +324,17 @@ onUnmounted(() => {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
|
||||
.log-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.log-entry {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
|
||||
.log-timestamp,
|
||||
.log-level,
|
||||
.log-component {
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
row-key="id"
|
||||
class="user-table"
|
||||
>
|
||||
|
||||
<template #bodyCell="{ column, record: user }">
|
||||
<template v-if="column.key === 'server'">
|
||||
<div class="server-cell">
|
||||
@@ -102,7 +101,10 @@
|
||||
|
||||
<template v-if="column.key === 'lastRun'">
|
||||
<div class="last-run-cell">
|
||||
<div v-if="!user.Data.LastAnnihilationDate && !user.Data.LastProxyDate" class="no-run-text">
|
||||
<div
|
||||
v-if="!user.Data.LastAnnihilationDate && !user.Data.LastProxyDate"
|
||||
class="no-run-text"
|
||||
>
|
||||
尚未运行
|
||||
</div>
|
||||
<template v-else>
|
||||
@@ -118,7 +120,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-if="column.key === 'userAction'">
|
||||
<a-space size="small">
|
||||
<a-tooltip title="编辑用户配置">
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
sub-title="为了正常安装和配置环境,请以管理员权限运行此应用"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleRestartAsAdmin">
|
||||
重新以管理员权限启动
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleRestartAsAdmin"> 重新以管理员权限启动 </a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
@@ -35,4 +33,4 @@ async function handleRestartAsAdmin() {
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -78,7 +78,7 @@ function handleForceEnterConfirm() {
|
||||
|
||||
// 事件处理
|
||||
function handleSwitchToManual() {
|
||||
aborted.value = true // 设置中断
|
||||
aborted.value = true // 设置中断
|
||||
props.onSwitchToManual()
|
||||
}
|
||||
|
||||
@@ -117,19 +117,19 @@ async function startAutoProcess() {
|
||||
let pipMirror = config.selectedPipMirror || 'tsinghua'
|
||||
let pipResult = await window.electronAPI.installDependencies(pipMirror)
|
||||
if (aborted.value) return
|
||||
|
||||
|
||||
// 如果初始化时的镜像源不通,让用户重新选择
|
||||
if (!pipResult.success) {
|
||||
logger.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
|
||||
|
||||
|
||||
// 切换到手动模式让用户重新选择镜像源
|
||||
progressText.value = '依赖安装失败,需要重新配置镜像源'
|
||||
progressStatus.value = 'exception'
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
progressText.value = '请点击下方按钮重新配置环境'
|
||||
}, 2000)
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<h3>获取后端源码</h3>
|
||||
<div class="install-section">
|
||||
<p>{{ backendExists ? '更新最新的后端代码' : '获取后端源代码' }}</p>
|
||||
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div
|
||||
v-for="mirror in gitMirrors"
|
||||
<div
|
||||
v-for="mirror in gitMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card"
|
||||
:class="{ active: selectedGitMirror === mirror.key }"
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testGitMirrorSpeed" :loading="testingGitSpeed" type="primary">
|
||||
{{ testingGitSpeed ? '测速中...' : '开始测速' }}
|
||||
@@ -54,7 +54,6 @@ import { GIT_MIRRORS } from '@/config/mirrors'
|
||||
|
||||
const gitMirrors = ref<Mirror[]>(GIT_MIRRORS)
|
||||
|
||||
|
||||
const selectedGitMirror = ref('github')
|
||||
const testingGitSpeed = ref(false)
|
||||
|
||||
@@ -81,17 +80,17 @@ async function saveMirrorConfig() {
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url.replace('.git', ''), {
|
||||
method: 'HEAD',
|
||||
|
||||
await fetch(url.replace('.git', ''), {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
@@ -102,14 +101,14 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
|
||||
async function testGitMirrorSpeed() {
|
||||
testingGitSpeed.value = true
|
||||
try {
|
||||
const promises = gitMirrors.value.map(async (mirror) => {
|
||||
const promises = gitMirrors.value.map(async mirror => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
|
||||
await Promise.all(promises)
|
||||
gitMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
|
||||
const fastest = gitMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedGitMirror.value = fastest.key
|
||||
@@ -131,14 +130,14 @@ function getSpeedClass(speed: number | null) {
|
||||
defineExpose({
|
||||
selectedGitMirror,
|
||||
testGitMirrorSpeed,
|
||||
gitMirrors
|
||||
gitMirrors,
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
|
||||
console.log('BackendStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testGitMirrorSpeed()
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<h3>安装 Python 依赖包</h3>
|
||||
<div class="install-section">
|
||||
<p>通过 pip 安装项目所需的 Python 依赖包</p>
|
||||
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div
|
||||
v-for="mirror in pipMirrors"
|
||||
<div
|
||||
v-for="mirror in pipMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card"
|
||||
:class="{ active: selectedPipMirror === mirror.key }"
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testPipMirrorSpeed" :loading="testingPipSpeed" type="primary">
|
||||
{{ testingPipSpeed ? '测速中...' : '重新测速' }}
|
||||
@@ -76,17 +76,17 @@ async function saveMirrorConfig() {
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
@@ -97,14 +97,14 @@ async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<numbe
|
||||
async function testPipMirrorSpeed() {
|
||||
testingPipSpeed.value = true
|
||||
try {
|
||||
const promises = pipMirrors.value.map(async (mirror) => {
|
||||
const promises = pipMirrors.value.map(async mirror => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
|
||||
await Promise.all(promises)
|
||||
pipMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
|
||||
const fastest = pipMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedPipMirror.value = fastest.key
|
||||
@@ -125,14 +125,14 @@ function getSpeedClass(speed: number | null) {
|
||||
|
||||
defineExpose({
|
||||
selectedPipMirror,
|
||||
testPipMirrorSpeed
|
||||
testPipMirrorSpeed,
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
|
||||
console.log('DependenciesStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testPipMirrorSpeed()
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
<div v-if="!gitInstalled" class="install-section">
|
||||
<p>需要安装 Git 工具来获取源代码</p>
|
||||
<div class="git-info">
|
||||
<a-alert
|
||||
message="Git 工具信息"
|
||||
<a-alert
|
||||
message="Git 工具信息"
|
||||
description="将安装便携版 Git 工具,包含完整的版本控制功能,无需系统安装。"
|
||||
type="info"
|
||||
show-icon
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="Git已成功安装,无需继续安装" />
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -43,13 +43,13 @@ async function handleForceReinstall() {
|
||||
if (!deleteResult.success) {
|
||||
throw new Error(`删除Git失败: ${deleteResult.error}`)
|
||||
}
|
||||
|
||||
|
||||
// 重新安装Git
|
||||
const installResult = await window.electronAPI.downloadGit()
|
||||
if (!installResult.success) {
|
||||
throw new Error(`重新安装Git失败: ${installResult.error}`)
|
||||
}
|
||||
|
||||
|
||||
console.log('Git强制重新安装成功')
|
||||
// 通知父组件更新状态
|
||||
window.location.reload() // 简单的页面刷新来更新状态
|
||||
@@ -62,7 +62,7 @@ async function handleForceReinstall() {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleForceReinstall
|
||||
handleForceReinstall,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
<div class="header">
|
||||
<h1>AUTO_MAA 初始化向导</h1>
|
||||
<p>欢迎使用 AUTO_MAA,让我们来配置您的运行环境</p>
|
||||
|
||||
|
||||
<div class="header-actions">
|
||||
<a-button size="large" type="primary" @click="handleSkipToHome">
|
||||
跳转至首页(仅开发用)
|
||||
</a-button>
|
||||
<a-button size="large" type="default" @click="handleJumpToStep(6)" style="margin-left: 16px;">
|
||||
<a-button
|
||||
size="large"
|
||||
type="default"
|
||||
@click="handleJumpToStep(6)"
|
||||
style="margin-left: 16px"
|
||||
>
|
||||
跳到启动服务(第七步)
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-steps
|
||||
:current="currentStep"
|
||||
:status="stepStatus"
|
||||
class="init-steps"
|
||||
>
|
||||
<a-steps :current="currentStep" :status="stepStatus" class="init-steps">
|
||||
<a-step title="主题设置" description="选择您喜欢的主题" />
|
||||
<a-step title="Python 环境" description="安装 Python 运行环境" />
|
||||
<a-step title="pip 安装" description="安装 Python 包管理器" />
|
||||
@@ -28,22 +29,26 @@
|
||||
<a-step title="启动服务" description="启动后端服务" />
|
||||
</a-steps>
|
||||
|
||||
<!-- <!– 全局进度条 –>-->
|
||||
<!-- <div v-if="isProcessing" class="global-progress">-->
|
||||
<!-- <a-progress -->
|
||||
<!-- :percent="globalProgress" -->
|
||||
<!-- :status="globalProgressStatus"-->
|
||||
<!-- :show-info="true"-->
|
||||
<!-- />-->
|
||||
<!-- <div class="progress-text">{{ progressText }}</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <!– 全局进度条 –>-->
|
||||
<!-- <div v-if="isProcessing" class="global-progress">-->
|
||||
<!-- <a-progress -->
|
||||
<!-- :percent="globalProgress" -->
|
||||
<!-- :status="globalProgressStatus"-->
|
||||
<!-- :show-info="true"-->
|
||||
<!-- />-->
|
||||
<!-- <div class="progress-text">{{ progressText }}</div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="step-content">
|
||||
<!-- 步骤 0: 主题设置 -->
|
||||
<ThemeStep v-if="currentStep === 0" ref="themeStepRef" />
|
||||
|
||||
<!-- 步骤 1: Python 环境 -->
|
||||
<PythonStep v-if="currentStep === 1" :python-installed="pythonInstalled" ref="pythonStepRef" />
|
||||
<PythonStep
|
||||
v-if="currentStep === 1"
|
||||
:python-installed="pythonInstalled"
|
||||
ref="pythonStepRef"
|
||||
/>
|
||||
|
||||
<!-- 步骤 2: pip 安装 -->
|
||||
<PipStep v-if="currentStep === 2" :pip-installed="pipInstalled" ref="pipStepRef" />
|
||||
@@ -71,20 +76,19 @@
|
||||
上一步
|
||||
</a-button>
|
||||
|
||||
|
||||
<a-button
|
||||
<a-button
|
||||
v-if="currentStep < 6"
|
||||
size="large"
|
||||
type="primary"
|
||||
type="primary"
|
||||
@click="handleNextStep"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
{{ getNextButtonText() }}
|
||||
</a-button>
|
||||
|
||||
|
||||
<!-- 第7步重新启动服务按钮 -->
|
||||
<a-button
|
||||
v-if="currentStep === 6"
|
||||
<a-button
|
||||
v-if="currentStep === 6"
|
||||
type="default"
|
||||
size="large"
|
||||
@click="handleNextStep"
|
||||
@@ -94,15 +98,15 @@
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="errorMessage" class="error-message">-->
|
||||
<!-- <a-alert -->
|
||||
<!-- :message="errorMessage" -->
|
||||
<!-- type="error" -->
|
||||
<!-- show-icon -->
|
||||
<!-- closable-->
|
||||
<!-- @close="errorMessage = ''"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-if="errorMessage" class="error-message">-->
|
||||
<!-- <a-alert -->
|
||||
<!-- :message="errorMessage" -->
|
||||
<!-- type="error" -->
|
||||
<!-- show-icon -->
|
||||
<!-- closable-->
|
||||
<!-- @close="errorMessage = ''"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -130,7 +134,7 @@ interface Props {
|
||||
backendExists: boolean
|
||||
dependenciesInstalled: boolean
|
||||
serviceStarted: boolean
|
||||
|
||||
|
||||
// 事件处理函数
|
||||
onSkipToHome: () => void
|
||||
onEnterApp: () => void
|
||||
@@ -183,7 +187,7 @@ async function handleNextStep() {
|
||||
console.log('nextStep 被调用,当前步骤:', currentStep.value)
|
||||
isProcessing.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
|
||||
try {
|
||||
switch (currentStep.value) {
|
||||
case 0: // 主题设置
|
||||
@@ -227,7 +231,7 @@ async function handleNextStep() {
|
||||
await startBackendService()
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if (currentStep.value < 6) {
|
||||
currentStep.value++
|
||||
// 进入新步骤时自动开始测速
|
||||
@@ -244,14 +248,22 @@ async function handleNextStep() {
|
||||
|
||||
function getNextButtonText() {
|
||||
switch (currentStep.value) {
|
||||
case 0: return '下一步'
|
||||
case 1: return props.pythonInstalled ? '下一步' : '安装 Python'
|
||||
case 2: return props.pipInstalled ? '下一步' : '安装 pip'
|
||||
case 3: return props.gitInstalled ? '下一步' : '安装 Git'
|
||||
case 4: return props.backendExists ? '更新代码' : '获取代码'
|
||||
case 5: return '安装依赖'
|
||||
case 6: return '启动服务'
|
||||
default: return '下一步'
|
||||
case 0:
|
||||
return '下一步'
|
||||
case 1:
|
||||
return props.pythonInstalled ? '下一步' : '安装 Python'
|
||||
case 2:
|
||||
return props.pipInstalled ? '下一步' : '安装 pip'
|
||||
case 3:
|
||||
return props.gitInstalled ? '下一步' : '安装 Git'
|
||||
case 4:
|
||||
return props.backendExists ? '更新代码' : '获取代码'
|
||||
case 5:
|
||||
return '安装依赖'
|
||||
case 6:
|
||||
return '启动服务'
|
||||
default:
|
||||
return '下一步'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,13 +380,13 @@ async function autoStartBackendService() {
|
||||
logger.info('自动启动后端服务')
|
||||
isProcessing.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.startingService = true
|
||||
serviceStepRef.value.showServiceProgress = true
|
||||
serviceStepRef.value.serviceStatus = '正在自动启动后端服务...'
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.startBackend()
|
||||
if (result.success) {
|
||||
@@ -384,7 +396,7 @@ async function autoStartBackendService() {
|
||||
}
|
||||
stepStatus.value = 'finish'
|
||||
logger.info('后端服务自动启动成功,延迟1秒后自动进入主页')
|
||||
|
||||
|
||||
// 延迟1秒后自动进入主页
|
||||
setTimeout(() => {
|
||||
handleEnterApp()
|
||||
@@ -413,13 +425,13 @@ async function autoStartBackendService() {
|
||||
// 手动启动后端服务(用户点击按钮时调用)
|
||||
async function startBackendService() {
|
||||
logger.info('手动重新启动后端服务')
|
||||
|
||||
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.startingService = true
|
||||
serviceStepRef.value.showServiceProgress = true
|
||||
serviceStepRef.value.serviceStatus = '正在重新启动后端服务...'
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.startBackend()
|
||||
if (result.success) {
|
||||
@@ -429,7 +441,7 @@ async function startBackendService() {
|
||||
}
|
||||
stepStatus.value = 'finish'
|
||||
logger.info('后端服务手动启动成功,延迟1秒后自动进入主页')
|
||||
|
||||
|
||||
// 延迟1秒后自动进入主页
|
||||
setTimeout(() => {
|
||||
handleEnterApp()
|
||||
@@ -456,7 +468,7 @@ function handleDownloadProgress(progress: any) {
|
||||
// 更新全局进度条
|
||||
globalProgress.value = progress.progress
|
||||
progressText.value = progress.message
|
||||
|
||||
|
||||
if (progress.status === 'error') {
|
||||
globalProgressStatus.value = 'exception'
|
||||
} else if (progress.status === 'completed') {
|
||||
@@ -464,7 +476,7 @@ function handleDownloadProgress(progress: any) {
|
||||
} else {
|
||||
globalProgressStatus.value = 'normal'
|
||||
}
|
||||
|
||||
|
||||
// 通知父组件
|
||||
props.onProgressUpdate(progress)
|
||||
}
|
||||
@@ -472,11 +484,11 @@ function handleDownloadProgress(progress: any) {
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
currentStep,
|
||||
handleDownloadProgress
|
||||
handleDownloadProgress,
|
||||
})
|
||||
|
||||
// 监听 errorMessage,一旦有内容就弹窗
|
||||
watch(errorMessage, (val) => {
|
||||
watch(errorMessage, val => {
|
||||
if (val) {
|
||||
message.error(val)
|
||||
// 弹窗后可选:自动清空 errorMessage
|
||||
@@ -554,7 +566,7 @@ watch(errorMessage, (val) => {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
.step-actions {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
<h3>安装 pip 包管理器</h3>
|
||||
<div v-if="!pipInstalled" class="install-section">
|
||||
<p>pip 是 Python 的包管理工具,用于安装和管理 Python 包</p>
|
||||
|
||||
|
||||
<div class="pip-info">
|
||||
<a-alert
|
||||
message="pip 安装信息"
|
||||
<a-alert
|
||||
message="pip 安装信息"
|
||||
description="将自动下载并安装 pip 包管理器,这是安装 Python 依赖包的必要工具。"
|
||||
type="info"
|
||||
show-icon
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="pip已成功安装,无需继续安装" />
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -44,13 +44,13 @@ async function handleForceReinstall() {
|
||||
if (!deleteResult.success) {
|
||||
throw new Error(`删除pip失败: ${deleteResult.error}`)
|
||||
}
|
||||
|
||||
|
||||
// 重新安装pip
|
||||
const installResult = await window.electronAPI.installPip()
|
||||
if (!installResult.success) {
|
||||
throw new Error(`重新安装pip失败: ${installResult.error}`)
|
||||
}
|
||||
|
||||
|
||||
console.log('pip强制重新安装成功')
|
||||
// 通知父组件更新状态
|
||||
window.location.reload() // 简单的页面刷新来更新状态
|
||||
@@ -63,7 +63,7 @@ async function handleForceReinstall() {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleForceReinstall
|
||||
handleForceReinstall,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
<template>
|
||||
<div class="step-panel">
|
||||
<h3>Python 运行环境</h3>
|
||||
<div v-if="!pythonInstalled" class="install-section">
|
||||
<p>需要安装 Python 3.13.0 运行环境(64位嵌入式版本)</p>
|
||||
<div class="step-panel">
|
||||
<h3>Python 运行环境</h3>
|
||||
<div v-if="!pythonInstalled" class="install-section">
|
||||
<p>需要安装 Python 3.13.0 运行环境(64位嵌入式版本)</p>
|
||||
|
||||
<div class="mirror-grid">
|
||||
<div v-for="mirror in pythonMirrors" :key="mirror.key" class="mirror-card"
|
||||
:class="{ active: selectedPythonMirror === mirror.key }" @click="selectedPythonMirror = mirror.key">
|
||||
<div class="mirror-header">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
|
||||
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
|
||||
<span v-else-if="testingSpeed">测试中...</span>
|
||||
<span v-else-if="mirror.speed === 9999">超时</span>
|
||||
<span v-else>{{ mirror.speed }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
<div class="mirror-grid">
|
||||
<div
|
||||
v-for="mirror in pythonMirrors"
|
||||
:key="mirror.key"
|
||||
class="mirror-card"
|
||||
:class="{ active: selectedPythonMirror === mirror.key }"
|
||||
@click="selectedPythonMirror = mirror.key"
|
||||
>
|
||||
<div class="mirror-header">
|
||||
<h4>{{ mirror.name }}</h4>
|
||||
<div class="speed-badge" :class="getSpeedClass(mirror.speed)">
|
||||
<span v-if="mirror.speed === null && !testingSpeed">未测试</span>
|
||||
<span v-else-if="testingSpeed">测试中...</span>
|
||||
<span v-else-if="mirror.speed === 9999">超时</span>
|
||||
<span v-else>{{ mirror.speed }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mirror-url">{{ mirror.url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-actions">
|
||||
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
|
||||
{{ testingSpeed ? '测速中...' : '重新测速' }}
|
||||
</a-button>
|
||||
<span class="test-note">3秒无响应视为超时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="Python已成功安装,无需继续安装" />
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<a-button @click="testPythonMirrorSpeed" :loading="testingSpeed" type="primary">
|
||||
{{ testingSpeed ? '测速中...' : '重新测速' }}
|
||||
</a-button>
|
||||
<span class="test-note">3秒无响应视为超时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="already-installed">
|
||||
<a-result status="success" title="Python已成功安装,无需继续安装" />
|
||||
<!-- <div class="reinstall-section">-->
|
||||
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -44,14 +49,14 @@ import { ref, onMounted } from 'vue'
|
||||
import { getConfig, saveConfig } from '@/utils/config'
|
||||
|
||||
interface Mirror {
|
||||
key: string
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
key: string
|
||||
name: string
|
||||
url: string
|
||||
speed: number | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
pythonInstalled: boolean
|
||||
pythonInstalled: boolean
|
||||
}>()
|
||||
|
||||
import { PYTHON_MIRRORS } from '@/config/mirrors'
|
||||
@@ -84,239 +89,239 @@ async function saveMirrorConfig() {
|
||||
}
|
||||
|
||||
async function testMirrorWithTimeout(url: string, timeout = 3000): Promise<number> {
|
||||
const startTime = Date.now()
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
return 9999 // 超时或失败
|
||||
}
|
||||
clearTimeout(timeoutId)
|
||||
return Date.now() - startTime
|
||||
} catch (error) {
|
||||
return 9999 // 超时或失败
|
||||
}
|
||||
}
|
||||
|
||||
async function testPythonMirrorSpeed() {
|
||||
testingSpeed.value = true
|
||||
try {
|
||||
const promises = pythonMirrors.value.map(async (mirror) => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
testingSpeed.value = true
|
||||
try {
|
||||
const promises = pythonMirrors.value.map(async mirror => {
|
||||
mirror.speed = await testMirrorWithTimeout(mirror.url)
|
||||
return mirror
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
await Promise.all(promises)
|
||||
|
||||
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
pythonMirrors.value.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||
|
||||
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedPythonMirror.value = fastest.key
|
||||
await saveMirrorConfig() // 保存最快的镜像源选择
|
||||
}
|
||||
} finally {
|
||||
testingSpeed.value = false
|
||||
const fastest = pythonMirrors.value.find(m => m.speed !== 9999)
|
||||
if (fastest) {
|
||||
selectedPythonMirror.value = fastest.key
|
||||
await saveMirrorConfig() // 保存最快的镜像源选择
|
||||
}
|
||||
} finally {
|
||||
testingSpeed.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getSpeedClass(speed: number | null) {
|
||||
if (speed === null) return 'speed-unknown'
|
||||
if (speed === 9999) return 'speed-timeout'
|
||||
if (speed < 500) return 'speed-fast'
|
||||
if (speed < 1500) return 'speed-medium'
|
||||
return 'speed-slow'
|
||||
if (speed === null) return 'speed-unknown'
|
||||
if (speed === 9999) return 'speed-timeout'
|
||||
if (speed < 500) return 'speed-fast'
|
||||
if (speed < 1500) return 'speed-medium'
|
||||
return 'speed-slow'
|
||||
}
|
||||
|
||||
// 强制重新安装Python
|
||||
async function handleForceReinstall() {
|
||||
reinstalling.value = true
|
||||
try {
|
||||
console.log('开始强制重新安装Python')
|
||||
// 先删除现有Python目录
|
||||
const deleteResult = await window.electronAPI.deletePython()
|
||||
if (!deleteResult.success) {
|
||||
throw new Error(`删除Python目录失败: ${deleteResult.error}`)
|
||||
}
|
||||
|
||||
// 重新下载安装Python
|
||||
const installResult = await window.electronAPI.downloadPython(selectedPythonMirror.value)
|
||||
if (!installResult.success) {
|
||||
throw new Error(`重新安装Python失败: ${installResult.error}`)
|
||||
}
|
||||
|
||||
console.log('Python强制重新安装成功')
|
||||
// 通知父组件更新状态
|
||||
window.location.reload() // 简单的页面刷新来更新状态
|
||||
} catch (error) {
|
||||
console.error('Python强制重新安装失败:', error)
|
||||
// 这里可以添加错误提示
|
||||
} finally {
|
||||
reinstalling.value = false
|
||||
reinstalling.value = true
|
||||
try {
|
||||
console.log('开始强制重新安装Python')
|
||||
// 先删除现有Python目录
|
||||
const deleteResult = await window.electronAPI.deletePython()
|
||||
if (!deleteResult.success) {
|
||||
throw new Error(`删除Python目录失败: ${deleteResult.error}`)
|
||||
}
|
||||
|
||||
// 重新下载安装Python
|
||||
const installResult = await window.electronAPI.downloadPython(selectedPythonMirror.value)
|
||||
if (!installResult.success) {
|
||||
throw new Error(`重新安装Python失败: ${installResult.error}`)
|
||||
}
|
||||
|
||||
console.log('Python强制重新安装成功')
|
||||
// 通知父组件更新状态
|
||||
window.location.reload() // 简单的页面刷新来更新状态
|
||||
} catch (error) {
|
||||
console.error('Python强制重新安装失败:', error)
|
||||
// 这里可以添加错误提示
|
||||
} finally {
|
||||
reinstalling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
selectedPythonMirror,
|
||||
testPythonMirrorSpeed,
|
||||
handleForceReinstall
|
||||
selectedPythonMirror,
|
||||
testPythonMirrorSpeed,
|
||||
handleForceReinstall,
|
||||
})
|
||||
|
||||
// 组件挂载时加载配置并自动开始测速
|
||||
onMounted(async () => {
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
if (!props.pythonInstalled) {
|
||||
console.log('PythonStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testPythonMirrorSpeed()
|
||||
}, 200) // 延迟200ms确保组件完全渲染
|
||||
}
|
||||
// 先加载配置
|
||||
await loadMirrorConfig()
|
||||
|
||||
if (!props.pythonInstalled) {
|
||||
console.log('PythonStep 组件挂载,自动开始测速')
|
||||
setTimeout(() => {
|
||||
testPythonMirrorSpeed()
|
||||
}, 200) // 延迟200ms确保组件完全渲染
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-panel {
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
padding: 20px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-panel h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.install-section p {
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
color: var(--ant-color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mirror-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-card {
|
||||
padding: 16px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--ant-color-bg-container);
|
||||
padding: 16px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.mirror-card:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mirror-card.active {
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
}
|
||||
|
||||
.mirror-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mirror-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.speed-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speed-badge.speed-unknown {
|
||||
background: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-tertiary);
|
||||
background: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.speed-badge.speed-fast {
|
||||
background: var(--ant-color-success-bg);
|
||||
color: var(--ant-color-success);
|
||||
background: var(--ant-color-success-bg);
|
||||
color: var(--ant-color-success);
|
||||
}
|
||||
|
||||
.speed-badge.speed-medium {
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
.speed-badge.speed-slow {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.speed-badge.speed-timeout {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.mirror-url {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
word-break: break-all;
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.test-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-note {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
|
||||
.already-installed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
gap: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.reinstall-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.reinstall-note {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -24,7 +24,7 @@ defineExpose({
|
||||
startingService,
|
||||
showServiceProgress,
|
||||
serviceProgress,
|
||||
serviceStatus
|
||||
serviceStatus,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<div class="setting-group">
|
||||
<label>主题色彩</label>
|
||||
<div class="color-picker">
|
||||
<div
|
||||
v-for="(color, key) in themeColors"
|
||||
<div
|
||||
v-for="(color, key) in themeColors"
|
||||
:key="key"
|
||||
class="color-option"
|
||||
:class="{ active: selectedThemeColor === key }"
|
||||
@@ -51,9 +51,9 @@ async function onThemeColorChange(color: ThemeColor) {
|
||||
|
||||
async function saveSettings() {
|
||||
await saveThemeConfig(selectedThemeMode.value, selectedThemeColor.value)
|
||||
console.log('主题设置已保存:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value
|
||||
console.log('主题设置已保存:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ async function loadSettings() {
|
||||
selectedThemeColor.value = config.themeColor
|
||||
setThemeMode(selectedThemeMode.value)
|
||||
setThemeColor(selectedThemeColor.value)
|
||||
console.log('主题设置已加载:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value
|
||||
console.log('主题设置已加载:', {
|
||||
themeMode: selectedThemeMode.value,
|
||||
themeColor: selectedThemeColor.value,
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('Failed to load theme settings:', error)
|
||||
@@ -78,7 +78,7 @@ defineExpose({
|
||||
loadSettings,
|
||||
saveSettings,
|
||||
selectedThemeMode,
|
||||
selectedThemeColor
|
||||
selectedThemeColor,
|
||||
})
|
||||
|
||||
// 组件挂载时加载设置
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
:scroll="{ x: 600 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">
|
||||
第{{ index + 1 }}个脚本
|
||||
</template>
|
||||
<template v-if="column.key === 'index'"> 第{{ index + 1 }}个脚本 </template>
|
||||
<template v-else-if="column.key === 'script'">
|
||||
{{ getScriptName(record.script) }}
|
||||
</template>
|
||||
@@ -31,7 +29,12 @@
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要删除这个队列项吗?" @confirm="deleteQueueItem(record.id)" ok-text="确定" cancel-text="取消">
|
||||
<a-popconfirm
|
||||
title="确定要删除这个队列项吗?"
|
||||
@confirm="deleteQueueItem(record.id)"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<a-button size="small" danger>
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
@@ -47,11 +50,22 @@
|
||||
</div>
|
||||
|
||||
<!-- 队列项编辑弹窗 -->
|
||||
<a-modal v-model:open="modalVisible" :title="editingQueueItem ? '编辑队列项' : '添加队列项'" @ok="saveQueueItem"
|
||||
@cancel="cancelEdit" :confirm-loading="saving" width="600px">
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="editingQueueItem ? '编辑队列项' : '添加队列项'"
|
||||
@ok="saveQueueItem"
|
||||
@cancel="cancelEdit"
|
||||
:confirm-loading="saving"
|
||||
width="600px"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<a-form-item label="关联脚本" name="script">
|
||||
<a-select v-model:value="form.script" placeholder="请选择关联脚本" allow-clear :options="scriptOptions" />
|
||||
<a-select
|
||||
v-model:value="form.script"
|
||||
placeholder="请选择关联脚本"
|
||||
allow-clear
|
||||
:options="scriptOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -66,7 +80,7 @@ import {
|
||||
ReloadOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
MoreOutlined
|
||||
MoreOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { Service } from '@/api'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
@@ -103,12 +117,12 @@ const getScriptName = (scriptId: string) => {
|
||||
// 表单引用和数据
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
script: ''
|
||||
script: '',
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
script: [{ required: true, message: '请选择关联脚本', trigger: 'change' }]
|
||||
script: [{ required: true, message: '请选择关联脚本', trigger: 'change' }],
|
||||
}
|
||||
|
||||
// 表格列配置
|
||||
@@ -135,10 +149,13 @@ const queueColumns = [
|
||||
const queueItems = ref(props.queueItems)
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.queueItems, (newQueueItems) => {
|
||||
queueItems.value = newQueueItems
|
||||
}, { deep: true })
|
||||
|
||||
watch(
|
||||
() => props.queueItems,
|
||||
newQueueItems => {
|
||||
queueItems.value = newQueueItems
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 加载脚本选项
|
||||
const loadOptions = async () => {
|
||||
@@ -161,12 +178,11 @@ const loadOptions = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 添加队列项
|
||||
const addQueueItem = async () => {
|
||||
editingQueueItem.value = null
|
||||
Object.assign(form, {
|
||||
script: ''
|
||||
script: '',
|
||||
})
|
||||
|
||||
// 确保在打开弹窗时加载脚本选项
|
||||
@@ -178,7 +194,7 @@ const addQueueItem = async () => {
|
||||
const editQueueItem = async (item: any) => {
|
||||
editingQueueItem.value = item
|
||||
Object.assign(form, {
|
||||
script: item.script || ''
|
||||
script: item.script || '',
|
||||
})
|
||||
|
||||
// 确保在打开弹窗时加载脚本选项
|
||||
@@ -199,9 +215,9 @@ const saveQueueItem = async () => {
|
||||
queueItemId: editingQueueItem.value.id,
|
||||
data: {
|
||||
Info: {
|
||||
ScriptId: form.script
|
||||
}
|
||||
}
|
||||
ScriptId: form.script,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
@@ -214,7 +230,7 @@ const saveQueueItem = async () => {
|
||||
// 添加队列项 - 先创建,再更新
|
||||
// 1. 先创建队列项,只传queueId
|
||||
const createResponse = await Service.addItemApiQueueItemAddPost({
|
||||
queueId: props.queueId
|
||||
queueId: props.queueId,
|
||||
})
|
||||
|
||||
// 2. 用返回的queueItemId更新队列项数据
|
||||
@@ -224,9 +240,9 @@ const saveQueueItem = async () => {
|
||||
queueItemId: createResponse.queueItemId,
|
||||
data: {
|
||||
Info: {
|
||||
ScriptId: form.script
|
||||
}
|
||||
}
|
||||
ScriptId: form.script,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (updateResponse.code === 200) {
|
||||
@@ -262,7 +278,7 @@ const deleteQueueItem = async (itemId: string) => {
|
||||
try {
|
||||
const response = await Service.deleteItemApiQueueItemDeletePost({
|
||||
queueId: props.queueId,
|
||||
queueItemId: itemId
|
||||
queueItemId: itemId,
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
|
||||
Reference in New Issue
Block a user