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