feat(logs): 日志管理增加筛选维度(操作人/角色/请求方法)

- 后端 querySystemLogs 支持 role/method 过滤;新增 distinctRoles/distinctActors
- 关键词搜索补充 target_name 匹配
- /api/system-logs 返回 roles 与 actors 供前端下拉
- 前端独立筛选工具条:操作人/角色/动作/方法/结果/日期范围 + 清除筛选
- 结束日期改为当日 23:59:59 含当天
This commit is contained in:
freedakgmail
2026-06-14 10:27:24 +08:00
parent 3716564b58
commit f42c04da8b
4 changed files with 94 additions and 26 deletions
+22 -1
View File
@@ -43,6 +43,8 @@ export interface SystemLogQuery {
offset: number;
actorId?: string;
action?: string;
role?: string;
method?: string;
q?: string;
from?: string;
to?: string;
@@ -58,13 +60,15 @@ export async function querySystemLogs(
const params: unknown[] = [];
if (opts.actorId !== undefined && opts.actorId !== '') { params.push(opts.actorId); where.push(`actor_id = $${params.length}`); }
if (opts.action !== undefined && opts.action !== '') { params.push(opts.action); where.push(`action = $${params.length}`); }
if (opts.role !== undefined && opts.role !== '') { params.push(opts.role); where.push(`role = $${params.length}`); }
if (opts.method !== undefined && opts.method !== '') { params.push(opts.method); where.push(`method = $${params.length}`); }
if (opts.success !== undefined) { params.push(opts.success); where.push(`success = $${params.length}`); }
if (opts.from !== undefined && opts.from !== '') { params.push(opts.from); where.push(`ts >= $${params.length}`); }
if (opts.to !== undefined && opts.to !== '') { params.push(opts.to); where.push(`ts <= $${params.length}`); }
if (opts.q !== undefined && opts.q.trim() !== '') {
params.push(`%${opts.q.trim()}%`);
const i = params.length;
where.push(`(path ILIKE $${i} OR actor_name ILIKE $${i} OR action ILIKE $${i} OR target_id ILIKE $${i})`);
where.push(`(path ILIKE $${i} OR actor_name ILIKE $${i} OR action ILIKE $${i} OR target_id ILIKE $${i} OR target_name ILIKE $${i})`);
}
const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
@@ -105,3 +109,20 @@ export async function distinctActions(pool: pg.Pool): Promise<string[]> {
const res = await pool.query('SELECT DISTINCT action FROM system_logs ORDER BY action');
return (res.rows as Array<{ action: string }>).map((r) => String(r.action));
}
/** 已出现过的角色(供筛选下拉)。 */
export async function distinctRoles(pool: pg.Pool): Promise<string[]> {
const res = await pool.query("SELECT DISTINCT role FROM system_logs WHERE role IS NOT NULL AND role <> '' ORDER BY role");
return (res.rows as Array<{ role: string }>).map((r) => String(r.role));
}
/** 已出现过的操作人(id+姓名,供筛选下拉)。 */
export async function distinctActors(pool: pg.Pool): Promise<Array<{ id: string; name: string }>> {
const res = await pool.query(
"SELECT actor_id, actor_name FROM system_logs WHERE actor_id IS NOT NULL AND actor_name IS NOT NULL GROUP BY actor_id, actor_name ORDER BY actor_name",
);
return (res.rows as Array<{ actor_id: string; actor_name: string }>).map((r) => ({
id: String(r.actor_id),
name: String(r.actor_name),
}));
}
+13 -3
View File
@@ -77,6 +77,8 @@ import {
insertSystemLog,
querySystemLogs,
distinctActions,
distinctRoles,
distinctActors,
getApprovalConfig,
saveApprovalConfig,
ensureApprovalConfig,
@@ -1425,12 +1427,14 @@ app.delete('/api/users/:id', requireRole('系统管理员'), async (c) => {
/** 分页查询系统操作日志(系统管理员)。 */
app.get('/api/system-logs', requireRole('系统管理员'), async (c) => {
if (pool === null) return c.json({ items: [], total: 0, page: 1, pageSize: 20, actions: [] });
if (pool === null) return c.json({ items: [], total: 0, page: 1, pageSize: 20, actions: [], roles: [], actors: [] });
const page = Math.max(1, Number(c.req.query('page') ?? 1) || 1);
const pageSize = Math.max(1, Math.min(Number(c.req.query('pageSize') ?? 20) || 20, 200));
const successQ = c.req.query('success');
const fActor = c.req.query('actorId');
const fAction = c.req.query('action');
const fRole = c.req.query('role');
const fMethod = c.req.query('method');
const fQ = c.req.query('q');
const fFrom = c.req.query('from');
const fTo = c.req.query('to');
@@ -1439,13 +1443,19 @@ app.get('/api/system-logs', requireRole('系统管理员'), async (c) => {
offset: (page - 1) * pageSize,
...(fActor ? { actorId: fActor } : {}),
...(fAction ? { action: fAction } : {}),
...(fRole ? { role: fRole } : {}),
...(fMethod ? { method: fMethod } : {}),
...(fQ ? { q: fQ } : {}),
...(fFrom ? { from: fFrom } : {}),
...(fTo ? { to: fTo } : {}),
...(successQ === 'true' || successQ === 'false' ? { success: successQ === 'true' } : {}),
});
const actions = await distinctActions(pool).catch(() => []);
return c.json({ items, total, page, pageSize, actions });
const [actions, roles, actors] = await Promise.all([
distinctActions(pool).catch(() => []),
distinctRoles(pool).catch(() => []),
distinctActors(pool).catch(() => []),
]);
return c.json({ items, total, page, pageSize, actions, roles, actors });
});
/* ------------------------------------------------------------------ *