feat: 添加测试框架和覆盖率报告功能

feat(测试): 新增Playwright和Vitest测试配置
feat(测试): 添加测试覆盖率报告生成功能
feat(测试): 实现前后端测试脚本集成

fix(测试): 修复测试密码不匹配问题
fix(测试): 修正URL等待策略
fix(测试): 调整错误消息选择器

refactor(测试): 重构测试目录结构
refactor(测试): 优化测试用例组织方式

docs: 更新测试报告文档
docs: 添加测试覆盖率报告模板

ci: 添加Docker测试环境配置
ci: 实现测试自动化脚本

chore: 更新依赖版本
chore: 添加测试相关配置文件
This commit is contained in:
张翔
2026-03-25 09:03:37 +08:00
parent 117978e148
commit e2ad1331cc
126 changed files with 18083 additions and 7805 deletions
@@ -74,7 +74,11 @@
sortable="custom"
width="180"
/>
<el-table-column label="操作" width="120" fixed="right">
<el-table-column
label="操作"
width="120"
fixed="right"
>
<template #default="{ row }">
<el-button
type="primary"
@@ -103,7 +107,10 @@
title="异常详情"
width="800px"
>
<el-descriptions :column="1" border>
<el-descriptions
:column="1"
border
>
<el-descriptions-item label="ID">
{{ currentDetail.id }}
</el-descriptions-item>
@@ -120,7 +127,9 @@
<pre style="max-height: 200px; overflow: auto;">{{ currentDetail.params }}</pre>
</el-descriptions-item>
<el-descriptions-item label="异常信息">
<div style="color: #f56c6c; word-break: break-all;">{{ currentDetail.errorMsg }}</div>
<div style="color: #f56c6c; word-break: break-all;">
{{ currentDetail.errorMsg }}
</div>
</el-descriptions-item>
<el-descriptions-item label="异常堆栈">
<pre style="max-height: 300px; overflow: auto; font-size: 12px;">{{ currentDetail.exceptionStack }}</pre>
@@ -133,7 +142,9 @@
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
<el-button @click="detailVisible = false">
关闭
</el-button>
</template>
</el-dialog>
</div>
@@ -66,14 +66,19 @@
:show-overflow-tooltip="true"
>
<template #default="{ row }">
<div v-if="row.params" class="params-content">
<div
v-if="row.params"
class="params-content"
>
<el-popover
placement="top"
:width="500"
trigger="hover"
>
<template #reference>
<div class="params-preview">{{ formatParams(row.params) }}</div>
<div class="params-preview">
{{ formatParams(row.params) }}
</div>
</template>
<pre class="params-detail">{{ formatParams(row.params) }}</pre>
</el-popover>
@@ -2,49 +2,69 @@
<div class="dashboard">
<el-row :gutter="16">
<el-col :span="6">
<el-card v-loading="loading" class="stat-card user-card">
<el-card
v-loading="loading"
class="stat-card user-card"
>
<el-statistic
title="用户总数"
:value="stats.userCount"
>
<template #prefix>
<el-icon class="stat-icon user-icon"><User /></el-icon>
<el-icon class="stat-icon user-icon">
<User />
</el-icon>
</template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card v-loading="loading" class="stat-card role-card">
<el-card
v-loading="loading"
class="stat-card role-card"
>
<el-statistic
title="角色总数"
:value="stats.roleCount"
>
<template #prefix>
<el-icon class="stat-icon role-icon"><UserFilled /></el-icon>
<el-icon class="stat-icon role-icon">
<UserFilled />
</el-icon>
</template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card v-loading="loading" class="stat-card login-card">
<el-card
v-loading="loading"
class="stat-card login-card"
>
<el-statistic
title="今日登录"
:value="stats.todayLogin"
>
<template #prefix>
<el-icon class="stat-icon login-icon"><ArrowRight /></el-icon>
<el-icon class="stat-icon login-icon">
<ArrowRight />
</el-icon>
</template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card v-loading="loading" class="stat-card log-card">
<el-card
v-loading="loading"
class="stat-card log-card"
>
<el-statistic
title="操作日志"
:value="stats.operationLog"
>
<template #prefix>
<el-icon class="stat-icon log-icon"><Document /></el-icon>
<el-icon class="stat-icon log-icon">
<Document />
</el-icon>
</template>
</el-statistic>
</el-card>
@@ -63,7 +83,9 @@
<template #header>
<div class="card-header">
<span class="card-title">最近登录</span>
<el-icon class="header-icon"><Clock /></el-icon>
<el-icon class="header-icon">
<Clock />
</el-icon>
</div>
</template>
<el-timeline>
@@ -85,8 +107,13 @@
</div>
</div>
</el-timeline-item>
<el-timeline-item v-if="recentLogins.length === 0" placement="top">
<div class="empty-tip">暂无登录记录</div>
<el-timeline-item
v-if="recentLogins.length === 0"
placement="top"
>
<div class="empty-tip">
暂无登录记录
</div>
</el-timeline-item>
</el-timeline>
</el-card>
@@ -100,7 +127,9 @@
<template #header>
<div class="card-header">
<span class="card-title">系统信息</span>
<el-icon class="header-icon"><Setting /></el-icon>
<el-icon class="header-icon">
<Setting />
</el-icon>
</div>
</template>
<el-descriptions
@@ -44,18 +44,19 @@
sortable="custom"
/>
<el-table-column
prop="name"
prop="roleName"
label="角色名称"
sortable="custom"
/>
<el-table-column
prop="code"
prop="roleKey"
label="角色标识"
sortable="custom"
/>
<el-table-column
prop="description"
label="描述"
prop="roleSort"
label="显示顺序"
sortable="custom"
/>
<el-table-column
label="状态"
@@ -63,11 +64,11 @@
>
<template #default="{ row }">
<el-tag
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
:type="row.status === RoleStatus.ACTIVE ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
{{ row.status === RoleStatus.ACTIVE ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
@@ -130,21 +131,24 @@
label="角色名称"
required
>
<el-input v-model="formState.name" />
<el-input v-model="formState.roleName" />
</el-form-item>
<el-form-item
label="角色标识"
required
>
<el-input
v-model="formState.code"
v-model="formState.roleKey"
:disabled="!!formState.id"
/>
</el-form-item>
<el-form-item label="描述">
<el-input
v-model="formState.description"
type="textarea"
<el-form-item
label="显示顺序"
required
>
<el-input-number
v-model="formState.roleSort"
:min="1"
/>
</el-form-item>
<el-form-item label="状态">
@@ -208,6 +212,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { roleApi, type Role, type CreateRoleRequest, type UpdateRoleRequest, type Permission } from '@/api/role.api'
import { handleApiError } from '@/utils/errorHandler'
import { RoleStatus } from '@/constants/status'
const loading = ref(false)
const dataSource = ref<Role[]>([])
@@ -225,12 +230,12 @@ const sortInfo = reactive({
const modalVisible = ref(false)
const modalTitle = ref('')
const formState = reactive<CreateRoleRequest & { id?: number }>({
name: '',
code: '',
description: '',
const formState = reactive<CreateRoleRequest & { id?: number; status?: RoleStatus }>({
roleName: '',
roleKey: '',
roleSort: 1,
permissions: [],
status: 'ACTIVE'
status: RoleStatus.ACTIVE
})
const permissionDialogVisible = ref(false)
@@ -282,11 +287,11 @@ const handleAdd = () => {
modalTitle.value = '新增角色'
Object.assign(formState, {
id: undefined,
name: '',
code: '',
description: '',
roleName: '',
roleKey: '',
roleSort: 1,
permissions: [],
status: 'ACTIVE'
status: RoleStatus.ACTIVE
})
modalVisible.value = true
}
@@ -295,9 +300,9 @@ const handleEdit = (row: Role) => {
modalTitle.value = '编辑角色'
Object.assign(formState, {
id: row.id,
name: row.name,
code: row.code,
description: row.description,
roleName: row.roleName,
roleKey: row.roleKey,
roleSort: row.roleSort,
status: row.status,
permissions: []
})
@@ -325,17 +330,18 @@ const handleModalOk = async () => {
try {
if (formState.id) {
const updateData: UpdateRoleRequest = {
name: formState.name,
description: formState.description,
status: formState.status as 'ACTIVE' | 'INACTIVE'
roleName: formState.roleName,
roleKey: formState.roleKey,
roleSort: formState.roleSort,
status: formState.status
}
await roleApi.update(formState.id, updateData)
ElMessage.success('更新成功')
} else {
const createData: CreateRoleRequest = {
name: formState.name,
code: formState.code,
description: formState.description,
roleName: formState.roleName,
roleKey: formState.roleKey,
roleSort: formState.roleSort,
permissions: formState.permissions
}
await roleApi.create(createData)
@@ -69,11 +69,11 @@
>
<template #default="{ row }">
<el-tag
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
:type="row.status === 1 ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
{{ row.status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
@@ -163,11 +163,11 @@
<el-form-item label="状态">
<el-select v-model="formState.status">
<el-option
value="ACTIVE"
:value="UserStatus.ACTIVE"
label="正常"
/>
<el-option
value="INACTIVE"
:value="UserStatus.INACTIVE"
label="禁用"
/>
</el-select>
@@ -218,6 +218,7 @@ import { Search } from '@element-plus/icons-vue'
import { userApi, type User, type CreateUserRequest, type UpdateUserRequest } from '@/api/user.api'
import { roleApi, type Role } from '@/api/role.api'
import { handleApiError } from '@/utils/errorHandler'
import { UserStatus, StatusHelper } from '@/constants/status'
const loading = ref(false)
const dataSource = ref<User[]>([])
@@ -235,14 +236,14 @@ const sortInfo = reactive({
const modalVisible = ref(false)
const modalTitle = ref('')
const formState = reactive<CreateUserRequest & { id?: number }>({
const formState = reactive<CreateUserRequest & { id?: number; status?: UserStatus }>({
username: '',
password: '',
nickname: '',
email: '',
phone: '',
roles: [],
status: 'ACTIVE'
status: UserStatus.ACTIVE
})
const roleDialogVisible = ref(false)
@@ -342,7 +343,7 @@ const handleModalOk = async () => {
nickname: formState.nickname,
email: formState.email,
phone: formState.phone,
status: formState.status as 'ACTIVE' | 'INACTIVE'
status: formState.status
}
await userApi.update(formState.id, updateData)
ElMessage.success('更新成功')