feat: 管理界面报刊风统一改造 — 移除独立暗色主题,预测/回测/监控/配置页增强
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Admin 后台 - 仪表盘
|
||||
* Admin 后台 - 仪表盘(报刊风)
|
||||
*
|
||||
* 响应式布局: 移动端 1 列 → 平板 2 列 → 桌面 4 列
|
||||
* 响应式: 移动端 1 列 → 平板 2 列 → 桌面 4 列
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { fetchDashboard, fetchHealth } from '../dal'
|
||||
import type { DashboardStats } from '../types'
|
||||
import { Card, CardBody, CardHeader, StatCard, Badge } from '../components'
|
||||
import { Card, CardBody, CardHeader, StatCard, Alert, SkeletonBlock } from '../components'
|
||||
|
||||
export default function Dashboard() {
|
||||
const [data, setData] = useState<DashboardStats | null>(null)
|
||||
@@ -36,65 +36,58 @@ export default function Dashboard() {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="rounded-lg border border-red-500/30 bg-red-500/10 p-6 text-center text-red-400">
|
||||
<p className="text-lg font-medium">加载仪表盘失败</p>
|
||||
<p className="mt-1 text-sm">{error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-3 rounded-md bg-red-500/20 px-4 py-2 text-sm hover:bg-red-500/30 min-h-[44px]"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Alert kind="error" title="加载仪表盘失败" message={error} />
|
||||
<button onClick={() => window.location.reload()} className="btn btn-sm">
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const healthText =
|
||||
health?.status === 'healthy' || health?.status === 'ok' ? '正常' : '异常'
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 统计卡片: 移动端 1 列 → 平板 2 列 → 桌面 4 列 */}
|
||||
{/* 统计:报纸数字版式 */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<StatCard
|
||||
label="健康状态"
|
||||
value={loading ? '—' : (health?.status === 'healthy' ? '✅ 正常' : '⚠️ 异常')}
|
||||
icon="●"
|
||||
/>
|
||||
<StatCard
|
||||
label="联赛数"
|
||||
value={loading ? '—' : data?.leagues.length ?? 0}
|
||||
icon="◫"
|
||||
/>
|
||||
<StatCard
|
||||
label="比赛数"
|
||||
value={loading ? '—' : data?.total_matches ?? 0}
|
||||
icon="◆"
|
||||
/>
|
||||
<StatCard
|
||||
label="预测数"
|
||||
value={loading ? '—' : data?.total_predictions ?? 0}
|
||||
icon="◇"
|
||||
value={loading ? '—' : healthText}
|
||||
hint={loading ? undefined : '每 60 秒随报眉自动复检'}
|
||||
/>
|
||||
<StatCard label="联赛数" value={loading ? '—' : data?.leagues.length ?? 0} />
|
||||
<StatCard label="比赛数" value={loading ? '—' : data?.total_matches ?? 0} />
|
||||
<StatCard label="预测数" value={loading ? '—' : data?.total_predictions ?? 0} />
|
||||
</div>
|
||||
|
||||
{/* 联赛列表 */}
|
||||
<Card>
|
||||
<CardHeader title="最近联赛" />
|
||||
<CardHeader title="已入库联赛" />
|
||||
<CardBody>
|
||||
{loading ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[1, 2, 3, 4].map(i => (
|
||||
<div key={i} className="h-6 w-20 animate-pulse rounded bg-gray-800" />
|
||||
<SkeletonBlock key={i} className="h-6 w-24" />
|
||||
))}
|
||||
</div>
|
||||
) : data && data.leagues.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{data.leagues.map(l => (
|
||||
<Badge key={l.code} status="info">
|
||||
{l.name_zh || l.name} ({l.code})
|
||||
</Badge>
|
||||
<span
|
||||
key={l.code}
|
||||
className="border border-ink-200 px-2.5 py-1 text-xs text-ink-700"
|
||||
>
|
||||
{l.name_zh || l.name}
|
||||
<span className="ml-1.5 font-mono text-2xs text-ink-400">{l.code}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-400">暂无联赛数据,请先触发采集</p>
|
||||
<p className="text-xs text-ink-500">
|
||||
暂无联赛数据,请先到「数据采集」导入比赛数据。
|
||||
</p>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -106,33 +99,39 @@ export default function Dashboard() {
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<a
|
||||
href="/admin/collection"
|
||||
className="flex items-center gap-3 rounded-lg border border-gray-800 p-4 transition-colors hover:border-gray-700 hover:bg-gray-800/50"
|
||||
className="group flex items-center justify-between border border-ink-300 p-4 transition-colors hover:border-ink-900 hover:bg-paper-100"
|
||||
>
|
||||
<span className="text-xl">◈</span>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-200">触发采集</div>
|
||||
<div className="text-xs text-gray-500">从数据源获取最新数据</div>
|
||||
<div className="font-serif text-sm font-bold text-ink-900">触发采集</div>
|
||||
<div className="mt-0.5 text-2xs text-ink-500">从数据源获取最新赛程与比分</div>
|
||||
</div>
|
||||
<span className="text-ink-300 transition-colors group-hover:text-press" aria-hidden="true">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="/admin/predictions"
|
||||
className="flex items-center gap-3 rounded-lg border border-gray-800 p-4 transition-colors hover:border-gray-700 hover:bg-gray-800/50"
|
||||
className="group flex items-center justify-between border border-ink-300 p-4 transition-colors hover:border-ink-900 hover:bg-paper-100"
|
||||
>
|
||||
<span className="text-xl">◆</span>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-200">新建预测</div>
|
||||
<div className="text-xs text-gray-500">使用 LLM 预测比赛结果</div>
|
||||
<div className="font-serif text-sm font-bold text-ink-900">新建预测</div>
|
||||
<div className="mt-0.5 text-2xs text-ink-500">调 LLM 生成比赛预测</div>
|
||||
</div>
|
||||
<span className="text-ink-300 transition-colors group-hover:text-press" aria-hidden="true">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="/admin/backtest"
|
||||
className="flex items-center gap-3 rounded-lg border border-gray-800 p-4 transition-colors hover:border-gray-700 hover:bg-gray-800/50"
|
||||
className="group flex items-center justify-between border border-ink-300 p-4 transition-colors hover:border-ink-900 hover:bg-paper-100"
|
||||
>
|
||||
<span className="text-xl">◉</span>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-200">运行回测</div>
|
||||
<div className="text-xs text-gray-500">在历史数据上验证策略</div>
|
||||
<div className="font-serif text-sm font-bold text-ink-900">运行回测</div>
|
||||
<div className="mt-0.5 text-2xs text-ink-500">在历史数据上检验准确率</div>
|
||||
</div>
|
||||
<span className="text-ink-300 transition-colors group-hover:text-press" aria-hidden="true">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</CardBody>
|
||||
|
||||
Reference in New Issue
Block a user