219 lines
5.8 KiB
TypeScript
219 lines
5.8 KiB
TypeScript
|
|
import { useEffect, useState } from 'react'
|
||
|
|
import {
|
||
|
|
Table,
|
||
|
|
Card,
|
||
|
|
Select,
|
||
|
|
DatePicker,
|
||
|
|
Button,
|
||
|
|
Tag,
|
||
|
|
Typography,
|
||
|
|
Space,
|
||
|
|
Input,
|
||
|
|
} from 'antd'
|
||
|
|
import {
|
||
|
|
SearchOutlined,
|
||
|
|
ReloadOutlined,
|
||
|
|
InfoCircleOutlined,
|
||
|
|
WarningOutlined,
|
||
|
|
CloseCircleOutlined,
|
||
|
|
BugOutlined,
|
||
|
|
} from '@ant-design/icons'
|
||
|
|
import { getSystemLogs } from '@/api/admin'
|
||
|
|
import type { SystemLog } from '@/types/api'
|
||
|
|
import './index.css'
|
||
|
|
|
||
|
|
const { Title } = Typography
|
||
|
|
const { RangePicker } = DatePicker
|
||
|
|
const { Option } = Select
|
||
|
|
|
||
|
|
const SystemLogs = () => {
|
||
|
|
const [loading, setLoading] = useState(false)
|
||
|
|
const [logs, setLogs] = useState<SystemLog[]>([])
|
||
|
|
const [total, setTotal] = useState(0)
|
||
|
|
const [currentPage, setCurrentPage] = useState(1)
|
||
|
|
const [pageSize, setPageSize] = useState(20)
|
||
|
|
const [levelFilter, setLevelFilter] = useState<string>('')
|
||
|
|
const [moduleFilter, setModuleFilter] = useState<string>('')
|
||
|
|
const [dateRange, setDateRange] = useState<[string, string] | null>(null)
|
||
|
|
|
||
|
|
const fetchLogs = async (page = currentPage, size = pageSize) => {
|
||
|
|
setLoading(true)
|
||
|
|
try {
|
||
|
|
const params: any = {
|
||
|
|
page,
|
||
|
|
page_size: size,
|
||
|
|
}
|
||
|
|
if (levelFilter) {
|
||
|
|
params.level = levelFilter
|
||
|
|
}
|
||
|
|
if (moduleFilter) {
|
||
|
|
params.module = moduleFilter
|
||
|
|
}
|
||
|
|
if (dateRange) {
|
||
|
|
params.start_date = dateRange[0]
|
||
|
|
params.end_date = dateRange[1]
|
||
|
|
}
|
||
|
|
|
||
|
|
const res = await getSystemLogs(params)
|
||
|
|
if (res.data) {
|
||
|
|
setLogs(res.data.logs)
|
||
|
|
setTotal(res.data.total)
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to fetch logs:', error)
|
||
|
|
} finally {
|
||
|
|
setLoading(false)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchLogs()
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
const handleSearch = () => {
|
||
|
|
setCurrentPage(1)
|
||
|
|
fetchLogs(1, pageSize)
|
||
|
|
}
|
||
|
|
|
||
|
|
const getLevelTag = (level: string) => {
|
||
|
|
const levelMap: Record<string, { color: string; icon: React.ReactNode; text: string }> = {
|
||
|
|
info: { color: 'blue', icon: <InfoCircleOutlined />, text: '信息' },
|
||
|
|
warning: { color: 'orange', icon: <WarningOutlined />, text: '警告' },
|
||
|
|
error: { color: 'red', icon: <CloseCircleOutlined />, text: '错误' },
|
||
|
|
debug: { color: 'default', icon: <BugOutlined />, text: '调试' },
|
||
|
|
}
|
||
|
|
const config = levelMap[level] || { color: 'default', icon: null, text: level }
|
||
|
|
return (
|
||
|
|
<Tag color={config.color} icon={config.icon}>
|
||
|
|
{config.text}
|
||
|
|
</Tag>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const columns = [
|
||
|
|
{
|
||
|
|
title: '时间',
|
||
|
|
dataIndex: 'created_at',
|
||
|
|
key: 'created_at',
|
||
|
|
width: 180,
|
||
|
|
render: (text: string) => new Date(text).toLocaleString('zh-CN'),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '级别',
|
||
|
|
dataIndex: 'level',
|
||
|
|
key: 'level',
|
||
|
|
width: 100,
|
||
|
|
render: (level: string) => getLevelTag(level),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '模块',
|
||
|
|
dataIndex: 'module',
|
||
|
|
key: 'module',
|
||
|
|
width: 120,
|
||
|
|
render: (text: string) => <Tag>{text}</Tag>,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '消息',
|
||
|
|
dataIndex: 'message',
|
||
|
|
key: 'message',
|
||
|
|
ellipsis: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '用户ID',
|
||
|
|
dataIndex: 'user_id',
|
||
|
|
key: 'user_id',
|
||
|
|
width: 150,
|
||
|
|
render: (text: string) => text ? <Tag>{text?.substring(0, 8)}...</Tag> : '-',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'IP地址',
|
||
|
|
dataIndex: 'ip',
|
||
|
|
key: 'ip',
|
||
|
|
width: 140,
|
||
|
|
render: (text: string) => text || '-',
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="system-logs">
|
||
|
|
<div className="page-header">
|
||
|
|
<Title level={4}>系统日志</Title>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<Card className="filter-card" bordered={false}>
|
||
|
|
<Space wrap>
|
||
|
|
<Select
|
||
|
|
placeholder="日志级别"
|
||
|
|
value={levelFilter || undefined}
|
||
|
|
onChange={setLevelFilter}
|
||
|
|
style={{ width: 120 }}
|
||
|
|
allowClear
|
||
|
|
>
|
||
|
|
<Option value="info">信息</Option>
|
||
|
|
<Option value="warning">警告</Option>
|
||
|
|
<Option value="error">错误</Option>
|
||
|
|
<Option value="debug">调试</Option>
|
||
|
|
</Select>
|
||
|
|
<Select
|
||
|
|
placeholder="模块"
|
||
|
|
value={moduleFilter || undefined}
|
||
|
|
onChange={setModuleFilter}
|
||
|
|
style={{ width: 150 }}
|
||
|
|
allowClear
|
||
|
|
>
|
||
|
|
<Option value="auth">认证</Option>
|
||
|
|
<Option value="order">订单</Option>
|
||
|
|
<Option value="file">文件</Option>
|
||
|
|
<Option value="print">打印</Option>
|
||
|
|
<Option value="pay">支付</Option>
|
||
|
|
<Option value="admin">管理</Option>
|
||
|
|
</Select>
|
||
|
|
<RangePicker
|
||
|
|
showTime
|
||
|
|
onChange={(dates) => {
|
||
|
|
if (dates) {
|
||
|
|
setDateRange([
|
||
|
|
dates[0]?.format('YYYY-MM-DD HH:mm:ss') || '',
|
||
|
|
dates[1]?.format('YYYY-MM-DD HH:mm:ss') || '',
|
||
|
|
])
|
||
|
|
} else {
|
||
|
|
setDateRange(null)
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
||
|
|
搜索
|
||
|
|
</Button>
|
||
|
|
<Button icon={<ReloadOutlined />} onClick={() => fetchLogs()}>
|
||
|
|
刷新
|
||
|
|
</Button>
|
||
|
|
</Space>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
<Card className="table-card" bordered={false}>
|
||
|
|
<Table
|
||
|
|
columns={columns}
|
||
|
|
dataSource={logs}
|
||
|
|
rowKey="id"
|
||
|
|
loading={loading}
|
||
|
|
pagination={{
|
||
|
|
current: currentPage,
|
||
|
|
pageSize: pageSize,
|
||
|
|
total: total,
|
||
|
|
showSizeChanger: true,
|
||
|
|
showQuickJumper: true,
|
||
|
|
showTotal: (total) => `共 ${total} 条记录`,
|
||
|
|
onChange: (page, size) => {
|
||
|
|
setCurrentPage(page)
|
||
|
|
setPageSize(size || 20)
|
||
|
|
fetchLogs(page, size || 20)
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default SystemLogs
|