+
+
管理员管理
+ } onClick={handleAdd}>
+ 添加管理员
+
+
+
+
+ `共 ${total} 条记录`,
+ onChange: (page, size) => {
+ setCurrentPage(page)
+ setPageSize(size || 10)
+ fetchAdmins(page, size || 10)
+ },
+ }}
+ />
+
+
+ setModalVisible(false)}
+ destroyOnClose
+ >
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+ {editingAdmin && (
+
+
+
+ )}
+
+
+
+ setResetModalVisible(false)}
+ destroyOnClose
+ >
+
+
+
+ ({
+ validator(_, value) {
+ if (!value || getFieldValue('new_password') === value) {
+ return Promise.resolve()
+ }
+ return Promise.reject(new Error('两次输入的密码不一致'))
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+ )
+}
+
+export default AdminManagement
diff --git a/src/pages/OrderManagement/index.tsx b/src/pages/OrderManagement/index.tsx
index a56eaa56..86955d7e 100644
--- a/src/pages/OrderManagement/index.tsx
+++ b/src/pages/OrderManagement/index.tsx
@@ -22,8 +22,9 @@ import {
CloseCircleOutlined,
PrinterOutlined,
FileTextOutlined,
+ DownloadOutlined,
} from '@ant-design/icons'
-import { getOrderList, getOrderDetail, updateOrderStatus } from '@/api/admin'
+import { getOrderList, getOrderDetail, updateOrderStatus, exportOrderList } from '@/api/admin'
import type { Order } from '@/types/api'
import './index.css'
@@ -101,6 +102,31 @@ const OrderManagement = () => {
}
}
+ const handleExport = async () => {
+ try {
+ const params: any = {}
+ if (statusFilter) {
+ params.status = statusFilter
+ }
+ if (dateRange) {
+ params.start_date = dateRange[0]
+ params.end_date = dateRange[1]
+ }
+ const blob = await exportOrderList(params)
+ const url = window.URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `订单列表_${new Date().toISOString().split('T')[0]}.xlsx`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ window.URL.revokeObjectURL(url)
+ message.success('导出成功')
+ } catch (error) {
+ message.error('导出失败')
+ }
+ }
+
const getStatusTag = (status: string) => {
const statusMap: Record = {
pending: { color: 'orange', text: '待支付', icon: },
@@ -264,6 +290,9 @@ const OrderManagement = () => {
} onClick={handleSearch}>
搜索
+ } onClick={handleExport}>
+ 导出
+
diff --git a/src/pages/Statistics/index.tsx b/src/pages/Statistics/index.tsx
index 4c8055ac..725b4748 100644
--- a/src/pages/Statistics/index.tsx
+++ b/src/pages/Statistics/index.tsx
@@ -19,8 +19,9 @@ import {
FallOutlined,
CalendarOutlined,
ReloadOutlined,
+ DownloadOutlined,
} from '@ant-design/icons'
-import { getStatistics, getDailyStatistics } from '@/api/admin'
+import { getStatistics, getDailyStatistics, exportStatistics } from '@/api/admin'
import type { StatisticsData } from '@/types/api'
import './index.css'
@@ -55,6 +56,27 @@ const Statistics = () => {
fetchStatistics()
}, [])
+ const handleExport = async () => {
+ try {
+ const params: any = {}
+ if (dateRange) {
+ params.start_date = dateRange[0]
+ params.end_date = dateRange[1]
+ }
+ const blob = await exportStatistics(params)
+ const url = window.URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `统计数据_${new Date().toISOString().split('T')[0]}.xlsx`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ window.URL.revokeObjectURL(url)
+ } catch (error) {
+ console.error('Failed to export statistics:', error)
+ }
+ }
+
const weeklyColumns = [
{
title: '日期',
@@ -137,6 +159,9 @@ const Statistics = () => {
} onClick={fetchStatistics}>
刷新
+ } onClick={handleExport}>
+ 导出
+
diff --git a/src/pages/SystemLogs/index.css b/src/pages/SystemLogs/index.css
new file mode 100644
index 00000000..555bb872
--- /dev/null
+++ b/src/pages/SystemLogs/index.css
@@ -0,0 +1,20 @@
+.system-logs {
+ padding: 0;
+}
+
+.page-header {
+ margin-bottom: 24px;
+}
+
+.page-header h4 {
+ margin-bottom: 0;
+}
+
+.filter-card {
+ margin-bottom: 16px;
+ border-radius: 12px;
+}
+
+.table-card {
+ border-radius: 12px;
+}
diff --git a/src/pages/SystemLogs/index.tsx b/src/pages/SystemLogs/index.tsx
new file mode 100644
index 00000000..328f614e
--- /dev/null
+++ b/src/pages/SystemLogs/index.tsx
@@ -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([])
+ const [total, setTotal] = useState(0)
+ const [currentPage, setCurrentPage] = useState(1)
+ const [pageSize, setPageSize] = useState(20)
+ const [levelFilter, setLevelFilter] = useState('')
+ const [moduleFilter, setModuleFilter] = useState('')
+ 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 = {
+ info: { color: 'blue', icon: , text: '信息' },
+ warning: { color: 'orange', icon: , text: '警告' },
+ error: { color: 'red', icon: , text: '错误' },
+ debug: { color: 'default', icon: , text: '调试' },
+ }
+ const config = levelMap[level] || { color: 'default', icon: null, text: level }
+ return (
+
+ {config.text}
+
+ )
+ }
+
+ 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) => {text},
+ },
+ {
+ title: '消息',
+ dataIndex: 'message',
+ key: 'message',
+ ellipsis: true,
+ },
+ {
+ title: '用户ID',
+ dataIndex: 'user_id',
+ key: 'user_id',
+ width: 150,
+ render: (text: string) => text ? {text?.substring(0, 8)}... : '-',
+ },
+ {
+ title: 'IP地址',
+ dataIndex: 'ip',
+ key: 'ip',
+ width: 140,
+ render: (text: string) => text || '-',
+ },
+ ]
+
+ return (
+
+
+
系统日志
+
+
+
+
+
+
+ {
+ if (dates) {
+ setDateRange([
+ dates[0]?.format('YYYY-MM-DD HH:mm:ss') || '',
+ dates[1]?.format('YYYY-MM-DD HH:mm:ss') || '',
+ ])
+ } else {
+ setDateRange(null)
+ }
+ }}
+ />
+ } onClick={handleSearch}>
+ 搜索
+
+ } onClick={() => fetchLogs()}>
+ 刷新
+
+
+
+
+
+ `共 ${total} 条记录`,
+ onChange: (page, size) => {
+ setCurrentPage(page)
+ setPageSize(size || 20)
+ fetchLogs(page, size || 20)
+ },
+ }}
+ />
+
+
+ )
+}
+
+export default SystemLogs
diff --git a/src/types/api.ts b/src/types/api.ts
index ed9532eb..a5d11ee0 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -112,3 +112,54 @@ export interface ApiResponse {
message: string
data: T
}
+
+// 系统日志
+export interface SystemLog {
+ id: string
+ level: 'info' | 'warning' | 'error' | 'debug'
+ message: string
+ module: string
+ created_at: string
+ user_id?: string
+ ip?: string
+}
+
+export interface SystemLogListResponse {
+ logs: SystemLog[]
+ total: number
+}
+
+// 管理员账号
+export interface Admin {
+ id: string
+ username: string
+ nickname: string
+ role: 'super' | 'admin' | 'operator'
+ status: 'active' | 'inactive'
+ created_at: string
+ updated_at: string
+}
+
+export interface AdminListResponse {
+ admins: Admin[]
+ total: number
+}
+
+export interface CreateAdminRequest {
+ username: string
+ password: string
+ nickname: string
+ role: 'super' | 'admin' | 'operator'
+}
+
+export interface UpdateAdminRequest {
+ id: string
+ nickname?: string
+ role?: 'super' | 'admin' | 'operator'
+ status?: 'active' | 'inactive'
+}
+
+export interface ResetAdminPasswordRequest {
+ id: string
+ new_password: string
+}