/** * Admin 后台 - 数据采集页面 * * 功能: * - 选择数据源(bzzoiro / understat / injuries) * - 选择联赛、日期范围 * - 触发采集任务 * - 实时显示任务进度 */ import { useEffect, useState, useCallback } from 'react' import { triggerCollection, fetchCollectionTasks, fetchLeagues } from '../dal' import type { CollectionRequest, CollectionTask, League } from '../types' import { Card, CardBody, CardHeader, Badge, ProgressBar, EmptyState } from '../components' import { SectionHeader } from '../components' const SOURCES = [ { value: 'bzzoiro', label: 'Bzzoiro', desc: '历史赛程与比分数据' }, { value: 'understat', label: 'Understat', desc: '进阶统计数据(xG/xA)' }, { value: 'injuries', label: 'Injuries', desc: '球员伤停信息' }, ] as const export default function CollectionPage() { const [leagues, setLeagues] = useState([]) const [tasks, setTasks] = useState([]) const [source, setSource] = useState('bzzoiro') const [leagueCode, setLeagueCode] = useState('') const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [successMsg, setSuccessMsg] = useState(null) // 加载联赛列表和任务历史 const loadData = useCallback(async () => { const [lg, ts] = await Promise.all([fetchLeagues(), fetchCollectionTasks()]) setLeagues(lg) setTasks(ts) }, []) useEffect(() => { loadData() // 自动刷新任务状态(每 5 秒) const timer = setInterval(loadData, 5000) return () => clearInterval(timer) }, [loadData]) async function handleSubmit(e: React.FormEvent) { e.preventDefault() setError(null) setSuccessMsg(null) setLoading(true) try { const body: CollectionRequest = { source: source as CollectionRequest['source'], league_code: leagueCode || undefined, date_from: dateFrom || undefined, date_to: dateTo || undefined, } const res = await triggerCollection(body) setSuccessMsg(`任务已创建: ${res.message}`) loadData() } catch (err: unknown) { setError(err instanceof Error ? err.message : '采集触发失败') } finally { setLoading(false) } } return (
{/* ── 采集配置表单 ── */}
{/* 数据源 */}
{SOURCES.map(s => ( ))}
{/* 联赛 */}
{/* 日期范围 */}
setDateFrom(e.target.value)} className="w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-blue-500 focus:outline-none" />
setDateTo(e.target.value)} className="w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-blue-500 focus:outline-none" />
{/* 提示消息 */} {error && (
{error}
)} {successMsg && (
{successMsg}
)} {/* 提交 */}
{/* ── 任务列表 ── */}
{tasks.length === 0 ? ( ) : (
{tasks.map(task => ( ))}
)}
) } function TaskRow({ task }: { task: CollectionTask }) { return (
{task.source} {task.status}
{task.processed}/{task.total}
{task.status === 'running' && (
)} {task.error_message && (

{task.error_message}

)} {task.finished_at && (

完成于 {new Date(task.finished_at).toLocaleString('zh-CN')}

)}
) }