feat: 添加导出功能、系统日志和管理员管理页面
This commit is contained in:
218
src/pages/SystemLogs/index.tsx
Normal file
218
src/pages/SystemLogs/index.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
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
|
||||
Reference in New Issue
Block a user