mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
优化模块测试视图
This commit is contained in:
@@ -353,7 +353,7 @@ onMounted(() => {
|
|||||||
<VDialogCloseBtn @click="systemTestDialog = false" />
|
<VDialogCloseBtn @click="systemTestDialog = false" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText class="pa-0">
|
||||||
<ModuleTestView />
|
<ModuleTestView />
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
@@ -1048,6 +1048,11 @@ export default {
|
|||||||
normal: 'Normal',
|
normal: 'Normal',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
error: 'Error',
|
error: 'Error',
|
||||||
|
checking: 'Checking...',
|
||||||
|
complete: 'Check Complete',
|
||||||
|
preparing: 'Preparing...',
|
||||||
|
totalModules: 'Total Modules',
|
||||||
|
recheck: 'Recheck',
|
||||||
},
|
},
|
||||||
nameTest: {
|
nameTest: {
|
||||||
recognize: 'Recognize',
|
recognize: 'Recognize',
|
||||||
|
|||||||
@@ -1044,6 +1044,11 @@ export default {
|
|||||||
normal: '正常',
|
normal: '正常',
|
||||||
disabled: '未启用',
|
disabled: '未启用',
|
||||||
error: '错误',
|
error: '错误',
|
||||||
|
checking: '正在检查...',
|
||||||
|
complete: '检查完成',
|
||||||
|
preparing: '准备检查...',
|
||||||
|
totalModules: '总模块数',
|
||||||
|
recheck: '重新检查',
|
||||||
},
|
},
|
||||||
nameTest: {
|
nameTest: {
|
||||||
recognize: '识别',
|
recognize: '识别',
|
||||||
|
|||||||
@@ -1043,6 +1043,11 @@ export default {
|
|||||||
normal: '正常',
|
normal: '正常',
|
||||||
disabled: '未啟用',
|
disabled: '未啟用',
|
||||||
error: '錯誤',
|
error: '錯誤',
|
||||||
|
checking: '正在檢查...',
|
||||||
|
complete: '檢查完成',
|
||||||
|
preparing: '準備檢查...',
|
||||||
|
totalModules: '總模組數',
|
||||||
|
recheck: '重新檢查',
|
||||||
},
|
},
|
||||||
nameTest: {
|
nameTest: {
|
||||||
recognize: '識別',
|
recognize: '識別',
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 主题
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
// 定义所有的模块ID、名称列表
|
// 定义所有的模块ID、名称列表
|
||||||
const modules = ref<
|
const modules = ref<
|
||||||
{
|
{
|
||||||
@@ -13,36 +17,83 @@ const modules = ref<
|
|||||||
state: 'success' | 'error' | 'warning' | 'info' | undefined
|
state: 'success' | 'error' | 'warning' | 'info' | undefined
|
||||||
errmsg: string
|
errmsg: string
|
||||||
loading: boolean
|
loading: boolean
|
||||||
|
visible: boolean
|
||||||
|
delay: number
|
||||||
}[]
|
}[]
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
|
// 总体进度
|
||||||
|
const overallProgress = ref(0)
|
||||||
|
const isChecking = ref(false)
|
||||||
|
const checkComplete = ref(false)
|
||||||
|
|
||||||
// 调用API查询模块列表
|
// 调用API查询模块列表
|
||||||
async function getModules() {
|
async function getModules() {
|
||||||
try {
|
try {
|
||||||
|
isChecking.value = true
|
||||||
|
overallProgress.value = 0
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get('system/modulelist')
|
const result: { [key: string]: any } = await api.get('system/modulelist')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const moduleList = result.data?.modules
|
const moduleList = result.data?.modules
|
||||||
if (moduleList) {
|
if (moduleList) {
|
||||||
moduleList.forEach((module: { id: string; name: string }) => {
|
// 初始化模块列表
|
||||||
modules.value.push({ id: module.id, name: module.name, state: undefined, errmsg: '', loading: false })
|
modules.value = moduleList.map((module: { id: string; name: string }, index: number) => ({
|
||||||
})
|
id: module.id,
|
||||||
// 逐个检查所有模块
|
name: module.name,
|
||||||
for (let i = 0; i < modules.value.length; i++) await moduleTest(i)
|
state: undefined,
|
||||||
|
errmsg: '',
|
||||||
|
loading: false,
|
||||||
|
visible: false,
|
||||||
|
delay: index * 200, // 每个模块延迟200ms出现
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 开始检查
|
||||||
|
await startModuleCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
isChecking.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开始模块检查
|
||||||
|
async function startModuleCheck() {
|
||||||
|
const totalModules = modules.value.length
|
||||||
|
|
||||||
|
for (let i = 0; i < modules.value.length; i++) {
|
||||||
|
const module = modules.value[i]
|
||||||
|
|
||||||
|
// 显示当前模块
|
||||||
|
setTimeout(() => {
|
||||||
|
module.visible = true
|
||||||
|
}, module.delay)
|
||||||
|
|
||||||
|
// 开始检查
|
||||||
|
await moduleTest(i)
|
||||||
|
|
||||||
|
// 更新总体进度
|
||||||
|
overallProgress.value = ((i + 1) / totalModules) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查完成
|
||||||
|
setTimeout(() => {
|
||||||
|
isChecking.value = false
|
||||||
|
checkComplete.value = true
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
// 调用API测试模块
|
// 调用API测试模块
|
||||||
async function moduleTest(index: number) {
|
async function moduleTest(index: number) {
|
||||||
try {
|
try {
|
||||||
const target = modules.value[index]
|
const target = modules.value[index]
|
||||||
const moduleid = target.id
|
const moduleid = target.id
|
||||||
target.loading = true
|
target.loading = true
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
|
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
|
||||||
target.loading = false
|
target.loading = false
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
target.state = 'success'
|
target.state = 'success'
|
||||||
target.name = `${target.name} - ${t('moduleTest.normal')}`
|
target.name = `${target.name} - ${t('moduleTest.normal')}`
|
||||||
@@ -56,25 +107,325 @@ async function moduleTest(index: number) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
const target = modules.value[index]
|
||||||
|
target.loading = false
|
||||||
|
target.state = 'error'
|
||||||
|
target.errmsg = '网络请求失败'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新检查
|
||||||
|
function recheck() {
|
||||||
|
modules.value = []
|
||||||
|
overallProgress.value = 0
|
||||||
|
isChecking.value = false
|
||||||
|
checkComplete.value = false
|
||||||
|
getModules()
|
||||||
|
}
|
||||||
|
|
||||||
// 加载
|
// 加载
|
||||||
onMounted(getModules)
|
onMounted(getModules)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VAlert
|
<div class="system-health-check">
|
||||||
v-for="(module, index) in modules"
|
<!-- 动态进度框 - 固定在顶部 -->
|
||||||
:key="index"
|
<div class="progress-container">
|
||||||
:type="module.state"
|
<div class="progress-card" :class="{ 'dark-theme': theme.global.current.value.dark }">
|
||||||
:title="module.name"
|
<div class="progress-header">
|
||||||
class="mb-2"
|
<VIcon
|
||||||
variant="tonal"
|
:icon="isChecking ? 'mdi-cog-sync' : checkComplete ? 'mdi-check-circle' : 'mdi-cog'"
|
||||||
>
|
:class="isChecking ? 'rotating' : ''"
|
||||||
{{ module.errmsg }}
|
size="28"
|
||||||
<template #append>
|
color="white"
|
||||||
<VProgressCircular v-if="module.loading" indeterminate />
|
/>
|
||||||
</template>
|
<h3 class="progress-title text-white">
|
||||||
</VAlert>
|
{{
|
||||||
|
isChecking
|
||||||
|
? t('moduleTest.checking')
|
||||||
|
: checkComplete
|
||||||
|
? t('moduleTest.complete')
|
||||||
|
: t('moduleTest.preparing')
|
||||||
|
}}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<VProgressLinear
|
||||||
|
v-model="overallProgress"
|
||||||
|
:color="checkComplete ? 'success' : 'white'"
|
||||||
|
height="6"
|
||||||
|
rounded
|
||||||
|
class="progress-bar"
|
||||||
|
/>
|
||||||
|
<div class="progress-text">{{ Math.round(overallProgress) }}%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">{{ modules.length }}</span>
|
||||||
|
<span class="stat-label">{{ t('moduleTest.totalModules') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number success">{{ modules.filter(m => m.state === 'success').length }}</span>
|
||||||
|
<span class="stat-label">{{ t('moduleTest.normal') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number error">{{ modules.filter(m => m.state === 'error').length }}</span>
|
||||||
|
<span class="stat-label">{{ t('moduleTest.error') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 检查结果列表 - 可滚动区域 -->
|
||||||
|
<div class="results-container">
|
||||||
|
<div class="module-list">
|
||||||
|
<Transition v-for="(module, index) in modules" :key="module.id" name="module-item" appear>
|
||||||
|
<div
|
||||||
|
v-show="module.visible"
|
||||||
|
class="module-item"
|
||||||
|
:class="[module.state, { 'dark-theme': theme.global.current.value.dark }]"
|
||||||
|
>
|
||||||
|
<div class="module-header">
|
||||||
|
<div class="module-icon">
|
||||||
|
<VIcon v-if="module.loading" icon="mdi-loading" class="rotating" color="primary" size="20" />
|
||||||
|
<VIcon v-else-if="module.state === 'success'" icon="mdi-check-circle" color="success" size="20" />
|
||||||
|
<VIcon v-else-if="module.state === 'error'" icon="mdi-alert-circle" color="error" size="20" />
|
||||||
|
<VIcon v-else icon="mdi-minus-circle" color="grey" size="20" />
|
||||||
|
</div>
|
||||||
|
<div class="module-info">
|
||||||
|
<div class="module-name">{{ module.name }}</div>
|
||||||
|
<div v-if="module.errmsg" class="module-error">{{ module.errmsg }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-status">
|
||||||
|
<VChip v-if="module.loading" color="primary" size="x-small" variant="tonal">
|
||||||
|
{{ t('moduleTest.checking') }}
|
||||||
|
</VChip>
|
||||||
|
<VChip v-else-if="module.state === 'success'" color="success" size="x-small" variant="tonal">
|
||||||
|
{{ t('moduleTest.normal') }}
|
||||||
|
</VChip>
|
||||||
|
<VChip v-else-if="module.state === 'error'" color="error" size="x-small" variant="tonal">
|
||||||
|
{{ t('moduleTest.error') }}
|
||||||
|
</VChip>
|
||||||
|
<VChip v-else-if="module.state === undefined" color="grey" size="x-small" variant="tonal">
|
||||||
|
{{ t('moduleTest.disabled') }}
|
||||||
|
</VChip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 重新检查按钮 -->
|
||||||
|
<div v-if="checkComplete" class="recheck-container">
|
||||||
|
<VBtn color="primary" variant="outlined" prepend-icon="mdi-refresh" size="small" @click="recheck">
|
||||||
|
{{ t('moduleTest.recheck') }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.system-health-check {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: var(--v-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 16px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-block-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
position: relative;
|
||||||
|
margin-block-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background: rgba(255, 255, 255, 20%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 90%);
|
||||||
|
color: #333;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
inset-block-start: -6px;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
padding-block: 2px;
|
||||||
|
padding-inline: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-block-end: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.success {
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.error {
|
||||||
|
color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-container {
|
||||||
|
flex: 1;
|
||||||
|
min-block-size: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-block: 0 16px;
|
||||||
|
padding-inline: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--v-border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--v-surface);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item.success {
|
||||||
|
border-color: #4caf50;
|
||||||
|
background: linear-gradient(135deg, #f8fff9 0%, #e8f5e8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item.success.dark-theme {
|
||||||
|
border-color: #4caf50;
|
||||||
|
background: linear-gradient(135deg, rgba(31, 47, 31, 30%) 0%, rgba(24, 32, 24, 60%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item.error {
|
||||||
|
border-color: #f44336;
|
||||||
|
background: linear-gradient(135deg, #fff8f8 0%, #ffe8e8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item.error.dark-theme {
|
||||||
|
border-color: #f44336;
|
||||||
|
background: linear-gradient(135deg, rgba(47, 31, 31, 30%) 0%, rgba(34, 24, 24, 60%) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-info {
|
||||||
|
flex: 1;
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-name {
|
||||||
|
color: var(--v-on-surface);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-block-end: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-error {
|
||||||
|
color: #f44336;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-block-start: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-status {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recheck-container {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--v-surface-variant);
|
||||||
|
border-block-start: 1px solid var(--v-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
.rotating {
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模块项单独动画 - 从下方滑出 */
|
||||||
|
.module-item-enter-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-item-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user