feat: 添加异常日志功能并优化UI样式

refactor: 重构后端查询逻辑和API响应处理

fix: 修复用户角色更新和文件上传问题

test: 添加前端性能测试脚本和E2E测试用例

chore: 更新依赖版本和配置文件

docs: 添加环境检查脚本和测试文档

style: 统一表格标签样式和路由命名

perf: 优化前端页面加载速度和响应时间
This commit is contained in:
张翔
2026-03-24 13:32:20 +08:00
parent a97d317e4a
commit be5d5ede90
184 changed files with 11231 additions and 1903 deletions
@@ -0,0 +1,224 @@
<template>
<div class="exception-log">
<el-card>
<template #header>
<div class="card-header">
<div class="search-section">
<el-input
v-model="searchKeyword"
placeholder="搜索操作人或异常信息"
clearable
style="width: 300px"
@clear="handleSearch"
@keyup.enter="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button
type="primary"
@click="handleSearch"
>
搜索
</el-button>
</div>
</div>
</template>
<el-table
v-loading="loading"
:data="dataSource"
style="width: 100%"
@sort-change="handleSortChange"
>
<el-table-column
prop="id"
label="ID"
sortable="custom"
width="80"
/>
<el-table-column
prop="username"
label="操作人"
sortable="custom"
width="120"
/>
<el-table-column
prop="operation"
label="操作模块"
sortable="custom"
width="150"
/>
<el-table-column
prop="method"
label="请求方法"
sortable="custom"
width="200"
:show-overflow-tooltip="true"
/>
<el-table-column
prop="errorMsg"
label="异常信息"
:show-overflow-tooltip="true"
width="250"
/>
<el-table-column
prop="ip"
label="IP地址"
sortable="custom"
width="120"
/>
<el-table-column
prop="createTime"
label="异常时间"
sortable="custom"
width="180"
/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="handleViewDetail(row)"
>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 16px; justify-content: flex-end"
@current-change="handleTableChange"
@size-change="handleSizeChange"
/>
</el-card>
<el-dialog
v-model="detailVisible"
title="异常详情"
width="800px"
>
<el-descriptions :column="1" border>
<el-descriptions-item label="ID">
{{ currentDetail.id }}
</el-descriptions-item>
<el-descriptions-item label="操作人">
{{ currentDetail.username }}
</el-descriptions-item>
<el-descriptions-item label="操作模块">
{{ currentDetail.operation }}
</el-descriptions-item>
<el-descriptions-item label="请求方法">
{{ currentDetail.method }}
</el-descriptions-item>
<el-descriptions-item label="请求参数">
<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>
</el-descriptions-item>
<el-descriptions-item label="异常堆栈">
<pre style="max-height: 300px; overflow: auto; font-size: 12px;">{{ currentDetail.exceptionStack }}</pre>
</el-descriptions-item>
<el-descriptions-item label="IP地址">
{{ currentDetail.ip }}
</el-descriptions-item>
<el-descriptions-item label="异常时间">
{{ currentDetail.createTime }}
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { exceptionLogApi, ExceptionLog } from '@/api/exceptionLog'
const loading = ref(false)
const dataSource = ref<ExceptionLog[]>([])
const searchKeyword = ref('')
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
const sortInfo = reactive({
sort: 'id',
order: 'asc'
})
const detailVisible = ref(false)
const currentDetail = ref<ExceptionLog>({})
const fetchData = async () => {
loading.value = true
try {
const res = await exceptionLogApi.getPage({
page: pagination.current - 1,
size: pagination.pageSize,
sort: sortInfo.sort,
order: sortInfo.order,
keyword: searchKeyword.value || undefined
})
dataSource.value = res.content
pagination.total = res.totalElements
} finally {
loading.value = false
}
}
const handleTableChange = () => {
fetchData()
}
const handleSizeChange = () => {
pagination.current = 1
fetchData()
}
const handleSearch = () => {
pagination.current = 1
fetchData()
}
const handleSortChange = ({ prop, order }: any) => {
sortInfo.sort = prop
sortInfo.order = order === 'ascending' ? 'asc' : 'desc'
fetchData()
}
const handleViewDetail = (row: ExceptionLog) => {
currentDetail.value = { ...row }
detailVisible.value = true
}
onMounted(() => fetchData())
</script>
<style scoped lang="css">
.exception-log {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
.search-section {
display: flex;
gap: 8px;
align-items: center;
}
}
}
</style>
@@ -63,7 +63,11 @@
/>
<el-table-column label="状态">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
<el-tag
:type="row.status === '0' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === '0' ? '成功' : '失败' }}
</el-tag>
</template>
@@ -58,7 +58,11 @@
/>
<el-table-column label="状态">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
<el-tag
:type="row.status === '0' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === '0' ? '成功' : '失败' }}
</el-tag>
</template>
@@ -35,7 +35,11 @@
/>
<el-table-column label="类型">
<template #default="{ row }">
<el-tag :type="row.configType === 'Y' ? '' : 'info'">
<el-tag
:type="row.configType === 'Y' ? 'info' : 'success'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.configType === 'Y' ? '内置' : '自定义' }}
</el-tag>
</template>
@@ -31,7 +31,11 @@
/>
<el-table-column label="状态">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
<el-tag
:type="row.status === '0' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
@@ -14,9 +14,22 @@
</el-upload>
</div>
</template>
<div class="search-bar">
<el-input
v-model="searchKeyword"
placeholder="搜索文件名"
clearable
style="width: 300px"
@input="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<el-table
v-loading="loading"
:data="dataSource"
:data="filteredDataSource"
style="width: 100%"
>
<el-table-column
@@ -46,6 +59,10 @@
prop="createdAt"
label="上传时间"
/>
<el-table-column
prop="createBy"
label="上传人"
/>
<el-table-column
label="操作"
width="150"
@@ -75,13 +92,26 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
import { Upload, Search } from '@element-plus/icons-vue'
import request from '@/utils/request'
const loading = ref(false)
const dataSource = ref([])
const searchKeyword = ref('')
const filteredDataSource = computed(() => {
if (!searchKeyword.value) {
return dataSource.value
}
return dataSource.value.filter((item: any) =>
item.fileName.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
const handleSearch = () => {
}
const fetchData = async () => {
loading.value = true
@@ -30,7 +30,11 @@
width="100"
>
<template #default="{ row }">
<el-tag :type="row.noticeType === '1' ? '' : 'success'">
<el-tag
:type="row.noticeType === '1' ? 'info' : 'success'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.noticeType === '1' ? '通知' : '公告' }}
</el-tag>
</template>
@@ -40,7 +44,11 @@
width="80"
>
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
<el-tag
:type="row.status === '0' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
@@ -69,6 +69,10 @@ const onFinish = async () => {
loading.value = true
try {
const res: any = await request.post('/auth/login', formState)
if (res.code === 401) {
ElMessage.error(res.message || '登录失败')
return
}
localStorage.setItem('token', res.token)
localStorage.setItem('userId', res.userId)
localStorage.setItem('username', res.username)
@@ -28,7 +28,11 @@
width="100"
>
<template #default="{ row }">
<el-tag :type="row.menuType === 'M' ? '' : row.menuType === 'C' ? 'success' : 'warning'">
<el-tag
:type="row.menuType === 'M' ? 'info' : row.menuType === 'C' ? 'success' : 'warning'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.menuType === 'M' ? '目录' : row.menuType === 'C' ? '菜单' : '按钮' }}
</el-tag>
</template>
@@ -62,7 +62,11 @@
width="100"
>
<template #default="{ row }">
<el-tag :type="row.status === 'ACTIVE' ? 'success' : 'danger'">
<el-tag
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
</el-tag>
</template>
@@ -68,7 +68,11 @@
width="100"
>
<template #default="{ row }">
<el-tag :type="row.status === 'ACTIVE' ? 'success' : 'danger'">
<el-tag
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
effect="dark"
style="font-weight: 500; font-size: 14px;"
>
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
</el-tag>
</template>