Files
novalon-manage-system/novalon-manage-web/src/views/audit/ExceptionLog.vue
T
张翔 e2ad1331cc feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置
feat(测试): 添加测试覆盖率报告生成功能
feat(测试): 实现前后端测试脚本集成

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

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

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

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

chore: 更新依赖版本
chore: 添加测试相关配置文件
2026-03-25 09:03:37 +08:00

236 lines
5.9 KiB
Vue

<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>