diff --git a/src/persistence/systemLogs.ts b/src/persistence/systemLogs.ts index 0102818..59fa1df 100644 --- a/src/persistence/systemLogs.ts +++ b/src/persistence/systemLogs.ts @@ -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 { 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 { + 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> { + 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), + })); +} diff --git a/src/server/index.ts b/src/server/index.ts index 72e1428..9de6369 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -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 }); }); /* ------------------------------------------------------------------ * diff --git a/web/src/api/client.ts b/web/src/api/client.ts index ef78e06..5aa6693 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -1008,16 +1008,21 @@ export interface SystemLogPage { page: number; pageSize: number; actions: string[]; + roles: string[]; + actors: Array<{ id: string; name: string }>; } /** 查询系统操作审计日志(系统管理员)。 */ export async function fetchSystemLogs(params: { - page: number; pageSize: number; actorId?: string; action?: string; q?: string; from?: string; to?: string; success?: 'true' | 'false'; + page: number; pageSize: number; actorId?: string; action?: string; role?: string; method?: string; q?: string; from?: string; to?: string; success?: 'true' | 'false'; }): Promise { const sp = new URLSearchParams(); sp.set('page', String(params.page)); sp.set('pageSize', String(params.pageSize)); if (params.action) sp.set('action', params.action); + if (params.actorId) sp.set('actorId', params.actorId); + if (params.role) sp.set('role', params.role); + if (params.method) sp.set('method', params.method); if (params.q) sp.set('q', params.q); if (params.from) sp.set('from', params.from); if (params.to) sp.set('to', params.to); diff --git a/web/src/pages/SystemLogs.tsx b/web/src/pages/SystemLogs.tsx index 008c75c..781c01c 100644 --- a/web/src/pages/SystemLogs.tsx +++ b/web/src/pages/SystemLogs.tsx @@ -23,7 +23,12 @@ export function SystemLogs(): JSX.Element { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [actions, setActions] = useState([]); + const [roles, setRoles] = useState([]); + const [actors, setActors] = useState>([]); const [action, setAction] = useState(''); + const [role, setRole] = useState(''); + const [method, setMethod] = useState(''); + const [actorId, setActorId] = useState(''); const [success, setSuccess] = useState<'' | 'true' | 'false'>(''); const [q, setQ] = useState(''); const [qInput, setQInput] = useState(''); @@ -43,17 +48,26 @@ export function SystemLogs(): JSX.Element { fetchSystemLogs({ page, pageSize, ...(action ? { action } : {}), + ...(role ? { role } : {}), + ...(method ? { method } : {}), + ...(actorId ? { actorId } : {}), ...(q ? { q } : {}), ...(from ? { from: new Date(from).toISOString() } : {}), - ...(to ? { to: new Date(to).toISOString() } : {}), + ...(to ? { to: new Date(`${to}T23:59:59`).toISOString() } : {}), ...(success ? { success } : {}), }) - .then((res) => { setItems(res.items); setTotal(res.total); setActions(res.actions); setError(null); }) + .then((res) => { setItems(res.items); setTotal(res.total); setActions(res.actions); setRoles(res.roles); setActors(res.actors); setError(null); }) .catch((e: unknown) => setError(e instanceof Error ? e.message : '加载失败')) .finally(() => setLoading(false)); - }, [page, pageSize, action, q, from, to, success]); + }, [page, pageSize, action, role, method, actorId, q, from, to, success]); useEffect(() => { load(); }, [load]); + const hasFilter = action !== '' || role !== '' || method !== '' || actorId !== '' || success !== '' || qInput !== '' || from !== '' || to !== ''; + const resetFilters = (): void => { + setAction(''); setRole(''); setMethod(''); setActorId(''); setSuccess(''); + setQInput(''); setQ(''); setFrom(''); setTo(''); setPage(1); + }; + const totalPages = Math.max(1, Math.ceil(total / pageSize)); const input: React.CSSProperties = { padding: `${space(1)}px ${space(2)}px`, border: `1px solid ${colorVar('color.border.default')}`, @@ -72,25 +86,43 @@ export function SystemLogs(): JSX.Element {

- - 操作日志({total}) -
- setQInput(e.target.value)} /> - - - { setFrom(e.target.value); setPage(1); }} title="起始日期" /> - { setTo(e.target.value); setPage(1); }} title="结束日期" /> -
+ + {/* 筛选工具条 */} +
+ setQInput(e.target.value)} /> + + + + + + + + {hasFilter && ( + + )}
- }> {error !== null &&
{error}
} {loading ?

加载中…

: (