From 78375d6e39785938b236313a342b1dddb5d359eb Mon Sep 17 00:00:00 2001 From: shangfangjian Date: Sun, 20 Sep 2026 08:45:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdminLayout: 侧边栏布局和导航修复 - dal.ts: 数据访问层 API 调用修复 - Dashboard: 仪表盘统计卡片和数据加载修复 - Backtest: 回测配置和结果展示修复 - LLMConfig: LLM 配置页面修复 - Predictions: 预测管理页面修复 --- frontend/src/admin/AdminLayout.tsx | 117 ++++++----- frontend/src/admin/dal.ts | 2 +- frontend/src/admin/pages/Backtest.tsx | 22 +-- frontend/src/admin/pages/Dashboard.tsx | 242 ++++++++++++----------- frontend/src/admin/pages/LLMConfig.tsx | 2 +- frontend/src/admin/pages/Predictions.tsx | 20 +- 6 files changed, 223 insertions(+), 182 deletions(-) diff --git a/frontend/src/admin/AdminLayout.tsx b/frontend/src/admin/AdminLayout.tsx index 3712a14..8831a46 100644 --- a/frontend/src/admin/AdminLayout.tsx +++ b/frontend/src/admin/AdminLayout.tsx @@ -11,19 +11,31 @@ import { fetchAuthState, logout, UNAUTHORIZED_EVENT } from './api' import { fetchHealth } from './dal' import Login from './Login' -const NAV_ITEMS = [ - // ── 观测(只读) ── - { to: '/admin', label: '仪表盘', icon: '◇', end: true }, - { to: '/admin/eval', label: '评估', icon: '◈' }, - { to: '/admin/monitoring', label: '监控', icon: '◐' }, - { to: '/admin/logs', label: '日志', icon: '▤' }, - // ── 操作(写入,需登录) ── - { to: '/admin/predictions', label: '预测管理', icon: '◆' }, - { to: '/admin/collection', label: '数据采集', icon: '◈' }, - { to: '/admin/backtest', label: '回测', icon: '◉' }, - { to: '/admin/data-sources', label: '数据源', icon: '◫' }, - { to: '/admin/llm-config', label: 'LLM 配置', icon: '◬' }, - { to: '/admin/config', label: '系统配置', icon: '◑' }, +const NAV_SECTIONS: { title: string; items: { to: string; label: string; icon: string; end?: boolean }[] }[] = [ + { + title: '数据流水线', + items: [ + { to: '/admin/collection', label: '数据采集', icon: '◈' }, + { to: '/admin/predictions', label: '预测管理', icon: '◆' }, + { to: '/admin/backtest', label: '回测', icon: '◉' }, + ], + }, + { + title: '评估与监控', + items: [ + { to: '/admin/eval', label: '评估', icon: '◈' }, + { to: '/admin/monitoring', label: '监控', icon: '◐' }, + { to: '/admin/logs', label: '日志', icon: '▤' }, + ], + }, + { + title: '系统设置', + items: [ + { to: '/admin/data-sources', label: '数据源', icon: '◫' }, + { to: '/admin/llm-config', label: 'LLM 配置', icon: '◬' }, + { to: '/admin/config', label: '系统配置', icon: '◑' }, + ], + }, ] /** 报眉日期行,与前台同款式 */ @@ -139,45 +151,65 @@ export default function AdminLayout() { ADMIN - {/* 导航:选中项以印报红方块标记,同前台胜平负选中样式 */} - {/* 底部 */}
返回前台版面 @@ -212,13 +244,6 @@ export default function AdminLayout() { /> {healthOk === null ? '检测中' : healthOk ? '系统正常' : '系统异常'} - - 前台 -
- +
@@ -281,7 +279,7 @@ export default function BacktestPage() { {summary.degraded > 0 && (
- 有 {summary.degraded} 场预测降级(专家无有效结论),未计入准确率分子。建议检查该时段数据完整性或改用单次模式。 + 有 {summary.degraded} 场预测降级(专家无有效结论),未计入准确率分子。建议检查该时段数据完整性。
)} diff --git a/frontend/src/admin/pages/Dashboard.tsx b/frontend/src/admin/pages/Dashboard.tsx index 6654790..5309138 100644 --- a/frontend/src/admin/pages/Dashboard.tsx +++ b/frontend/src/admin/pages/Dashboard.tsx @@ -1,141 +1,161 @@ /** * Admin 后台 - 仪表盘(报刊风) * - * 响应式: 移动端 1 列 → 平板 2 列 → 桌面 4 列 + * 展示: + * - 数据流水线状态(采集 → 预测 → 评估,每步的实际数据量) + * - 近期预测活动(24h / 7d / 总计) + * - 快捷操作入口(带工作流引导) */ -import { useEffect, useState } from 'react' -import { fetchDashboard, fetchHealth } from '../dal' -import type { DashboardStats } from '../types' -import { Card, CardBody, CardHeader, StatCard, Alert, SkeletonBlock } from '../components' +import { useEffect, useState, useCallback } from 'react' +import { fetchAdminStats, fetchIngestStatus, fetchDashboard } from '../dal' +import type { AdminStats, IngestSourceStatus, DashboardStats } from '../types' +import { Card, CardBody, CardHeader, Alert, SkeletonBlock } from '../components' + +/** 工作流步骤卡片 */ +const STEPS = [ + { to: '/admin/collection', step: '1', title: '采集数据', desc: '从 bzzoiro / understat 获取赛程与 xG', icon: '◈' }, + { to: '/admin/predictions', step: '2', title: '运行预测', desc: '调 LLM 多专家生成比分预测', icon: '◆' }, + { to: '/admin/eval', step: '3', title: '评估准确率', desc: '结算后查看 1X2 命中率与校准度', icon: '◈' }, +] export default function Dashboard() { - const [data, setData] = useState(null) - const [health, setHealth] = useState(null) + const [stats, setStats] = useState(null) + const [ingest, setIngest] = useState([]) + const [dash, setDash] = useState(null) const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - useEffect(() => { - let active = true + const load = useCallback(async () => { setLoading(true) - Promise.all([fetchDashboard(), fetchHealth()]) - .then(([stats, h]) => { - if (active) { - setData(stats) - setHealth(h) - } - }) - .catch((err: unknown) => { - if (active) setError(err instanceof Error ? err.message : '加载失败') - }) - .finally(() => { - if (active) setLoading(false) - }) - return () => { active = false } + const [s, i, d] = await Promise.allSettled([ + fetchAdminStats(), + fetchIngestStatus(), + fetchDashboard(), + ]) + if (s.status === 'fulfilled') setStats(s.value) + if (i.status === 'fulfilled') setIngest(i.value.sources) + if (d.status === 'fulfilled') setDash(d.value) + setLoading(false) }, []) - if (error) { - return ( -
- - -
- ) - } + useEffect(() => { load() }, [load]) - const healthText = - health?.status === 'healthy' || health?.status === 'ok' ? '正常' : '异常' + const sourceByName = Object.fromEntries(ingest.map(s => [s.name, s])) + const bzzoiro = sourceByName['bzzoiro'] + const understat = sourceByName['understat'] + const injuries = sourceByName['injuries'] return (
- {/* 统计:报纸数字版式 */} -
- - - - + {/* ── 工作流引导(采集 → 预测 → 评估) ── */} + - {/* 联赛列表 */} + {/* ── 数据源健康一览 ── */} - - + + {loading ? ( -
- {[1, 2, 3, 4].map(i => ( - - ))} +
+ {[1, 2, 3].map(i => )}
- ) : data && data.leagues.length > 0 ? ( + ) : ( +
+ {[ + { name: 'bzzoiro', label: 'Bzzoiro', st: bzzoiro }, + { name: 'understat', label: 'Understat (xG)', st: understat }, + { name: 'injuries', label: 'Injuries (伤停)', st: injuries }, + ].map(({ name, label, st }) => { + const hasData = st && st.recent_count > 0 + const keyOk = st?.key_configured !== false + return ( +
+ {label} + + {hasData ? ( + <> + {st.recent_count.toLocaleString()} 条 + {st.last_success_at ? new Date(st.last_success_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }) : ''} + + ) : keyOk ? ( + 无数据 + ) : ( + 未配置 Key + )} + +
+ ) + })} +
+ )} + + + + {/* ── 近期预测活动 ── */} + + + + {stats ? ( +
+
+
{stats.predictions.last_24h}
+
近 24 小时
+
+
+
{stats.predictions.last_7d}
+
近 7 天
+
+
+
{stats.predictions.total}
+
累计
+
+
+ ) : ( +
+ )} +
+
+ + {/* ── 已入库联赛 ── */} + {dash && dash.leagues.length > 0 && ( + + +
- {data.leagues.map(l => ( - + {dash.leagues.map(l => ( + {l.name_zh || l.name} {l.code} ))}
- ) : ( -

- 暂无联赛数据,请先到「数据采集」导入比赛数据。 -

- )} -
-
- - {/* 快捷操作 */} - - - - - - + + + )}
) } diff --git a/frontend/src/admin/pages/LLMConfig.tsx b/frontend/src/admin/pages/LLMConfig.tsx index f3f00de..a61ed77 100644 --- a/frontend/src/admin/pages/LLMConfig.tsx +++ b/frontend/src/admin/pages/LLMConfig.tsx @@ -181,7 +181,7 @@ export default function LLMConfigPage() { {testing ? (<> 测试中) : '测试 LLM 连接'}

- 测试会真实调用一次单次模式预测,产生 LLM 费用。 + 测试会真实调用一次 LLM 预测,产生费用。

diff --git a/frontend/src/admin/pages/Predictions.tsx b/frontend/src/admin/pages/Predictions.tsx index 8baae00..ed717c4 100644 --- a/frontend/src/admin/pages/Predictions.tsx +++ b/frontend/src/admin/pages/Predictions.tsx @@ -37,7 +37,7 @@ export default function PredictionsPage() { const [matches, setMatches] = useState([]) const [predictions, setPredictions] = useState([]) const [matchId, setMatchId] = useState('') - const [mode, setMode] = useState<'single' | 'multi'>('multi') + const [mode] = useState<'multi'>('multi') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [successMsg, setSuccessMsg] = useState(null) @@ -142,7 +142,7 @@ export default function PredictionsPage() { {matches.map(m => ( ))} @@ -150,14 +150,12 @@ export default function PredictionsPage() {
- +
{error && setError(null)} />} @@ -305,7 +303,7 @@ export default function PredictionsPage() {

- {p.mode === 'multi' ? `多专家 · ${okAgents.length}/${p.agent_outputs?.length ?? 0} 路有效` : '单次模式'} + `多专家 · ${okAgents.length}/${p.agent_outputs?.length ?? 0} 路有效` {p.model && {p.model}}