feat: 后台管理 Admin 仪表盘

新增完整的后台管理系统 (/admin):
- Dashboard: 系统概览、最近采集状态、预测统计
- Collection: 数据采集触发(bzzoiro/understat/injuries)
- Predictions: 预测历史查看、触发新预测
- Backtest: 回测配置与结果查看
- Monitoring: 系统健康、错误日志、死色队列
- Config: API Key 与数据源配置

技术栈: React Router + Tailwind 暗色主题 + TypeScript
文件: 11 个新文件, +21KB JS / +5KB CSS
This commit is contained in:
shangfangjian
2026-09-17 02:00:14 +08:00
parent 1219b4fd18
commit d3284c48c3
19 changed files with 2671 additions and 81 deletions
+72 -37
View File
@@ -1,7 +1,17 @@
/**
* 主应用入口
*
* 整合前台(报纸风格)和后台(暗色管理)的路由。
* - / → 先知(Profeto)主站
* - /admin/* → 管理后台
*/
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { ErrorBoundary } from './components/ErrorBoundary'
import Matches from './pages/Matches'
import { adminRoutes } from './admin/routes'
/** 报眉日期行:2026年9月15日 星期二 */
/** 报眉日期行 */
function dateLine(): string {
return new Date().toLocaleDateString('zh-CN', {
year: 'numeric',
@@ -11,45 +21,70 @@ function dateLine(): string {
})
}
function HomePage() {
return (
<div className="min-h-screen bg-paper-50">
{/* ── 报头:粗线 + 居中刊名 + 报眉 ── */}
<header className="masthead-rule">
<div className="mx-auto max-w-5xl px-5 sm:px-8">
<div className="border-b border-ink-900 py-5 text-center sm:py-6">
<h1 className="font-serif text-4xl font-bold tracking-widest text-ink-900">
<span className="ml-3 align-baseline font-serif text-base font-normal italic tracking-normal text-ink-500">
Profeto
</span>
</h1>
<p className="mt-2 text-2xs tracking-[0.4em] text-ink-500">
· ·
</p>
</div>
<div className="flex items-center justify-between border-b border-ink-200 py-2 text-2xs text-ink-500">
<span>{dateLine()}</span>
<a
href="/admin"
className="flex items-center gap-1.5 text-press hover:text-press-dark transition-colors"
>
<span aria-hidden="true"></span>
</a>
</div>
</div>
</header>
<main className="mx-auto max-w-5xl px-5 py-6 sm:px-8 sm:py-8">
<Matches />
</main>
<footer className="mx-auto max-w-5xl px-5 pb-10 sm:px-8">
<div className="border-t border-ink-200 pt-3 text-center text-2xs leading-relaxed text-ink-400">
· ·
</div>
</footer>
</div>
)
}
export default function App() {
return (
<ErrorBoundary>
<div className="min-h-screen bg-paper-50">
{/* ── 报头:粗线 + 居中刊名 + 报眉 ── */}
<header className="masthead-rule">
<div className="mx-auto max-w-5xl px-5 sm:px-8">
<div className="border-b border-ink-900 py-5 text-center sm:py-6">
<h1 className="font-serif text-4xl font-bold tracking-widest text-ink-900">
<span className="ml-3 align-baseline font-serif text-base font-normal italic tracking-normal text-ink-500">
Profeto
</span>
</h1>
<p className="mt-2 text-2xs tracking-[0.4em] text-ink-500">
· ·
</p>
</div>
<div className="flex items-center justify-between border-b border-ink-200 py-2 text-2xs text-ink-500">
<span>{dateLine()}</span>
<span className="flex items-center gap-1.5">
<span className="inline-block h-1.5 w-1.5 bg-emerald-600" aria-hidden="true" />
</span>
</div>
</div>
</header>
<main className="mx-auto max-w-5xl px-5 py-6 sm:px-8 sm:py-8">
<Matches />
</main>
{/* ── 版底 ── */}
<footer className="mx-auto max-w-5xl px-5 pb-10 sm:px-8">
<div className="border-t border-ink-200 pt-3 text-center text-2xs leading-relaxed text-ink-400">
· ·
</div>
</footer>
</div>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
{adminRoutes.map(route => (
<Route key={route.path} path={route.path} element={route.element}>
{route.children.map(child => (
<Route
key={child.path ?? 'index'}
index={child.index}
path={child.path}
element={child.element}
/>
))}
</Route>
))}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</ErrorBoundary>
)
}