feat: add manual recharge and operation logs

This commit is contained in:
hectorzhao
2026-07-01 15:54:56 +08:00
parent 50d75f1cf3
commit 7ff6d7eac3
14 changed files with 339 additions and 32 deletions
+132
View File
@@ -0,0 +1,132 @@
import { useMemo, useState } from 'react';
import { CalendarDays, Download, FileText, Search } from 'lucide-react';
import { Breadcrumb, Button, Input, Select, Table, Tag, type TableColumn } from '@/components/ui';
type LogLevel = 'info' | 'success' | 'warning' | 'error';
type AdminSystemLog = {
id: string;
time: string;
level: LogLevel;
tenant: string;
module: string;
operator: string;
action: string;
resourceId: string;
detail: string;
ip: string;
};
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',
};
const logsSeed: AdminSystemLog[] = [
{ id: 'SYS202607010001', time: '2026-07-01 13:42:10', level: 'success', tenant: '上海云舟科技有限公司', module: '账户计费', operator: '运营', action: '人工充值', resourceId: 'RCG202607010001', detail: '人工充值 ¥2,000.00,备注:线下转账到账', ip: '10.0.1.12' },
{ id: 'SYS202607010002', time: '2026-07-01 13:20:33', level: 'info', tenant: '杭州星澜商贸有限公司', module: '短信审核', operator: '审核员A', action: '审核通过', resourceId: 'AUD202607010018', detail: '营销短信任务进入发送队列', ip: '10.0.1.15' },
{ id: 'SYS202607010003', time: '2026-07-01 12:58:44', level: 'warning', tenant: '深圳北辰出行服务有限公司', module: '风控', operator: 'system', action: '触发人工审核', resourceId: 'RISK202607010009', detail: '重复号码比例 21.4%,超过阈值 20%', ip: '127.0.0.1' },
{ id: 'SYS202607010004', time: '2026-07-01 12:11:02', level: 'error', tenant: '广州麦芒科技', module: '发送链路', operator: 'gateway', action: 'SubmitResp失败', resourceId: 'MSG-7b9e', detail: '通道返回 REJECT,错误码 8', ip: '10.0.2.21' },
{ id: 'SYS202607010005', time: '2026-07-01 11:46:29', level: 'info', tenant: '平台', module: '系统管理', operator: '平台管理员', action: '创建用户', resourceId: 'USR202607010006', detail: '新增运营用户:report-admin', ip: '10.0.1.10' },
];
export function AdminSystemLogsPage() {
const [keyword, setKeyword] = useState('');
const [level, setLevel] = useState('all');
const [module, setModule] = useState('all');
const [range, setRange] = useState('today');
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.tenant} ${item.operator} ${item.action} ${item.resourceId} ${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 columns = useMemo<Array<TableColumn<AdminSystemLog>>>(() => [
{ key: 'time', title: '时间', width: '180px', render: (record) => <span className="muted">{record.time}</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: '详情', render: (record) => <span className="system-log-detail">{record.detail}</span> },
{ 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>
<div>
<Breadcrumb items={['系统管理', '系统日志']} />
<h1></h1>
</div>
</div>
<Button icon={<Download size={17} />} variant="secondary"></Button>
</div>
<div className="system-log-filters">
<Input
onChange={(event) => setKeyword(event.target.value)}
placeholder="搜索企业、操作人、动作、资源ID或详情"
prefix={<Search size={16} />}
value={keyword}
/>
<Select
onChange={(event) => setLevel(event.target.value)}
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)} 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={filteredLogs} emptyText="暂无系统日志" rowKey="id" />
</div>
</section>
);
}