Files
lislgosms/src/apps/admin/AdminSystemLogsPage.tsx
T

143 lines
5.4 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import { CalendarDays, Download, FileText, Search } from 'lucide-react';
import { Button, Input, Pagination, Select, Table, Tag, type TableColumn } from '@/components/ui';
import { adminApi, type OperationLogItem } from '@/api/adminApi';
type LogLevel = 'info' | 'success' | 'warning' | 'error';
const levelLabelMap: Record<LogLevel, string> = {
info: '信息',
success: '成功',
warning: '警告',
error: '错误',
};
const levelToneMap: Record<LogLevel, 'info' | 'success' | 'warning' | 'danger'> = {
info: 'info',
success: 'success',
warning: 'warning',
error: 'danger',
};
export function AdminSystemLogsPage() {
const [keyword, setKeyword] = useState('');
const [level, setLevel] = useState('all');
const [module, setModule] = useState('all');
const [range, setRange] = useState('today');
const [page, setPage] = useState(1);
const pageSize = 5;
const [logs, setLogs] = useState<OperationLogItem[]>([]);
const [modules, setModules] = useState<string[]>([]);
const [total, setTotal] = useState(0);
const [error, setError] = useState('');
useEffect(() => {
adminApi.listSystemLogs({ keyword, level, module, range, page, pageSize })
.then((data) => {
setLogs(data.items);
setModules(data.modules);
setTotal(data.total);
setError('');
})
.catch((err) => {
setLogs([]);
setTotal(0);
setError(err instanceof Error ? err.message : '系统日志加载失败');
});
}, [keyword, level, module, range, page]);
const moduleOptions = useMemo(() => {
return [{ label: '全部模块', value: 'all' }, ...modules.map((item) => ({ label: item, value: item }))];
}, [modules]);
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const currentPage = Math.min(page, totalPages);
const columns = useMemo<Array<TableColumn<OperationLogItem>>>(() => [
{ key: 'time', title: '时间', width: '180px', render: (record) => <span className="muted">{new Date(record.time).toLocaleString('zh-CN')}</span> },
{ key: 'level', title: '级别', width: '100px', render: (record) => <Tag tone={levelToneMap[record.level]}>{levelLabelMap[record.level]}</Tag> },
{ key: 'tenant', title: '企业', width: '190px', render: (record) => <strong>{record.tenant}</strong> },
{ key: 'module', title: '模块', width: '130px', render: (record) => record.module },
{ key: 'operator', title: '操作人', width: '120px', render: (record) => <strong>{record.operator}</strong> },
{ key: 'action', title: '动作', width: '140px', render: (record) => record.action },
{ key: 'resourceId', title: '资源ID', width: '150px', render: (record) => <span className="muted">{record.resourceId}</span> },
{
key: 'detail',
title: '详情',
width: '320px',
render: (record) => (
<div className="system-log-detail-card">
<strong>{record.action}</strong>
<span>{JSON.stringify(record.detail)}</span>
<small>{record.resourceId}</small>
</div>
),
},
{ key: 'ip', title: 'IP', width: '120px', render: (record) => <span className="muted">{record.ip}</span> },
], []);
return (
<section className="page-stack system-page">
<div className="system-page-toolbar">
<div className="sms-send-title">
<span className="sms-send-title__icon"><FileText size={22} /></span>
<h1>系统日志</h1>
</div>
<Button icon={<Download size={17} />} variant="secondary">导出日志</Button>
</div>
<div className="system-log-filters">
<Input
onChange={(event) => { setKeyword(event.target.value); setPage(1); }}
placeholder="搜索企业、操作人、动作、资源ID或详情"
prefix={<Search size={16} />}
value={keyword}
/>
<Select
onChange={(event) => { setLevel(event.target.value); setPage(1); }}
options={[
{ label: '全部级别', value: 'all' },
{ label: '信息', value: 'info' },
{ label: '成功', value: 'success' },
{ label: '警告', value: 'warning' },
{ label: '错误', value: 'error' },
]}
value={level}
/>
<Select onChange={(event) => { setModule(event.target.value); setPage(1); }} options={moduleOptions} value={module} />
</div>
<div className="system-log-range">
<span><CalendarDays size={18} /> 时间范围:</span>
{[
{ label: '今天', value: 'today' },
{ label: '近7天', value: '7d' },
{ label: '近30天', value: '30d' },
{ label: '全部', value: 'all' },
].map((item) => (
<Button
key={item.value}
onClick={() => setRange(item.value)}
size="sm"
variant={range === item.value ? 'primary' : 'secondary'}
>
{item.label}
</Button>
))}
</div>
<div className="surface system-table-card">
<Table columns={columns} data={logs} emptyText={error || '暂无系统日志'} rowKey="id" />
<Pagination
nextDisabled={currentPage >= totalPages}
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
page={currentPage}
previousDisabled={currentPage <= 1}
total={total}
/>
</div>
</section>
);
}