From dd24c813ab54dc12bcb7610d3c984627633c3c17 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Thu, 5 Feb 2026 09:05:36 +0800 Subject: [PATCH] feat: Implement Dashboard page displaying pool status, statistics, and recent batch runs. --- .../components/dashboard/RecentRecords.tsx | 112 ++++++++++++++---- frontend/src/pages/Dashboard.tsx | 4 +- 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/dashboard/RecentRecords.tsx b/frontend/src/components/dashboard/RecentRecords.tsx index d8b7ba7..1911013 100644 --- a/frontend/src/components/dashboard/RecentRecords.tsx +++ b/frontend/src/components/dashboard/RecentRecords.tsx @@ -1,16 +1,62 @@ -import { Clock, CheckCircle, XCircle } from 'lucide-react' +import { useState, useEffect, useCallback } from 'react' +import { Clock, CheckCircle, RefreshCw, AlertCircle, XCircle } from 'lucide-react' import { Link } from 'react-router-dom' -import type { AddRecord } from '../../types' -import { formatRelativeTime } from '../../utils/format' import { Card, CardHeader, CardTitle, Button } from '../common' -interface RecentRecordsProps { - records: AddRecord[] - loading?: boolean +interface BatchRun { + id: number + started_at: string + finished_at: string + total_owners: number + total_registered: number + total_added_to_s2a: number + success_rate: number + duration_seconds: number + status: string + errors: string } -export default function RecentRecords({ records, loading = false }: RecentRecordsProps) { - const recentRecords = records.slice(0, 5) +// 格式化相对时间 +function formatRelativeTime(dateStr: string): string { + if (!dateStr) return '-' + const date = new Date(dateStr) + const now = new Date() + const diffMs = now.getTime() - date.getTime() + const diffMins = Math.floor(diffMs / 60000) + const diffHours = Math.floor(diffMins / 60) + const diffDays = Math.floor(diffHours / 24) + + if (diffMins < 1) return '刚刚' + if (diffMins < 60) return `${diffMins}分钟前` + if (diffHours < 24) return `${diffHours}小时前` + if (diffDays < 7) return `${diffDays}天前` + return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }) +} + +export default function RecentRecords() { + const [runs, setRuns] = useState([]) + const [loading, setLoading] = useState(true) + + const fetchRuns = useCallback(async () => { + setLoading(true) + try { + const res = await fetch('/api/batch/runs') + if (res.ok) { + const data = await res.json() + if (data.code === 0) { + // 只取最近5条 + setRuns((data.data || []).slice(0, 5)) + } + } + } catch (e) { + console.error('获取批次记录失败:', e) + } + setLoading(false) + }, []) + + useEffect(() => { + fetchRuns() + }, [fetchRuns]) return ( @@ -24,12 +70,12 @@ export default function RecentRecords({ records, loading = false }: RecentRecord {loading ? ( -
+
{[1, 2, 3].map((i) => (
))}
- ) : recentRecords.length === 0 ? ( + ) : runs.length === 0 ? (

暂无加号记录

@@ -38,40 +84,54 @@ export default function RecentRecords({ records, loading = false }: RecentRecord
) : ( -
- {recentRecords.map((record) => ( +
+ {runs.map((run) => (
= 80 + ? 'bg-green-100 dark:bg-green-900/30' + : 'bg-yellow-100 dark:bg-yellow-900/30' + : run.status === 'running' + ? 'bg-blue-100 dark:bg-blue-900/30' + : 'bg-red-100 dark:bg-red-900/30' + }`} > - {record.failed === 0 ? ( - + {run.status === 'completed' ? ( + run.success_rate >= 80 ? ( + + ) : ( + + ) + ) : run.status === 'running' ? ( + ) : ( - + )}

- {record.source === 'manual' ? '手动上传' : '自动补号'} + 批次 #{run.id} + + {run.total_owners} 个母号 +

- {formatRelativeTime(record.timestamp)} + {formatRelativeTime(run.started_at)}

-

- +{record.success} +

+ +{run.total_added_to_s2a} +

+

+ {run.status === 'running' ? '运行中' : `${run.success_rate.toFixed(0)}%`}

- {record.failed > 0 &&

失败 {record.failed}

}
))} diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index cc024d4..377d3c9 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -4,14 +4,12 @@ import { Upload, RefreshCw, Settings, Trash2 } from 'lucide-react' import { PoolStatus, RecentRecords } from '../components/dashboard' import { Card, CardHeader, CardTitle, CardContent, Button } from '../components/common' import { useS2AApi } from '../hooks/useS2AApi' -import { useRecords } from '../hooks/useRecords' import { useConfig } from '../hooks/useConfig' import type { DashboardStats } from '../types' import { formatNumber, formatCurrency } from '../utils/format' export default function Dashboard() { const { getDashboardStats, loading, error, isConnected } = useS2AApi() - const { records } = useRecords() const { config } = useConfig() const [stats, setStats] = useState(null) const [refreshing, setRefreshing] = useState(false) @@ -202,7 +200,7 @@ export default function Dashboard() { )} {/* Recent Records */} - +
) }