diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 7e5666a..65f3b51 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -10,6 +10,7 @@ import { StaticData } from './pages/admin/StaticData'; import { Users } from './pages/admin/Users'; import { NASADownload } from './pages/admin/NASADownload'; import { SystemSettings } from './pages/admin/SystemSettings'; +import { Tasks } from './pages/admin/Tasks'; import { auth } from './utils/auth'; import { ToastProvider } from './contexts/ToastContext'; import App from './App'; @@ -48,6 +49,7 @@ export function Router() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/pages/admin/NASADownload.tsx b/frontend/src/pages/admin/NASADownload.tsx index bba2ce0..fa3f26b 100644 --- a/frontend/src/pages/admin/NASADownload.tsx +++ b/frontend/src/pages/admin/NASADownload.tsx @@ -179,22 +179,31 @@ export function NASADownload() { setDownloadProgress({ current: 0, total: datesToDownload.length }); try { - const { data } = await request.post('/celestial/positions/download', { - body_ids: selectedBodies, - dates: datesToDownload - }); + if (selectedDate) { + // Sync download for single date + const { data } = await request.post('/celestial/positions/download', { + body_ids: selectedBodies, + dates: datesToDownload + }); - setDownloadProgress({ current: datesToDownload.length, total: datesToDownload.length }); + setDownloadProgress({ current: datesToDownload.length, total: datesToDownload.length }); - if (data.total_success > 0) { - message.success(`成功下载 ${data.total_success} 条数据${data.total_failed > 0 ? `,${data.total_failed} 条失败` : ''}`); - // Reload available dates to show newly downloaded data - loadAvailableDates(); + if (data.total_success > 0) { + message.success(`成功下载 ${data.total_success} 条数据${data.total_failed > 0 ? `,${data.total_failed} 条失败` : ''}`); + loadAvailableDates(); + } else { + message.error('下载失败'); + } } else { - message.error('下载失败'); + // Async download for range + await request.post('/celestial/positions/download-async', { + body_ids: selectedBodies, + dates: datesToDownload + }); + message.success('后台下载任务已启动,请前往“系统任务”查看进度'); } } catch (error) { - message.error('下载请求失败'); + message.error('请求失败'); } finally { setDownloading(false); setDownloadProgress({ current: 0, total: 0 }); @@ -331,7 +340,7 @@ export function NASADownload() { disabled={selectedBodies.length === 0} loading={downloading} > - 下载范围内所有日期 + 下载范围内数据 (后台任务) } diff --git a/frontend/src/pages/admin/Tasks.tsx b/frontend/src/pages/admin/Tasks.tsx new file mode 100644 index 0000000..19cb3b6 --- /dev/null +++ b/frontend/src/pages/admin/Tasks.tsx @@ -0,0 +1,176 @@ +import { useState, useEffect, useRef } from 'react'; +import { Tag, Progress, Button, Modal, Descriptions, Badge, Typography } from 'antd'; +import { ReloadOutlined, EyeOutlined } from '@ant-design/icons'; +import type { ColumnsType } from 'antd/es/table'; +import { DataTable } from '../../components/admin/DataTable'; +import { request } from '../../utils/request'; + +const { Text } = Typography; + +interface Task { + id: number; + task_type: string; + status: string; + progress: number; + description: string; + created_at: string; + started_at?: string; + completed_at?: string; + error_message?: string; + result?: any; +} + +export function Tasks() { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [currentTask, setCurrentTask] = useState(null); + const [detailsVisible, setDetailsVisible] = useState(false); + + // Auto-refresh logic + const timerRef = useRef | null>(null); + + const loadData = async () => { + setLoading(true); + try { + const res = await request.get('/celestial/tasks?limit=50'); + setData(res.data); + } finally { + setLoading(false); + } + }; + + // Polling for active tasks + useEffect(() => { + loadData(); + + timerRef.current = setInterval(() => { + // Silent refresh to avoid table flickering + request.get('/celestial/tasks?limit=50').then(res => { + setData(res.data); + }); + }, 3000); + + return () => { + if (timerRef.current) clearInterval(timerRef.current); + }; + }, []); + + const columns: ColumnsType = [ + { + title: 'ID', + dataIndex: 'id', + width: 80, + }, + { + title: '任务类型', + dataIndex: 'task_type', + width: 150, + render: (type: string) => {type} + }, + { + title: '描述', + dataIndex: 'description', + ellipsis: true, + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + render: (status: string) => { + const colors: Record = { + pending: 'default', + running: 'processing', + completed: 'success', + failed: 'error', + cancelled: 'warning' + }; + return ; + } + }, + { + title: '进度', + dataIndex: 'progress', + width: 200, + render: (progress: number, record: Task) => ( + + ) + }, + { + title: '创建时间', + dataIndex: 'created_at', + width: 180, + render: (time: string) => new Date(time).toLocaleString() + }, + { + title: '操作', + key: 'action', + width: 100, + fixed: 'right', + render: (_, record) => ( + + ) + } + ]; + + return ( +
+
+ +
+ + + + setDetailsVisible(false)} + footer={null} + width={800} + > + {currentTask && ( + + {currentTask.id} + {currentTask.task_type} + + + + {currentTask.description} + {currentTask.error_message && ( + + {currentTask.error_message} + + )} + +
+ {currentTask.result ? ( +
{JSON.stringify(currentTask.result, null, 2)}
+ ) : ( + 暂无结果 + )} +
+
+
+ )} +
+
+ ); +}