提交 226dc687 authored 作者: 陈泽健's avatar 陈泽健

feat(frontend): 性能测试改为子路由结构,监控/报告面板改用路由传参

- 性能测试页面重构为父容器 + 子路由(任务管理/执行监控/报告查看)
- 移除旧版 el-tabs 传参逻辑,监控/报告面板改为从路由 query 读取 taskId
- 任务列表监控/报告按钮改为路由跳转携带 taskId
- App.vue 菜单改为子菜单结构,补充性能测试页标题映射
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 f3f5d771
......@@ -78,11 +78,16 @@
</el-sub-menu>
</el-sub-menu>
<!-- 性能测试(独立一级菜单) -->
<el-menu-item index="/performance">
<el-icon><Odometer /></el-icon>
<span>性能测试</span>
</el-menu-item>
<!-- 性能测试(父菜单,含三个子页面) -->
<el-sub-menu index="/performance">
<template #title>
<el-icon><Odometer /></el-icon>
<span>性能测试</span>
</template>
<el-menu-item index="/performance/tasks">任务管理</el-menu-item>
<el-menu-item index="/performance/monitor">执行监控</el-menu-item>
<el-menu-item index="/performance/report">报告查看</el-menu-item>
</el-sub-menu>
<!-- 功能测试报告(独立一级菜单) -->
<el-menu-item index="/reports/functional">
......@@ -243,13 +248,20 @@ const DEPLOY_LABELS: Record<string, string> = {
executions: '部署执行',
}
/** 性能测试子页面标签映射 */
const PERFORMANCE_LABELS: Record<string, string> = {
tasks: '任务管理',
monitor: '执行监控',
report: '报告查看',
}
/** 当前激活菜单项(无 type 时回退到默认子项以保持高亮) */
const currentRoute = computed(() => {
const seg = route.path.split('/').filter(Boolean)
const root = '/' + (seg[0] || '')
// 无 type 参数时回退到默认子项,保证菜单高亮
if (seg.length === 1 && ['/modules', '/cases', '/execution', '/reports', '/system', '/deploy', '/device-sim'].includes(root)) {
const defaultType = root === '/modules' ? 'standard' : root === '/system' ? 'settings' : root === '/deploy' ? 'servers' : root === '/device-sim' ? 'settings' : 'ui'
if (seg.length === 1 && ['/modules', '/cases', '/execution', '/reports', '/system', '/deploy', '/device-sim', '/performance'].includes(root)) {
const defaultType = root === '/modules' ? 'standard' : root === '/system' ? 'settings' : root === '/deploy' ? 'servers' : root === '/device-sim' ? 'settings' : root === '/performance' ? 'tasks' : 'ui'
return `${root}/${defaultType}`
}
return route.path
......@@ -284,6 +296,7 @@ const pageTitle = computed(() => {
else if (root === '/device-sim') typeLabel = DEVICE_SIM_LABELS[type]
else if (root === '/system') typeLabel = SYSTEM_LABELS[type]
else if (root === '/deploy') typeLabel = DEPLOY_LABELS[type]
else if (root === '/performance') typeLabel = PERFORMANCE_LABELS[type]
return typeLabel ? `${base} - ${typeLabel}` : base
})
......
......@@ -133,7 +133,28 @@ const routes: RouteRecordRaw[] = [
path: '/performance',
name: 'Performance',
component: () => import('@/views/performance/index.vue'),
meta: { title: '性能测试' }
redirect: '/performance/tasks',
meta: { title: '性能测试' },
children: [
{
path: 'tasks',
name: 'PerfTasks',
component: () => import('@/views/performance/TaskList.vue'),
meta: { title: '任务管理' }
},
{
path: 'monitor',
name: 'PerfMonitor',
component: () => import('@/views/performance/MonitorPanel.vue'),
meta: { title: '执行监控' }
},
{
path: 'report',
name: 'PerfReport',
component: () => import('@/views/performance/ReportPanel.vue'),
meta: { title: '报告查看' }
}
]
},
// 重定向:旧版路径 → 性能测试模块
{
......
......@@ -92,15 +92,16 @@
*/
import { ref, watch, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Monitor } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { createPerfMonitorStream } from '@/api/performance'
import type { EChartsOption } from 'echarts'
const props = defineProps<{
taskId: string
}>()
const route = useRoute()
/** 从路由 query 读取任务 ID(由任务列表监控按钮跳转携带) */
const taskId = computed(() => (route.query.taskId as string) || '')
// 连接状态
const connectionStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
......@@ -275,7 +276,7 @@ function handleComplete(data: any) {
/** 连接 WebSocket */
function connectWs() {
if (!props.taskId) return
if (!taskId.value) return
// 关闭旧连接
disconnectWs()
......@@ -299,7 +300,7 @@ function connectWs() {
connectionStatus.value = 'connecting'
ws = createPerfMonitorStream(
props.taskId,
taskId.value,
handleSnapshot,
handleComplete,
() => {
......@@ -333,7 +334,7 @@ function refresh() {
defineExpose({ refresh })
watch(() => props.taskId, (newId) => {
watch(() => route.query.taskId, (newId) => {
if (newId) {
nextTick(() => {
initCharts()
......@@ -343,7 +344,7 @@ watch(() => props.taskId, (newId) => {
})
onMounted(() => {
if (props.taskId) {
if (taskId.value) {
nextTick(() => {
initCharts()
connectWs()
......
......@@ -147,6 +147,7 @@
*/
import { ref, watch, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Document, Refresh, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
......@@ -154,9 +155,9 @@ import { getReport } from '@/api/performance'
import type { PerformanceReportResponse } from '@/types/performance'
import type { EChartsOption } from 'echarts'
const props = defineProps<{
taskId: string
}>()
const route = useRoute()
/** 从路由 query 读取任务 ID(由任务列表报告按钮跳转携带) */
const taskId = computed(() => (route.query.taskId as string) || '')
const loading = ref(false)
const report = ref<PerformanceReportResponse | null>(null)
......@@ -193,10 +194,10 @@ const statusRows = computed(() => {
/** 加载报告 */
async function loadReport() {
if (!props.taskId) return
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(props.taskId)
const res = await getReport(taskId.value)
report.value = res
await nextTick()
initCharts()
......@@ -294,14 +295,14 @@ function formatTime(t: string | null | undefined) {
defineExpose({ loadReport })
watch(() => props.taskId, (newId) => {
watch(() => route.query.taskId, (newId) => {
if (newId) {
loadReport()
}
})
onMounted(() => {
if (props.taskId) {
if (taskId.value) {
loadReport()
}
})
......
......@@ -69,11 +69,11 @@
停止
</el-button>
<el-button v-if="row.status === 'running' || row.status === 'completed'"
link type="primary" size="small" @click="$emit('viewMonitor', row.id)">
link type="primary" size="small" @click="router.push('/performance/monitor?taskId=' + row.id)">
监控
</el-button>
<el-button v-if="row.status === 'completed' || row.status === 'failed'"
link type="primary" size="small" @click="$emit('viewReport', row.id)">
link type="primary" size="small" @click="router.push('/performance/report?taskId=' + row.id)">
报告
</el-button>
<el-button link type="primary" size="small" @click="openEditDialog(row)">
......@@ -221,15 +221,13 @@
*/
import { ref, onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import { listTasks, getTask, createTask, updateTask, deleteTask, runTask, stopTask } from '@/api/performance'
import type { PerformanceTask, PerformanceTaskListItem, PerformanceTaskCreate } from '@/types/performance'
defineEmits<{
viewMonitor: [taskId: string]
viewReport: [taskId: string]
}>()
const router = useRouter()
const loading = ref(false)
const tasks = ref<PerformanceTaskListItem[]>([])
......
<!--
组件名称:PerformanceIndex.vue
组件描述:性能测试模块主页 - 任务管理 / 执行监控 / 报告查看
组件描述:性能测试模块父级容器,渲染子路由
@author czj
@date 2026-08-12
......@@ -11,71 +11,18 @@
<div class="page-header">
<h2>性能测试</h2>
</div>
<el-tabs v-model="activeTab" type="border-card" @tab-change="onTabChange">
<!-- ==================== Tab 1: 任务管理 ==================== -->
<el-tab-pane label="任务管理" name="tasks">
<TaskList ref="taskListRef" @view-monitor="viewMonitor" @view-report="viewReport" />
</el-tab-pane>
<!-- ==================== Tab 2: 执行监控 ==================== -->
<el-tab-pane label="执行监控" name="monitor">
<MonitorPanel ref="monitorPanelRef" :task-id="monitorTaskId" />
</el-tab-pane>
<!-- ==================== Tab 3: 报告查看 ==================== -->
<el-tab-pane label="报告查看" name="report">
<ReportPanel ref="reportPanelRef" :task-id="reportTaskId" />
</el-tab-pane>
</el-tabs>
<router-view />
</div>
</template>
<script setup lang="ts">
/**
* 性能测试模块主页
*
* 三个子标签页:
* 1. 任务管理 — CRUD 列表
* 2. 执行监控 — 实时 TPS/响应时间图表
* 3. 报告查看 — 执行指标汇总与快照详情
* 性能测试模块容器组件
* 三个子页面通过路由切换:
* 1. /performance/tasks — 任务管理
* 2. /performance/monitor — 执行监控
* 3. /performance/report — 报告查看
*/
import { ref } from 'vue'
import TaskList from './TaskList.vue'
import MonitorPanel from './MonitorPanel.vue'
import ReportPanel from './ReportPanel.vue'
const activeTab = ref('tasks')
const monitorTaskId = ref('')
const reportTaskId = ref('')
const taskListRef = ref()
const monitorPanelRef = ref()
const reportPanelRef = ref()
/** 切换到执行监控标签 */
function viewMonitor(taskId: string) {
monitorTaskId.value = taskId
activeTab.value = 'monitor'
}
/** 切换到报告查看标签 */
function viewReport(taskId: string) {
reportTaskId.value = taskId
activeTab.value = 'report'
}
/** 标签切换回调 */
function onTabChange(tab: string) {
// 切换到监控标签时,如果已有任务 ID 则自动刷新
if (tab === 'monitor' && monitorTaskId.value && monitorPanelRef.value) {
monitorPanelRef.value.refresh?.()
}
// 切换到报告标签时,如果已有任务 ID 则自动加载报告
if (tab === 'report' && reportTaskId.value && reportPanelRef.value) {
reportPanelRef.value.loadReport?.()
}
}
</script>
<style scoped>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论