fix: connect operations pages to real backend

This commit is contained in:
hectorzhao
2026-07-02 15:16:34 +08:00
parent ab421cf8a7
commit 321cf716f2
22 changed files with 1255 additions and 429 deletions
+28 -41
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { CalendarDays, Download, FileText, Search } from 'lucide-react';
import {
Button,
@@ -9,20 +9,10 @@ import {
Tag,
type TableColumn,
} from '@/components/ui';
import { clientApi, type OperationLogItem } from '@/api/adminApi';
type LogLevel = 'info' | 'success' | 'warning' | 'error';
type SystemLog = {
id: string;
time: string;
level: LogLevel;
module: string;
operator: string;
action: string;
detail: string;
ip: string;
};
const levelLabelMap: Record<LogLevel, string> = {
info: '信息',
success: '成功',
@@ -37,18 +27,6 @@ const levelToneMap: Record<LogLevel, 'info' | 'success' | 'warning' | 'danger'>
error: 'danger',
};
const logsSeed: SystemLog[] = [
{ id: 'LOG001', time: '2026-03-17 14:35:22', level: 'info', module: '用户管理', operator: '张三', action: '创建用户', detail: '创建用户账号:李四(lisi@example.com', ip: '192.168.1.100' },
{ id: 'LOG002', time: '2026-03-17 14:20:15', level: 'success', module: '短信服务', operator: '李四', action: '发送短信', detail: '批量发送短信至500个号码,发送成功', ip: '192.168.1.101' },
{ id: 'LOG003', time: '2026-03-17 13:45:33', level: 'warning', module: '彩信服务', operator: '王五', action: '模板审核', detail: '彩信模板“春节祝福”审核未通过,原因:内容包含敏感词', ip: '192.168.1.102' },
{ id: 'LOG004', time: '2026-03-17 12:10:08', level: 'error', module: '系统管理', operator: '赵六', action: '登录失败', detail: '用户登录失败,错误:密码错误(连续3次)', ip: '192.168.1.103' },
{ id: 'LOG005', time: '2026-03-17 11:30:45', level: 'info', module: '用户管理', operator: '张三', action: '修改权限', detail: '修改用户“孙七”的角色:普通用户 → 管理员', ip: '192.168.1.100' },
{ id: 'LOG006', time: '2026-03-17 10:15:20', level: 'success', module: '短信服务', operator: '李四', action: '签名审核', detail: '短信签名“优品商城”审核通过', ip: '192.168.1.101' },
{ id: 'LOG007', time: '2026-03-17 09:50:12', level: 'info', module: '彩信服务', operator: '王五', action: '创建模板', detail: '创建彩信模板“新品发布”(模板IDMMS_1a2b3c4d', ip: '192.168.1.102' },
{ id: 'LOG008', time: '2026-03-17 09:05:33', level: 'error', module: '短信服务', operator: '李四', action: '发送失败', detail: '短信发送失败,错误:余额不足', ip: '192.168.1.101' },
{ id: 'LOG009', time: '2026-03-17 08:40:18', level: 'warning', module: '系统管理', operator: 'system', action: '系统告警', detail: '系统磁盘使用率超过80%,当前使用率:85%', ip: '127.0.0.1' },
];
export function ClientSystemLogsPage() {
const [keyword, setKeyword] = useState('');
const [level, setLevel] = useState('all');
@@ -56,30 +34,39 @@ export function ClientSystemLogsPage() {
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(() => {
clientApi.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(() => {
const modules = Array.from(new Set(logsSeed.map((item) => item.module)));
return [{ label: '全部模块', value: 'all' }, ...modules.map((item) => ({ label: item, value: item }))];
}, []);
const filteredLogs = logsSeed.filter((item) => {
const target = `${item.operator} ${item.action} ${item.detail}`;
const matchesKeyword = !keyword || target.toLowerCase().includes(keyword.toLowerCase());
const matchesLevel = level === 'all' || item.level === level;
const matchesModule = module === 'all' || item.module === module;
return matchesKeyword && matchesLevel && matchesModule;
});
const totalPages = Math.max(1, Math.ceil(filteredLogs.length / pageSize));
}, [modules]);
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const currentPage = Math.min(page, totalPages);
const pagedLogs = filteredLogs.slice((currentPage - 1) * pageSize, currentPage * pageSize);
const columns = useMemo<Array<TableColumn<SystemLog>>>(() => [
{ key: 'time', title: '时间', width: '190px', render: (record) => <span className="muted">{record.time}</span> },
const columns = useMemo<Array<TableColumn<OperationLogItem>>>(() => [
{ key: 'time', title: '时间', width: '190px', render: (record) => <span className="muted">{new Date(record.time).toLocaleString('zh-CN')}</span> },
{ key: 'level', title: '级别', width: '110px', render: (record) => <Tag tone={levelToneMap[record.level]}>{levelLabelMap[record.level]}</Tag> },
{ key: 'module', title: '模块', width: '150px', render: (record) => <strong>{record.module}</strong> },
{ key: 'operator', title: '操作人', width: '130px', render: (record) => <strong>{record.operator}</strong> },
{ key: 'action', title: '操作', width: '160px', render: (record) => <strong>{record.action}</strong> },
{ key: 'detail', title: '详情', render: (record) => <span className="system-log-detail">{record.detail}</span> },
{ key: 'detail', title: '详情', render: (record) => <span className="system-log-detail">{JSON.stringify(record.detail)}</span> },
{ key: 'ip', title: 'IP地址', width: '150px', render: (record) => <span className="muted">{record.ip}</span> },
], []);
@@ -134,14 +121,14 @@ export function ClientSystemLogsPage() {
</div>
<div className="surface system-table-card">
<Table columns={columns} data={pagedLogs} emptyText="暂无系统日志" rowKey="id" />
<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={filteredLogs.length}
total={total}
/>
</div>
</section>