Commit 2d5a76fa by jianan

操作日志1

parent 53d4357f
import request from '@/utils/request'
// 审计日志分页查询
export function getAuditLogPage(data) {
return request({
url: '/csf/api/auditlog/log/page',
method: 'post',
data
})
}
// 审计详情查询
export function getAuditLogDetail(requestId) {
return request({
url: '/csf/api/auditlog/log/detail?requestId=' + requestId,
method: 'get'
})
}
<template>
<div class="app-container">
<CommonPage
:operationBtnList="operationBtnList"
:visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig"/>
</template>
<template #table>
<el-table :data="tableData" v-loading="loading" border>
<el-table-column prop="operTime" label="操作时间" width="170">
<template #default="scope">
<span>{{ parseTime(scope.row.operTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="module" label="业务模块" width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="businessType" label="业务类型" width="100">
<template #default="scope">
<span>{{ BUSINESS_TYPE_MAP[scope.row.businessType] || scope.row.businessType }}</span>
</template>
</el-table-column>
<el-table-column prop="content" label="日志内容" min-width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="operName" label="操作人" width="100"/>
<el-table-column prop="deptName" label="部门" width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<el-tag :type="scope.row.status === 0 ? 'success' : 'danger'" size="small">
{{ scope.row.status === 0 ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="operIp" label="操作IP" width="140"/>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</template>
</CommonPage>
<!-- 审计详情弹窗 -->
<el-dialog
title="审计详情"
v-model="dialogVisible"
width="1000px"
append-to-body
:close-on-click-modal="false"
>
<div v-loading="detailLoading">
<!-- 操作日志基本信息 -->
<el-descriptions v-if="detail.operLog" title="基本信息" :column="2" border style="margin-bottom: 20px">
<el-descriptions-item label="业务模块">{{ detail.operLog.module }}</el-descriptions-item>
<el-descriptions-item label="业务类型">
{{ BUSINESS_TYPE_MAP[detail.operLog.businessType] || detail.operLog.businessType }}
</el-descriptions-item>
<el-descriptions-item label="操作人">{{ detail.operLog.operName }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ detail.operLog.deptName }}</el-descriptions-item>
<el-descriptions-item label="操作时间">{{ parseTime(detail.operLog.operTime) }}</el-descriptions-item>
<el-descriptions-item label="操作IP">{{ detail.operLog.operIp }}</el-descriptions-item>
<el-descriptions-item label="请求URL" :span="2">{{ detail.operLog.operUrl }}</el-descriptions-item>
<el-descriptions-item label="日志内容" :span="2">{{ detail.operLog.content }}</el-descriptions-item>
<el-descriptions-item label="操作状态">
<el-tag :type="detail.operLog.status === 0 ? 'success' : 'danger'" size="small">
{{ detail.operLog.status === 0 ? '成功' : '失败' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="请求ID">{{ detail.operLog.requestId }}</el-descriptions-item>
<el-descriptions-item label="请求参数" :span="2">
<div class="json-content">{{ detail.operLog.operParam || '-' }}</div>
</el-descriptions-item>
<el-descriptions-item label="返回结果" :span="2">
<div class="json-content">{{ detail.operLog.jsonResult || '-' }}</div>
</el-descriptions-item>
<el-descriptions-item v-if="detail.operLog.status === 1" label="错误信息" :span="2">
<span style="color: #f56c6c">{{ detail.operLog.errorMsg }}</span>
</el-descriptions-item>
</el-descriptions>
<!-- 数据快照 -->
<template v-if="detail.snapshots && detail.snapshots.length > 0">
<h4 class="section-title">数据快照(共 {{ detail.snapshots.length }} 条)</h4>
<el-table :data="detail.snapshots" border size="small" style="margin-bottom: 20px">
<el-table-column prop="tableName" label="表名" width="150"/>
<el-table-column prop="recordId" label="记录ID" width="120"/>
<el-table-column label="变更前数据" min-width="280">
<template #default="scope">
<pre v-if="scope.row.beforeJson" class="json-pre">{{ formatJson(scope.row.beforeJson) }}</pre>
<span v-else class="empty-hint">- (新增)</span>
</template>
</el-table-column>
<el-table-column label="变更后数据" min-width="280">
<template #default="scope">
<pre v-if="scope.row.afterJson" class="json-pre">{{ formatJson(scope.row.afterJson) }}</pre>
<span v-else class="empty-hint">- (删除)</span>
</template>
</el-table-column>
</el-table>
</template>
<!-- 字段变更明细 -->
<template v-if="detail.changes && detail.changes.length > 0">
<h4 class="section-title">字段变更明细(共 {{ detail.changes.length }} 条)</h4>
<el-table :data="detail.changes" border size="small">
<el-table-column prop="tableName" label="表名" width="150"/>
<el-table-column prop="fieldComment" label="字段名" width="140"/>
<el-table-column label="变更类型" width="80">
<template #default="scope">
<el-tag
:type="changeTypeTag(scope.row.changeType)"
size="small"
>{{ scope.row.changeType }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="oldValue" label="旧值" min-width="200" :show-overflow-tooltip="true"/>
<el-table-column prop="newValue" label="新值" min-width="200" :show-overflow-tooltip="true"/>
</el-table>
</template>
</div>
<template #footer>
<el-button @click="dialogVisible = false">关 闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue'
import CommonPage from '@/components/commonPage'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import {getAuditLogPage, getAuditLogDetail} from '@/api/monitor/auditlog'
const {proxy} = getCurrentInstance()
// 业务类型映射
const BUSINESS_TYPE_MAP = {
0: '其它', 1: '新增', 2: '修改', 3: '删除',
4: '授权', 5: '导出', 6: '导入', 7: '强退',
8: '生成代码', 9: '清空数据'
}
// 搜索表单配置
const searchFormRef = ref(null)
const searchConfig = ref([
{type: 'input', prop: 'module', label: '业务模块', placeholder: '请输入业务模块'},
{type: 'input', prop: 'operName', label: '操作人', placeholder: '请输入操作人'},
{
type: 'select',
prop: 'status',
label: '操作状态',
placeholder: '请选择',
options: [
{label: '成功', value: 0},
{label: '失败', value: 1}
]
},
{
type: 'daterange',
prop: 'operTimeRange',
label: '操作时间',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
])
// 表格数据
const tableData = ref([])
const loading = ref(false)
// 分页
const currentPage = ref(1)
const pageSize = ref(10)
const pageTotal = ref(0)
const handleSizeChange = val => {
pageSize.value = val
getList()
}
const handleCurrentChange = val => {
currentPage.value = val
getList()
}
// 获取列表数据
const getList = async () => {
loading.value = true
try {
const formData = searchFormRef.value?.getFormData() || {}
const params = {
...formData,
startTime: formData.operTimeRange ? formData.operTimeRange[0] : undefined,
endTime: formData.operTimeRange ? formData.operTimeRange[1] : undefined,
operTimeRange: undefined,
pageNo: currentPage.value,
pageSize: pageSize.value
}
const response = await getAuditLogPage(params)
tableData.value = response.data.records || []
pageTotal.value = response.data.total || 0
} finally {
loading.value = false
}
}
getList()
// 查询
const handleQuery = () => {
currentPage.value = 1
getList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetForm()
currentPage.value = 1
getList()
}
const visibleDefaultButtons = ref(['reset', 'query'])
const operationBtnList = ref([
{key: 'reset', direction: 'right', click: handleReset},
{key: 'query', direction: 'right', click: handleQuery}
])
// ========== 详情弹窗 ==========
const dialogVisible = ref(false)
const detailLoading = ref(false)
const detail = reactive({
operLog: null,
snapshots: [],
changes: []
})
const handleView = async (row) => {
dialogVisible.value = true
detail.operLog = null
detail.snapshots = []
detail.changes = []
if (!row.requestId) return
detailLoading.value = true
try {
const response = await getAuditLogDetail(row.requestId)
if (response.data) {
detail.operLog = response.data.operLog || null
detail.snapshots = response.data.snapshots || []
detail.changes = response.data.changes || []
}
} finally {
detailLoading.value = false
}
}
const formatJson = (jsonStr) => {
try {
return JSON.stringify(JSON.parse(jsonStr), null, 2)
} catch {
return jsonStr
}
}
const changeTypeTag = (type) => {
switch (type) {
case 'ADD':
return 'success'
case 'MODIFY':
return 'warning'
case 'DELETE':
return 'danger'
default:
return 'info'
}
}
</script>
<style scoped>
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin: 0 0 12px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.json-content {
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 12px;
color: #606266;
background: #f5f7fa;
padding: 8px;
border-radius: 4px;
}
.json-pre {
margin: 0;
padding: 6px 8px;
background: #f5f7fa;
border-radius: 4px;
font-size: 12px;
line-height: 1.5;
max-height: 200px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
.empty-hint {
color: #c0c4cc;
font-size: 12px;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment