feat(audit): 优化审计日志的人性化展示

This commit is contained in:
DXC
2026-04-22 10:44:13 +08:00
parent 4b794b9bcc
commit 1205d9c7e8
2 changed files with 76 additions and 7 deletions

View File

@ -118,7 +118,7 @@ def insert_audit_log(connection, action, target, details):
target_id = str(target.bom_no) target_id = str(target.bom_no)
# 获取目标名称(用于展示) # 获取目标名称(用于展示)
target_name = target_id target_name = ''
for name_field in ['name', 'title', 'material_name', 'product_name', 'display_name', 'username']: for name_field in ['name', 'title', 'material_name', 'product_name', 'display_name', 'username']:
if hasattr(target, name_field): if hasattr(target, name_field):
val = getattr(target, name_field) val = getattr(target, name_field)
@ -126,6 +126,27 @@ def insert_audit_log(connection, action, target, details):
target_name = str(val) target_name = str(val)
break break
# 如果当前表没名字,但它有关联的物料对象 (比如 material.name)
if not target_name and hasattr(target, 'material') and target.material:
target_name = getattr(target.material, 'name', '')
# 如果当前表有 material_id尝试从关联的 material 表查询名称
if not target_name and hasattr(target, 'material_id') and target.material_id:
try:
# 使用 connection 查询物料表获取名称
result = connection.execute(
text("SELECT name FROM material_base WHERE id = :id"),
{'id': target.material_id}
).fetchone()
if result:
target_name = str(result[0])
except Exception:
pass
# 如果实在找不到名字,再用 表名 + ID 兜底
if not target_name:
target_name = f"{tablename} ID:{target_id}"
user_info = get_current_user_info() user_info = get_current_user_info()
# 推断模块名称 # 推断模块名称

View File

@ -55,12 +55,16 @@
</el-table-column> </el-table-column>
<el-table-column prop="action" label="操作类型" width="100"> <el-table-column prop="action" label="操作类型" width="100">
<template #default="scope"> <template #default="scope">
<el-tag :type="getActionType(scope.row.action)">{{ scope.row.action }}</el-tag> <el-tag :type="getActionType(scope.row.action)">{{ actionMap[scope.row.action] || scope.row.action }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="target_name" label="操作对象" min-width="150" show-overflow-tooltip /> <el-table-column prop="target_name" label="操作对象" min-width="150" show-overflow-tooltip />
<el-table-column prop="ip_address" label="IP地址" width="130" /> <el-table-column prop="ip_address" label="IP地址" width="130" />
<el-table-column prop="created_at" label="操作时间" width="170" /> <el-table-column prop="created_at" label="操作时间" width="170">
<template #default="scope">
{{ formatLocalTime(scope.row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right"> <el-table-column label="操作" width="120" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button <el-button
@ -100,12 +104,12 @@
<el-tag>{{ currentLog.module }}</el-tag> <el-tag>{{ currentLog.module }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作类型"> <el-descriptions-item label="操作类型">
<el-tag :type="getActionType(currentLog.action)">{{ currentLog.action }}</el-tag> <el-tag :type="getActionType(currentLog.action)">{{ actionMap[currentLog.action] || currentLog.action }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作对象" :span="2">{{ currentLog.target_name || '-' }}</el-descriptions-item> <el-descriptions-item label="操作对象" :span="2">{{ currentLog.target_name || '-' }}</el-descriptions-item>
<el-descriptions-item label="IP地址">{{ currentLog.ip_address }}</el-descriptions-item> <el-descriptions-item label="IP地址">{{ currentLog.ip_address }}</el-descriptions-item>
<el-descriptions-item label="请求方式">{{ currentLog.method }}</el-descriptions-item> <el-descriptions-item label="请求方式">{{ currentLog.method }}</el-descriptions-item>
<el-descriptions-item label="操作时间" :span="2">{{ currentLog.created_at }}</el-descriptions-item> <el-descriptions-item label="操作时间" :span="2">{{ formatLocalTime(currentLog.created_at) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<!-- 变更明细区域支持同时展示多种结构 --> <!-- 变更明细区域支持同时展示多种结构 -->
@ -118,9 +122,9 @@
字段变更详情 {{ changesList.length }} 处变更 字段变更详情 {{ changesList.length }} 处变更
</div> </div>
<el-table :data="changesList" border stripe size="small" max-height="350"> <el-table :data="changesList" border stripe size="small" max-height="350">
<el-table-column prop="field" label="字段名" width="150"> <el-table-column label="字段名" width="150">
<template #default="{ row }"> <template #default="{ row }">
<span class="field-name">{{ row.field }}</span> <span class="field-name">{{ fieldMap[row.field] || row.field }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="修改前" min-width="200"> <el-table-column label="修改前" min-width="200">
@ -218,6 +222,50 @@ const dateRange = ref<[string, string] | null>(null)
const moduleOptions = ref<string[]>([]) const moduleOptions = ref<string[]>([])
const actionOptions = ref<string[]>(['create', 'update', 'delete', 'export', 'import']) const actionOptions = ref<string[]>(['create', 'update', 'delete', 'export', 'import'])
// ============================================================
// 中文化映射
// ============================================================
// 操作类型中文化映射
const actionMap: Record<string, string> = {
'UPDATE': '修改',
'CREATE': '新增',
'DELETE': '删除',
'LOGIN': '登录',
'LOGOUT': '登出'
};
// 字段名中文化映射 (常见业务字段)
const fieldMap: Record<string, string> = {
'available_quantity': '可用库存',
'in_quantity': '入库数量',
'stock_quantity': '总库存',
'out_quantity': '出库数量',
'name': '名称',
'material_name': '物料名称',
'spec_model': '规格型号',
'category': '类别',
'status': '状态',
'remark': '备注',
'is_active': '是否启用'
};
// 时间格式化:将后端的 UTC 时间字符串转换为本地时间 (UTC+8)
const formatLocalTime = (timeStr: string) => {
if (!timeStr) return '-'
// 补全 'Z' 让浏览器识别为 UTC 时间,自动转为当前系统的时区
const date = new Date(timeStr.replace(' ', 'T') + 'Z')
if (isNaN(date.getTime())) return timeStr
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
const h = String(date.getHours()).padStart(2, '0')
const min = String(date.getMinutes()).padStart(2, '0')
const s = String(date.getSeconds()).padStart(2, '0')
return `${y}-${m}-${d} ${h}:${min}:${s}`
}
// 详情弹窗 // 详情弹窗
const detailDialogVisible = ref(false) const detailDialogVisible = ref(false)
const currentLog = ref<any>({}) const currentLog = ref<any>({})