import { ReloadOutlined, SearchOutlined } from '@ant-design/icons' import { Alert, Button, Card, Empty, Form, Input, Select, Space, Table, Typography } from 'antd' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useSearchParams } from 'react-router' import { AdminRangePicker } from '@/components/admin/AdminDatePicker' import PageHeader from '@/components/admin/PageHeader' import StatusTag from '@/components/admin/StatusTag' import dayjs from '@/lib/dayjs' import { fetchAdminAuditLogs } from '@/services/admin' import type { AdminAuditLogItem } from '@/types/admin' import { hasAdminRole } from '@/utils/admin-auth' import { formatAuditAction, formatAuditTargetType } from '@/utils/admin-display' import { adminAuditActionOptions, adminAuditTargetTypeOptions } from '@/utils/admin-options' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination, resolveAdminPageSize, } from '@/utils/admin-pagination' import { formatAdminDateTime } from '@/utils/admin-time' import { stringifyDisplayJson } from '@/utils/date-time' type AuditFilterForm = { actorUsername?: string action?: string targetType?: string dateRange?: unknown } export default function AdminAuditLogsPage() { const isAdmin = hasAdminRole('admin') const [searchParams, setSearchParams] = useSearchParams() const page = Number(searchParams.get('page') || 1) || 1 const pageSize = resolveAdminPageSize(searchParams.get('pageSize'), ADMIN_DEFAULT_PAGE_SIZE) const filters = { actorUsername: searchParams.get('actorUsername') || '', action: searchParams.get('action') || '', targetType: searchParams.get('targetType') || '', dateFrom: searchParams.get('dateFrom') || '', dateTo: searchParams.get('dateTo') || '', } const queryParams = useMemo( () => ({ page, pageSize, ...filters }), [ filters.action, filters.actorUsername, filters.dateFrom, filters.dateTo, filters.targetType, page, pageSize, ], ) const query = useQuery({ queryKey: ['admin-audit-logs', queryParams], enabled: isAdmin, queryFn: () => fetchAdminAuditLogs(queryParams), }) const data = query.data?.data const columns: TableColumnsType = [ { title: '时间', dataIndex: 'createdAt', width: 180, render: (value) => formatAdminDateTime(value), }, { title: '操作人', minWidth: 140, render: (_, row) => (
{row.actorUsername || '-'}
), }, { title: '动作', dataIndex: 'action', minWidth: 180, render: (value) => {formatAuditAction(value)}, }, { title: '目标', minWidth: 180, render: (_, row) => ( {formatAuditTargetType(row.targetType)} #{row.targetId || '-'} ), }, { title: '摘要', minWidth: 260, render: (_, row) => {formatPayload(row.payload)}, }, ] function applyFilters(values: AuditFilterForm) { const [dateFrom, dateTo] = Array.isArray(values.dateRange) ? values.dateRange.map((item: { format?: (pattern: string) => string }) => item?.format ? item.format('YYYY-MM-DD') : '', ) : ['', ''] setSearchParams( cleanParams({ actorUsername: values.actorUsername, action: values.action, targetType: values.targetType, dateFrom, dateTo, page: 1, pageSize, }), ) } function resetFilters() { setSearchParams(cleanParams({ page: 1, pageSize })) } return (
共 {data?.pagination.total || 0} 条审计记录 } /> {!isAdmin ? ( ) : ( <> layout="inline" initialValues={{ actorUsername: filters.actorUsername, action: filters.action, targetType: filters.targetType, dateRange: filters.dateFrom && filters.dateTo ? [dayjs(filters.dateFrom), dayjs(filters.dateTo)] : undefined, }} onFinish={applyFilters} className="filter-form" > {query.error ? ( ) : null} rowKey="logId" loading={query.isLoading || query.isFetching} columns={columns} dataSource={data?.items || []} locale={{ emptyText: }} scroll={{ x: 980 }} pagination={buildAdminTablePagination({ current: data?.pagination.page || page, pageSize: data?.pagination.pageSize || pageSize, total: data?.pagination.total || 0, onChange: (nextPage, nextPageSize) => { setSearchParams( cleanParams({ ...filters, page: nextPage, pageSize: nextPageSize }), ) }, })} /> )}
) } function formatPayload(payload: Record) { const text = stringifyDisplayJson(payload, 0) return text.length > 140 ? `${text.slice(0, 140)}...` : text } function cleanParams(params: Record) { const next = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { const normalized = String(value ?? '').trim() if (normalized) { next.set(key, normalized) } }) return next }