refactor: 前端构建按需化与 Mock 懒加载

移除全量 Element Plus 与全局 VChart 注册,改为按需引入样式与组件内局部图表;Mock 适配器改为按 VITE_ENABLE_MOCK 环境变量懒加载,生产默认不拦截请求;ECharts 精简为看板所需图表,各视图与 stores 同步适配。
This commit is contained in:
caoxiaozhu
2026-07-16 11:03:54 +08:00
parent ab9e87f948
commit 4173b53b1b
18 changed files with 137 additions and 92 deletions

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import VChart from 'vue-echarts'
import '@/plugins/echarts'
import type { EChartsOption } from 'echarts'
type ServiceState = 'normal' | 'busy' | 'error'
@@ -253,7 +255,7 @@ const loginDurationStats: LoginDurationStat[] = [
const loginDurationChartOption = computed<EChartsOption>(() => ({
animationDuration: 500,
grid: { top: 8, right: 60, bottom: 6, left: 8, containLabel: true },
grid: { top: 8, right: 12, bottom: 6, left: 8, containLabel: true },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
@@ -284,7 +286,7 @@ const loginDurationChartOption = computed<EChartsOption>(() => ({
barMaxWidth: 18,
barCategoryGap: '34%',
itemStyle: { color: '#4f46e5', borderRadius: [0, 4, 4, 0] },
label: { show: true, position: 'right', distance: 6, color: '#64748b', fontSize: 11, formatter: '{c} 小时' },
label: { show: true, position: 'insideRight', distance: 6, color: '#ffffff', fontSize: 11, formatter: '{c} 小时' },
},
],
}))

View File

@@ -1,11 +1,16 @@
<script setup lang="ts">
import { computed, watch } from 'vue'
import { MdEditor } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { computed, defineAsyncComponent, watch } from 'vue'
import { EVAL_METHODS, EVAL_METHOD_PROMPTS } from '@/constants/dimension'
import { DIMENSION_TYPE_MAP } from '@/constants'
import type { DimensionType, ModelItem } from '@/types'
// 编辑器体积较大,仅在用户进入包含 Prompt 的指标配置时加载。
const MdEditor = defineAsyncComponent(async () => {
await import('md-editor-v3/lib/style.css')
const module = await import('md-editor-v3')
return module.MdEditor
})
export interface DimensionFormDraft {
type: DimensionType | ''
description: string

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import DataTablePage from '@/components/DataTablePage.vue'
import ModelStatusTag from '@/components/ModelStatusTag.vue'
import { usePolling } from '@/composables/usePolling'
import { useModelsStore } from '@/stores/models'
import {
getFineTuneList,
@@ -44,14 +45,10 @@ const filteredList = computed(() => {
})
})
let progressTimer: ReturnType<typeof setInterval> | null = null
async function loadData() {
loading.value = true
try {
dataList.value = (await getFineTuneList()) || []
// 列表加载完成后,立即获取一次运行中任务的进度
refreshProgress()
} catch {
// 拦截器已提示
} finally {
@@ -101,14 +98,13 @@ function formatDateTime(value?: string) {
return new Date(value).toLocaleString('zh-CN', { hour12: false })
}
onMounted(() => {
modelsStore.load()
loadData()
progressTimer = setInterval(refreshProgress, 5000)
})
const { start: startProgressPolling } = usePolling(refreshProgress, 5000, { immediate: false })
onUnmounted(() => {
if (progressTimer) clearInterval(progressTimer)
onMounted(async () => {
void modelsStore.load()
await loadData()
await refreshProgress()
startProgressPolling()
})
</script>

View File

@@ -136,7 +136,7 @@ async function handleLogin() {
position: relative;
color: #fff;
background-color: #121127;
background-image: url('@/assets/login-hero-flow.png');
background-image: url('@/assets/login-hero-flow.jpg');
background-size: cover;
background-position: center;
overflow: hidden;

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import VChart from 'vue-echarts'
import PageCard from '@/components/PageCard.vue'
import '@/plugins/echarts-hardware'
import { getSystemInfo } from '@/api/modules/system'
import type { GpuInfo, SystemInfo } from '@/types'

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { ref, computed, onMounted, watch } from 'vue'
import PageCard from '@/components/PageCard.vue'
import { useCountdown } from '@/composables/useCountdown'
import { usePolling } from '@/composables/usePolling'
import {
getLogFiles,
getLogContent,
@@ -30,29 +31,26 @@ const fullContent = ref('')
// 自动刷新
const refreshInterval = ref(10)
const { remaining, start: startCountdown, stop: stopCountdown } = useCountdown(10)
let refreshTimer: ReturnType<typeof setInterval> | null = null
const filteredContent = computed(() => {
if (!keyword.value.trim()) return fullContent.value
const filteredLog = computed(() => {
if (!keyword.value.trim()) return { content: fullContent.value, count: 0 }
const kw = keyword.value.toLowerCase().trim()
return fullContent.value
const lines = fullContent.value
.split('\n')
.filter((line) => line.toLowerCase().includes(kw))
.join('\n')
return { content: lines.join('\n'), count: lines.length }
})
const matchCount = computed(() => {
if (!keyword.value.trim()) return 0
const kw = keyword.value.toLowerCase().trim()
return fullContent.value.split('\n').filter((line) => line.toLowerCase().includes(kw)).length
})
const filteredContent = computed(() => filteredLog.value.content)
const matchCount = computed(() => filteredLog.value.count)
async function loadSysFiles() {
try {
sysFiles.value = (await getLogFiles(sysDate.value)) || []
if (sysFiles.value.length > 0) {
sysSelected.value = sysFiles.value[0].file
loadSysContent()
const firstFile = sysFiles.value[0].file
if (sysSelected.value === firstFile) void loadSysContent()
else sysSelected.value = firstFile
} else {
sysContent.value = '该日期暂无日志文件'
}
@@ -76,8 +74,9 @@ async function loadTrainFiles() {
try {
trainFiles.value = (await getTrainingLogFiles()) || []
if (trainFiles.value.length > 0) {
trainSelected.value = trainFiles.value[0].file
loadTrainContent()
const firstFile = trainFiles.value[0].file
if (trainSelected.value === firstFile) void loadTrainContent()
else trainSelected.value = firstFile
} else {
trainContent.value = '暂无训练日志'
}
@@ -97,29 +96,28 @@ async function loadTrainContent() {
}
}
function refresh() {
async function refresh() {
if (activeTab.value === 'system') {
loadSysContent()
await loadSysContent()
} else {
loadTrainContent()
await loadTrainContent()
}
}
const { start: startPolling, stop: stopPolling } = usePolling(
refresh,
() => refreshInterval.value * 1000,
{ immediate: false },
)
function startAutoRefresh() {
stopAutoRefresh()
stopPolling()
if (refreshInterval.value === 0) {
stopCountdown()
return
}
startCountdown()
refreshTimer = setInterval(refresh, refreshInterval.value * 1000)
}
function stopAutoRefresh() {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
startPolling()
}
watch(refreshInterval, startAutoRefresh)
@@ -135,8 +133,6 @@ onMounted(() => {
loadSysFiles()
startAutoRefresh()
})
onUnmounted(stopAutoRefresh)
</script>
<template>

View File

@@ -1,9 +1,12 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import VChart from 'vue-echarts'
import PageCard from '@/components/PageCard.vue'
import ModelStatusTag from '@/components/ModelStatusTag.vue'
import TrainingTaskOverview from './training-log/TrainingTaskOverview.vue'
import { usePolling } from '@/composables/usePolling'
import '@/plugins/echarts-training-log'
import { useModelsStore } from '@/stores/models'
import { getFineTune } from '@/api/modules/fineTune'
import { getTrainingLogFiles, getTrainingLogContent } from '@/api/modules/log'
@@ -55,7 +58,6 @@ const paramsExpanded = ref(false)
const GPU_PREVIEW_LIMIT = 4
const gpuExpanded = ref(false)
let timer: ReturnType<typeof setInterval> | null = null
let refreshInFlight = false
/** 三个曲线的 ECharts 配置(响应式,数据变化自动重绘) */
@@ -257,14 +259,16 @@ async function refreshAll() {
}
}
onMounted(() => {
modelsStore.load()
refreshAll()
timer = setInterval(refreshAll, 5000)
})
const isTerminalTask = () => ['completed', 'failed', 'stopped', 'cancelled'].includes(task.value?.status || '')
const { start: startPolling, stop: stopPolling } = usePolling(async () => {
await refreshAll()
if (isTerminalTask()) stopPolling()
}, 5000, { immediate: false })
onUnmounted(() => {
if (timer) clearInterval(timer)
onMounted(async () => {
void modelsStore.load()
await refreshAll()
if (!isTerminalTask()) startPolling()
})
</script>