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

feat(frontend): 菜单重构为自动化测试父菜单,性能/功能报告独立,修复性能任务编辑详情加载

- App.vue: 菜单重构(自动化测试整合 /auto 父菜单,性能测试、功能测试报告独立一级菜单,部署/设备模拟/系统管理重新排序)
- Cases.vue: 移除 deploy/performance/functional 用例类型选项
- Reports.vue: 报告类型标签映射(ui/api/security/functional)
- TaskList.vue: 编辑弹窗改用 getTask 获取完整详情,修复 stepConcurrency 数组处理
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 8a8af0dd
......@@ -37,67 +37,83 @@
<el-menu-item index="/modules/custom">项目定制模块</el-menu-item>
</el-sub-menu>
<!-- 用例管理(父菜单) -->
<el-sub-menu index="/cases">
<!-- 自动化测试(父菜单,UI/接口/安全 三类) -->
<el-sub-menu index="/auto">
<template #title>
<el-icon><Document /></el-icon>
<span>用例管理</span>
<el-icon><VideoPlay /></el-icon>
<span>自动化测试</span>
</template>
<el-menu-item index="/cases/ui">UI自动化</el-menu-item>
<el-menu-item index="/cases/api">接口自动化</el-menu-item>
<el-menu-item index="/cases/security">安全自动化测试</el-menu-item>
<el-menu-item index="/cases/deploy">自动化部署测试</el-menu-item>
<el-menu-item index="/performance">性能测试</el-menu-item>
<el-menu-item index="/cases/functional">功能测试</el-menu-item>
<!-- 用例管理 -->
<el-sub-menu index="/cases">
<template #title>
<el-icon><Document /></el-icon>
<span>用例管理</span>
</template>
<el-menu-item index="/cases/ui">UI自动化</el-menu-item>
<el-menu-item index="/cases/api">接口自动化</el-menu-item>
<el-menu-item index="/cases/security">安全自动化测试</el-menu-item>
</el-sub-menu>
<!-- 执行中心 -->
<el-sub-menu index="/execution">
<template #title>
<el-icon><VideoPlay /></el-icon>
<span>执行中心</span>
</template>
<el-menu-item index="/execution/ui">UI自动化</el-menu-item>
<el-menu-item index="/execution/api">接口自动化</el-menu-item>
<el-menu-item index="/execution/security">安全自动化测试</el-menu-item>
</el-sub-menu>
<!-- 报告中心 -->
<el-sub-menu index="/reports">
<template #title>
<el-icon><DataLine /></el-icon>
<span>报告中心</span>
</template>
<el-menu-item index="/reports/ui">UI自动化测试</el-menu-item>
<el-menu-item index="/reports/api">接口测试</el-menu-item>
<el-menu-item index="/reports/security">安全测试</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="/recorder">
<el-icon><VideoCamera /></el-icon>
<span>用例录制</span>
<!-- 性能测试(独立一级菜单) -->
<el-menu-item index="/performance">
<el-icon><Odometer /></el-icon>
<span>性能测试</span>
</el-menu-item>
<!-- 执行中心(父菜单,6 种类型) -->
<el-sub-menu index="/execution">
<template #title>
<el-icon><VideoPlay /></el-icon>
<span>执行中心</span>
</template>
<el-menu-item index="/execution/ui">UI自动化</el-menu-item>
<el-menu-item index="/execution/api">接口自动化</el-menu-item>
<el-menu-item index="/execution/security">安全自动化测试</el-menu-item>
<el-menu-item index="/execution/deploy">自动化部署测试</el-menu-item>
<el-menu-item index="/performance">性能测试</el-menu-item>
<el-menu-item index="/execution/functional">功能测试</el-menu-item>
</el-sub-menu>
<!-- 功能测试报告(独立一级菜单) -->
<el-menu-item index="/reports/functional">
<el-icon><DocumentChecked /></el-icon>
<span>功能测试报告</span>
</el-menu-item>
<!-- 报告中心(父菜单,5 种类型,无部署) -->
<el-sub-menu index="/reports">
<!-- 自动化部署测试(父菜单) -->
<el-sub-menu index="/deploy">
<template #title>
<el-icon><DataLine /></el-icon>
<span>报告中心</span>
<el-icon><Promotion /></el-icon>
<span>自动化部署测试</span>
</template>
<el-menu-item index="/reports/ui">UI自动化测试</el-menu-item>
<el-menu-item index="/reports/api">接口测试</el-menu-item>
<el-menu-item index="/reports/security">安全测试</el-menu-item>
<el-menu-item index="/performance">性能测试</el-menu-item>
<el-menu-item index="/reports/functional">功能测试</el-menu-item>
<el-menu-item index="/deploy/servers">服务器管理</el-menu-item>
<el-menu-item index="/deploy/scripts">部署脚本</el-menu-item>
<el-menu-item index="/deploy/executions">部署执行</el-menu-item>
</el-sub-menu>
<!-- 系统管理(父菜单) -->
<el-sub-menu index="/system">
<!-- 辅助工具(父菜单) -->
<el-sub-menu index="/tools">
<template #title>
<el-icon><Tools /></el-icon>
<span>系统管理</span>
<span>辅助工具</span>
</template>
<el-menu-item index="/system/settings">系统配置</el-menu-item>
<el-menu-item index="/system/info">系统信息</el-menu-item>
<el-menu-item index="/system/logs">操作日志</el-menu-item>
<el-menu-item index="/recorder">用例录制</el-menu-item>
</el-sub-menu>
<!-- 设备模拟(菜单) -->
<!-- 设备模拟(独立一级菜单) -->
<el-sub-menu index="/device-sim">
<template #title>
<el-icon><Connection /></el-icon>
<el-icon><Iphone /></el-icon>
<span>设备模拟</span>
</template>
<el-menu-item index="/device-sim/settings">环境配置</el-menu-item>
......@@ -107,15 +123,15 @@
<el-menu-item index="/device-sim/client">集控客户端模拟</el-menu-item>
</el-sub-menu>
<!-- 自动化部署测试(父菜单) -->
<el-sub-menu index="/deploy">
<!-- 系统管理(父菜单) -->
<el-sub-menu index="/system">
<template #title>
<el-icon><Promotion /></el-icon>
<span>自动化部署测试</span>
<el-icon><Setting /></el-icon>
<span>系统管理</span>
</template>
<el-menu-item index="/deploy/servers">服务器管理</el-menu-item>
<el-menu-item index="/deploy/scripts">部署脚本</el-menu-item>
<el-menu-item index="/deploy/executions">部署执行</el-menu-item>
<el-menu-item index="/system/settings">系统配置</el-menu-item>
<el-menu-item index="/system/info">系统信息</el-menu-item>
<el-menu-item index="/system/logs">操作日志</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
......@@ -164,12 +180,14 @@ import {
DataBoard,
Folder,
Document,
VideoCamera,
Tools,
VideoPlay,
DataLine,
Connection,
Promotion
Odometer,
DocumentChecked,
Promotion,
Iphone,
Tools,
Setting
} from '@element-plus/icons-vue'
// ==================== 响应式数据 ====================
......@@ -186,9 +204,6 @@ const CASE_TYPE_LABELS: Record<string, string> = {
ui: 'UI自动化',
api: '接口自动化',
security: '安全自动化测试',
deploy: '自动化部署测试',
performance: '性能测试',
functional: '功能测试',
}
/** 设备模拟子页面标签映射 */
......@@ -200,13 +215,12 @@ const DEVICE_SIM_LABELS: Record<string, string> = {
client: '集控客户端模拟',
}
/** 分类标签映射:报告中心(不含部署) */
/** 分类标签映射:报告中心 */
const REPORT_TYPE_LABELS: Record<string, string> = {
ui: 'UI自动化测试',
api: '接口测试',
security: '安全测试',
performance: '性能测试',
functional: '功能测试',
functional: '功能测试报告',
}
/** 模块类型标签映射 */
......@@ -234,8 +248,8 @@ const currentRoute = computed(() => {
const seg = route.path.split('/').filter(Boolean)
const root = '/' + (seg[0] || '')
// 无 type 参数时回退到默认子项,保证菜单高亮
if (seg.length === 1 && ['/modules', '/cases', '/execution', '/reports', '/device-sim', '/system', '/deploy', '/performance'].includes(root)) {
const defaultType = root === '/modules' ? 'standard' : root === '/device-sim' ? 'settings' : root === '/system' ? 'settings' : root === '/deploy' ? 'servers' : 'ui'
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'
return `${root}/${defaultType}`
}
return route.path
......@@ -248,14 +262,16 @@ const pageTitle = computed(() => {
const baseMap: Record<string, string> = {
'/': '测试总览',
'/modules': '模块管理',
'/auto': '自动化测试',
'/cases': '用例管理',
'/recorder': '用例录制',
'/execution': '执行中心',
'/reports': '报告中心',
'/system': '系统管理',
'/device-sim': '设备模拟',
'/performance': '性能测试',
'/deploy': '自动化部署测试',
'/performance': '性能测试'
'/tools': '辅助工具',
'/device-sim': '设备模拟',
'/system': '系统管理',
}
const base = baseMap[root] || '测试管理平台'
const type = seg[1]
......
......@@ -600,9 +600,6 @@ const CASE_TYPE_OPTIONS = [
{ label: 'UI自动化', value: 'ui' },
{ label: '接口自动化', value: 'api' },
{ label: '安全自动化测试', value: 'security' },
{ label: '自动化部署测试', value: 'deploy' },
{ label: '性能测试', value: 'performance' },
{ label: '功能测试', value: 'functional' },
]
/** 当前用例类型(来自路由参数,默认 ui) */
......
......@@ -30,8 +30,8 @@
<el-table-column prop="name" label="执行名称" min-width="200" />
<el-table-column label="报告类型" width="120">
<template #default="{ row }">
<el-tag :type="row.case_type === 'security' ? 'danger' : 'primary'" size="small">
{{ row.case_type === 'security' ? '安全测试' : 'UI自动化' }}
<el-tag :type="reportTypeTag(row.case_type)" size="small">
{{ reportTypeLabel(row.case_type) }}
</el-tag>
</template>
</el-table-column>
......@@ -189,6 +189,22 @@ const currentSecurityExecutionId = ref('')
/** 当前用例类型(来自路由参数,默认 ui) */
const currentType = computed(() => (route.params.type as string) || 'ui')
/** 报告类型标签映射 */
const REPORT_TYPE_LABELS: Record<string, string> = {
ui: 'UI自动化测试',
api: '接口测试',
security: '安全测试',
functional: '功能测试报告',
}
const REPORT_TYPE_TAGS: Record<string, string> = {
ui: 'primary',
api: 'success',
security: 'danger',
functional: 'warning',
}
const reportTypeLabel = (type: string) => REPORT_TYPE_LABELS[type] || type
const reportTypeTag = (type: string) => REPORT_TYPE_TAGS[type] || 'info'
/** 渲染后的 Markdown */
const renderedMarkdown = computed(() => {
if (!securityReportContent.value) return ''
......
......@@ -223,7 +223,7 @@
import { ref, onMounted, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import { listTasks, 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'
defineEmits<{
......@@ -319,27 +319,36 @@ function openCreateDialog() {
}
/** 打开编辑弹窗 */
function openEditDialog(task: PerformanceTask) {
editingTask.value = task
async function openEditDialog(task: PerformanceTaskListItem) {
// 表格行是 PerformanceTaskListItem(缺少 body/headers/stepConcurrency 等字段),
// 必须调用 getTask 获取完整详情再填充表单
let fullTask: PerformanceTask
try {
fullTask = await getTask(task.id)
} catch {
// 回退到行数据(至少能显示名称/URL/模式等基本信息)
fullTask = task as unknown as PerformanceTask
}
editingTask.value = fullTask
Object.assign(form, {
name: task.name,
description: task.description,
targetUrl: task.targetUrl,
method: task.method,
headers: task.headers ? JSON.stringify(task.headers, null, 2) : '',
body: task.body || '',
bodyTemplate: task.bodyTemplate,
mode: task.mode,
concurrency: task.concurrency,
qpsTarget: task.qpsTarget,
duration: task.duration,
rampUp: task.rampUp,
stepConcurrency: task.stepConcurrency,
stepDuration: task.stepDuration,
authRequired: task.authRequired,
accountKey: task.accountKey,
signRequest: task.signRequest,
assertions: task.assertions || [],
name: fullTask.name,
description: fullTask.description,
targetUrl: fullTask.targetUrl,
method: fullTask.method,
headers: fullTask.headers ? JSON.stringify(fullTask.headers, null, 2) : '',
body: fullTask.body ? (typeof fullTask.body === 'string' ? fullTask.body : JSON.stringify(fullTask.body, null, 2)) : '',
bodyTemplate: fullTask.bodyTemplate,
mode: fullTask.mode,
concurrency: fullTask.concurrency,
qpsTarget: fullTask.qpsTarget,
duration: fullTask.duration,
rampUp: fullTask.rampUp,
stepConcurrency: Array.isArray(fullTask.stepConcurrency) ? Math.max(...fullTask.stepConcurrency) : fullTask.stepConcurrency,
stepDuration: fullTask.stepDuration,
authRequired: fullTask.authRequired,
accountKey: fullTask.accountKey,
signRequest: fullTask.signRequest,
assertions: fullTask.assertions || [],
})
dialogVisible.value = true
}
......@@ -377,7 +386,7 @@ async function handleSave() {
qpsTarget: form.mode === 'qps' ? form.qpsTarget : null,
duration: form.duration,
rampUp: form.rampUp || null,
stepConcurrency: form.mode === 'step' ? form.stepConcurrency : null,
stepConcurrency: form.mode === 'step' && form.stepConcurrency ? [form.stepConcurrency] : null,
stepDuration: form.mode === 'step' ? (form.stepDuration || 30) : null,
authRequired: form.authRequired,
accountKey: form.authRequired ? form.accountKey : null,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论