feat(audit-ui): 审计页 UI 重构 —— 干净模块下拉、变更摘要、抽屉详情、触发来源
后端:GET /audit/modules 与 /logs 的 modules 改为下发最终选项 [{value,label}]
· 历史命名(入库管理/采购入库/成品入库/库存管理)折叠进「入库」一项且
**不再单独列出**,下拉里看不到历史包袱。展开仍只由后端 expand_modules
负责 —— 前端若自己再拼一份成员表,将来后端加成员会静默失效(本项目
已经踩过两次这种漂移)。
· 英文历史 module(image_embeddings 2719 条 / purchase_request 75 条 /
sys_element 6 条)在这里翻成中文 label,value 保留英文原值否则筛选
匹配不上。映射表 MODULE_LABELS 从**前端** AuditLog.vue 的 moduleMap
收进 audit_labels.py —— 那份硬编副本历史上已经漂移过一次。
· /audit/labels 新增 moduleDisplay {原始值 → 展示名},同时覆盖聚合成员与
英文历史值:只覆盖后者会出现「下拉显示『入库』、表格显示『库存管理』」,
用户会以为筛选没生效。
后端:新增「触发来源」
· 摘要追加 (来源:/outbound/request) 形式的 URL 尾段(去掉 /api/vN 前缀)。
解决一个具体误解:业务方看到「入库/库存管理」里有大量非库管人员的
UPDATE,以为越权改库存,实际是出库/借还/盘点等单据流转触发的自动扣减。
实测该类 UPDATE 的来源:outbound 1582、outbound/request 424、
borrow/dispatch 102、stocktake/update-quantity 95 —— 光看"谁改的"永远
解释不清,必须能看出"哪个流程触发的"。
· 来源拼在**截断之后**,保证这条关键信息永远不会被截掉。
· 列表与导出共用 changes_summary,故 Excel 台账同样带来源。
后端:其余
· 新增 target_keyword(ilike 同时匹配 target_id / target_name),
放进共用的 _build_audit_query,导出自动支持。
· action 在响应里归一化为 CREATE/UPDATE/DELETE(复用 audit_labels
.canon_action,覆盖批量删除/分配/归还等全部历史别名)。库里还有 1573 条
中文 action,归一后前端不必再为每种历史写法兜底。
· 新增 summary 字段。不算在模型 @property 上:摘要要把 user_id/base_id
翻成人名/物料名,需要查库,模型属性里发查询就是 N+1;改为整页一次
load_ref_maps 批量解析。
· 过滤对象 repr 脏值(<MaterialBase 3030>)。快照收集早期漏跳关系属性,
库里留了 1529 条这种值 —— 不是业务数据,真正的值在对应 _id 字段里。
判据收窄到「<类名 空格 内容>」,避免误伤备注里的「<急件>」。
前端 AuditLog.vue
· 模块下拉改平铺 [{value,label}],删除硬编的 moduleMap
· 新增「操作对象」模糊搜索
· 合并「操作人」「姓名」两列(判据 username==='system' —— 响应里没有
operator_type 字段,那是请求参数名,照搬会永远不显示系统标签)
· 新增「变更摘要」列;操作对象显示为「名称 #id」
· 操作时间改为恒显示北京时间,不再按浏览器时区换算,与数据库/日报/导出一致
· 详情弹窗改 el-drawer,按 action 分支渲染:UPDATE 出对比表,
CREATE/DELETE 出快照属性表,并跳过对象 repr
· 抽屉顶部高亮展示触发来源(METHOD + URL 告警条)
验证:后端 18 + 22 + 10 项断言全过(模块选项无历史名且「入库」=四值之和、
英文 value 仍可筛选、url_source 边界、来源不被截断、导出口径与列表一致、
权限 401/403/200 矩阵);vue-tsc --noEmit 与 vite build 均 exit=0。
日报回归:三天附件 490.5K/193.5K/10.3K,6 列结构与改动前一致。
This commit is contained in:
@ -389,9 +389,106 @@ def person_name_label(value):
|
||||
# 已经是手工同步的副本,不能再塞第二份进去)。
|
||||
# =============================================================================
|
||||
MODULE_GROUPS = {
|
||||
'入库(全部)': ('入库管理', '采购入库', '成品入库', '库存管理'),
|
||||
'入库': ('入库管理', '采购入库', '成品入库', '库存管理'),
|
||||
}
|
||||
|
||||
# 历史遗留的**英文** module 值 → 中文展示名。
|
||||
#
|
||||
# ★ 来源:早期全局监听器(app/utils/audit_events.py,现已停用)没有表白名单,
|
||||
# 系统表、草稿表、向量表都会被审计,而 _infer_module_name 对未登记的类名
|
||||
# 直接回退成**表名**,于是这些英文值混进了 module 列。
|
||||
# 实测存量:image_embeddings 2719 条、purchase_request 75 条、sys_element 6 条;
|
||||
# 表里其余的(stock_buy 等)当前没有数据,但换个环境可能出现,一并留着。
|
||||
#
|
||||
# ★ 这份表原本硬编在前端 AuditLog.vue 的 moduleMap 里,**已经漂移过一次**:
|
||||
# 前端加了表名后端不知道、后端加了前端不显示。收进本文件后,前端经
|
||||
# GET /audit/labels 的 moduleDisplay 取,和字段名/状态码一样只有一份。
|
||||
MODULE_LABELS = {
|
||||
'image_embeddings': '图像特征(历史)',
|
||||
'purchase_request': '采购申请',
|
||||
'sys_element': '系统元素',
|
||||
'sys_menu': '系统菜单',
|
||||
'sys_user': '系统用户',
|
||||
'sys_role': '系统角色',
|
||||
'sys_role_permission': '角色权限',
|
||||
'sys_warehouse_location': '库位设置',
|
||||
'material_base': '物料主数据',
|
||||
'material_warning_settings': '物料预警设置',
|
||||
'stock_buy': '采购库存',
|
||||
'stock_semi': '半成品库存',
|
||||
'stock_product': '成品库存',
|
||||
'stock_adjustment': '库存调整',
|
||||
'trans_outbound': '出库流水',
|
||||
'trans_borrow': '借还流水',
|
||||
'trans_scrap': '报废流水',
|
||||
'trans_repair': '维修单',
|
||||
'bom_table': 'BOM配方',
|
||||
'bom_draft_table': 'BOM草稿',
|
||||
'stocktake_draft': '盘点草稿',
|
||||
}
|
||||
|
||||
|
||||
def module_display(module):
|
||||
"""
|
||||
单个 module 值 → 展示名。
|
||||
|
||||
三级解析,顺序不能换:
|
||||
1. 聚合成员 → 聚合名(「入库管理」→「入库」)
|
||||
2. 英文历史值 → 中文(「image_embeddings」→「图像特征(历史)」)
|
||||
3. 其余原样(现行监听器写的就是中文,无需翻译)
|
||||
|
||||
★ 第 1 级必须在第 2 级之前:聚合名优先,否则「库存管理」会被
|
||||
MODULE_LABELS 之类的表抢先命中而显示成别的。
|
||||
"""
|
||||
m = (module or '').strip()
|
||||
for group, members in MODULE_GROUPS.items():
|
||||
if m in members:
|
||||
return group
|
||||
return MODULE_LABELS.get(m, m)
|
||||
|
||||
|
||||
def module_display_map():
|
||||
"""
|
||||
完整展示映射 {原始值: 展示名},下发给前端。
|
||||
|
||||
★ 必须把**聚合成员**也算进来(不只是 MODULE_LABELS):表格与详情里拿到
|
||||
的是原始 module 值,不映射的话会出现「下拉显示『入库』、表格显示
|
||||
『库存管理』」——用户会以为筛选没生效。
|
||||
"""
|
||||
out = dict(MODULE_LABELS)
|
||||
for group, members in MODULE_GROUPS.items():
|
||||
for m in members:
|
||||
out[m] = group
|
||||
return out
|
||||
|
||||
|
||||
def module_options(raw_modules):
|
||||
"""
|
||||
库里的原始 module 值 → 前端「模块」下拉的最终选项 [{value, label}]。
|
||||
|
||||
★ 为什么由后端算,而不是前端拉原始值自己拼:
|
||||
分组规则(哪些历史值属于同一个业务概念)由 MODULE_GROUPS 管。前端再算
|
||||
一遍就是第二份副本 —— 将来往组里加成员,前端会**静默失效且没有任何
|
||||
报错**。这是本项目反复踩过的坑(见本文件开头、以及前端 AuditLog.vue
|
||||
那个已经漂移过一次的 moduleMap)。
|
||||
|
||||
★ 已归入聚合项的历史值**不再单独列出**:`入库管理`/`采购入库`/`成品入库`
|
||||
与 `库存管理` 是被改名的同一批数据。同时列出会让用户以为它们是不同的
|
||||
东西,选了历史值又只看得到断裂前的一半 —— 那正是 2026-09-10 的口径
|
||||
断裂在界面上的表现。
|
||||
|
||||
代价:无法只查 `库存管理`(09-10 之后的 1371 条)。这是有意取舍 ——
|
||||
那几个名字本身就是历史包袱,把选择权从用户手里拿走比让他选错更安全。
|
||||
"""
|
||||
grouped = {m for members in MODULE_GROUPS.values() for m in members}
|
||||
# 聚合项置顶,其余按名称排序(DISTINCT 返回顺序不保证,排序后位置稳定)
|
||||
opts = [{'value': name, 'label': name} for name in MODULE_GROUPS]
|
||||
# ★ label 走 module_display,英文历史值在这里就变成中文 ——
|
||||
# value 保持原始字符串,否则筛选匹配不上(后端按 module 列的原值过滤)
|
||||
opts += [{'value': m, 'label': module_display(m)}
|
||||
for m in sorted(set(raw_modules or [])) if m and m not in grouped]
|
||||
return opts
|
||||
|
||||
|
||||
def expand_modules(values):
|
||||
"""
|
||||
@ -448,7 +545,11 @@ def labels_payload():
|
||||
'defaultStatus': _stringify_keys(DEFAULT_STATUS_LABELS),
|
||||
'booleanFields': sorted(BOOLEAN_FIELDS),
|
||||
'enumFields': sorted(ENUM_FIELDS),
|
||||
# 模块聚合项及其成员 —— 前端把键注入「模块」下拉,选中后原样回传给
|
||||
# 后端,由 expand_modules() 展开。前端不需要知道成员是谁。
|
||||
# 模块聚合规则及其成员。前端**不需要**它来渲染下拉(下拉的选项由
|
||||
# /audit/modules 下发),保留是为了让"规则"本身可被查看/调试。
|
||||
'moduleGroups': {k: list(v) for k, v in MODULE_GROUPS.items()},
|
||||
# {原始 module 值: 展示名} —— 前端用它把表格/详情里的原始值翻成中文,
|
||||
# 否则会「下拉显示『入库』、表格显示『库存管理』」。
|
||||
# ★ 含英文历史值,前端不再自带一份 moduleMap。
|
||||
'moduleDisplay': module_display_map(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user