feat: 添加异常日志功能并优化UI样式
refactor: 重构后端查询逻辑和API响应处理 fix: 修复用户角色更新和文件上传问题 test: 添加前端性能测试脚本和E2E测试用例 chore: 更新依赖版本和配置文件 docs: 添加环境检查脚本和测试文档 style: 统一表格标签样式和路由命名 perf: 优化前端页面加载速度和响应时间
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user