feat(admin): 新增「操作审计」页面 + 收敛前端角色判断

- pages/admin/AdminAuditLogPage.tsx:审计日志查询页。支持操作人/模块/动作/
  结果状态/日期区间筛选,表格按时间倒序,行内「详情」抽屉展示完整 URL、
  UA、request_id、错误信息与变更详情。
  中文标签(模块/动作)由服务端下发,前端不维护枚举映射 —— 与
  constants/task.ts 里状态映射的既有做法一致,避免两端各写一份开始漂移。
- services/auditApi.ts:审计接口客户端与类型定义。
- 路由 /admin/audit + 侧边栏「操作审计」入口。
- AdminProductsPage 里手写的角色判断改为复用 isAdminRole():这是同一份
  「管理员角色」规则的第 4 处副本,本次一并对齐(另一处 TaskFlowView 已在用)。
  constants/task.ts 的注释同步指向后端新位置 core/roles.py。

验证:tsc --noEmit 通过;vite build 通过(产出独立 chunk
AdminAuditLogPage-*.js);对接真实后端校验响应字段与 TS 接口定义逐字段一致。
This commit is contained in:
openhands
2026-09-21 02:28:17 +00:00
parent 7635802a42
commit 14f707461d
6 changed files with 445 additions and 4 deletions

View File

@ -0,0 +1,77 @@
/** 操作审计日志 API */
import api from "./api";
export interface AuditLogItem {
id: string;
user_id: string | null;
display_name: string | null;
role: string | null;
action: string;
/** 服务端补的中文标签,前端不再各自维护枚举映射 */
action_label: string | null;
module: string;
module_label: string | null;
target_type: string | null;
target_id: string | null;
target_name: string | null;
details: Record<string, unknown> | null;
ip_address: string | null;
user_agent: string | null;
method: string | null;
url: string | null;
status_code: number | null;
error_message: string | null;
/** 拿着它可在后端结构化日志中定位同一次请求 */
request_id: string | null;
created_at: string;
}
export interface AuditLogListResponse {
items: AuditLogItem[];
total: number;
}
export interface AuditOption {
value: string;
label: string;
}
export interface AuditOptionsResponse {
modules: AuditOption[];
actions: AuditOption[];
}
export interface AuditLogQuery {
user_id?: string;
module?: string;
action?: string;
target_id?: string;
request_id?: string;
status_code?: number;
/** YYYY-MM-DD */
start_date?: string;
/** YYYY-MM-DD含当天 */
end_date?: string;
page?: number;
page_size?: number;
}
/** 分页查询审计日志(按时间倒序) */
export async function fetchAuditLogs(q: AuditLogQuery = {}): Promise<AuditLogListResponse> {
const params = Object.fromEntries(
Object.entries(q).filter(([, v]) => v !== undefined && v !== null && v !== "")
);
const { data } = await api.get<AuditLogListResponse>("/audit/logs", { params });
return data;
}
/** 获取模块/动作筛选项 */
export async function fetchAuditOptions(): Promise<AuditOptionsResponse> {
const { data } = await api.get<AuditOptionsResponse>("/audit/options");
return data;
}