From 4b14178574ff637245ea29fea43bfdca84198e10 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 18:01:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M4-05=20=E2=80=94=20reporting=20admin?= =?UTF-8?q?=20page=20with=20CSV=20download=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReportingAdmin page: download user/learning/review CSV with day range selector Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 2 + src/config/menu.tsx | 1 + src/pages/ReportingAdmin.tsx | 72 ++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/pages/ReportingAdmin.tsx diff --git a/src/App.tsx b/src/App.tsx index e7503bb..d9a8f8c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,6 +43,7 @@ const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin')) const CacheAdmin = lazy(() => import('./pages/CacheAdmin')) const LearningData = lazy(() => import('./pages/LearningData')) const BackupAdmin = lazy(() => import('./pages/BackupAdmin')) +const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin')) const queryClient = new QueryClient() @@ -180,6 +181,7 @@ function App() { }>} /> }>} /> }>} /> + }>} /> }>} /> } /> diff --git a/src/config/menu.tsx b/src/config/menu.tsx index 8df465b..37f6daa 100644 --- a/src/config/menu.tsx +++ b/src/config/menu.tsx @@ -27,6 +27,7 @@ export const adminMenuItems: AdminMenuItem[] = [ ]}, { path: '/imports', name: '文档导入', icon: }, { path: '/files', name: '文件与 COS', icon: }, + { path: '/reporting', name: '报表导出', icon: , requiredRole: 'ADMIN' }, { path: '/audit', name: '审计日志', icon: , requiredRole: 'ADMIN' }, { path: '/billing', name: 'API 用量', icon: , requiredRole: 'SUPER_ADMIN' }, { path: '/git', name: '代码仓库', icon: }, diff --git a/src/pages/ReportingAdmin.tsx b/src/pages/ReportingAdmin.tsx new file mode 100644 index 0000000..ab2574d --- /dev/null +++ b/src/pages/ReportingAdmin.tsx @@ -0,0 +1,72 @@ +import { Card, Button, Space, Typography, Table, message, Select } from 'antd' +import { DownloadOutlined } from '@ant-design/icons' +import { useQuery } from '@tanstack/react-query' +import { api } from '@/services/http-client' +import { useState } from 'react' + +const { Title } = Typography + +function downloadBlob(data: string, filename: string) { + const blob = new Blob(['' + data], { type: 'text/csv;charset=utf-8' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url; a.download = filename + a.click(); URL.revokeObjectURL(url) +} + +export default function ReportingAdmin() { + const [days, setDays] = useState(30) + + const { data: jobs } = useQuery({ + queryKey: ['admin', 'export-jobs'], + queryFn: () => api.get('/admin-api/reporting/jobs'), + }) + + const download = async (type: string) => { + try { + const res = await fetch(`/admin-api/reporting/export/${type}?days=${days}`, { + headers: { Authorization: `Bearer ${localStorage.getItem('accessToken') || ''}` }, + }) + if (!res.ok) throw new Error('下载失败') + const text = await res.text() + downloadBlob(text, `${type}-report-${days}d.csv`) + } catch { + message.error('导出失败') + } + } + + const jobColumns = [ + { title: 'ID', dataIndex: 'id', width: 100, ellipsis: true }, + { title: '类型', dataIndex: 'type', width: 80 }, + { title: '状态', dataIndex: 'status', width: 80 }, + { title: '格式', dataIndex: 'format', width: 60 }, + { title: '文件大小', dataIndex: 'fileSize', width: 90, render: (s: number) => s ? `${(s / 1024).toFixed(1)} KB` : '-' }, + { title: '创建时间', dataIndex: 'createdAt', width: 120, render: (d: string) => new Date(d).toLocaleString() }, + ] + + return ( +
+ 报表与导出 + + + + 时间范围: +