UI/UX 全面改进:22项优化落地(报纸风前端 + 管理后台)

P0 严重问题:
- 采集任务进度反馈:Collection 页新增任务状态跟踪(运行中/完成/失败) + 轮询 ingest 状态
- 积分榜加载态:切换联赛显示 spinner + 禁用 tab 防重复点击
- 数据完整性可操作:问题列表每项加「去修复」按钮,跳转采集页并预填参数
- 面包屑导航:顶部显示 仪表盘 > 当前页

P1 重要问题:
- 侧边栏当前页指示器:1.5px 圆点 → 4px 左侧彩色竖条 + 背景高亮
- Key Ring 重置确认:点击「重置冷却」前弹窗确认
- 预测比赛选择器:只显示未开赛 + 联赛筛选 + 显示可预测数量
- 表格移动端溢出:评估页表格加 overflow-x-auto
- 设置页合并:数据源 + LLM + 系统配置 → 统一「设置」页
- 预测按钮去重:移动端/桌面端合并为一个响应式按钮

P2 体验优化:
- 日志滚动保持:自动滚动仅当用户未向上滚动时
- 评估结果预览:显示「共 N 组」
- ⌘K 命令面板:键盘导航跳转所有管理页面
- 专家意见折叠:默认折叠,可展开(已存在)
- 版本信息:侧边栏底部显示 v1.0
- 积分榜表格:min-w 防止挤压

Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
shangfangjian
2026-09-20 21:24:21 +08:00
co-authored by new-provider/LongCat-2.0 <
parent dd538d66d7
commit fb27f6e2d2
13 changed files with 854 additions and 107 deletions
+57 -17
View File
@@ -10,6 +10,32 @@ import { NavLink, Outlet, useLocation } from 'react-router-dom'
import { fetchAuthState, logout, UNAUTHORIZED_EVENT } from './api'
import { fetchHealth } from './dal'
import Login from './Login'
import { useCommandPalette, CommandPalette } from './useCommandPalette'
const NAV_PAGES: Array<{ to: string; label: string; group: string }> = [
{ to: '/admin', label: '仪表盘', group: '概览' },
{ to: '/admin/collection', label: '数据采集', group: '数据流水线' },
{ to: '/admin/data-completeness', label: '数据完整性', group: '数据流水线' },
{ to: '/admin/predictions', label: '预测管理', group: '数据流水线' },
{ to: '/admin/backtest', label: '回测', group: '数据流水线' },
{ to: '/admin/monitoring', label: '监控', group: '评估与监控' },
{ to: '/admin/eval', label: '评估', group: '评估与监控' },
{ to: '/admin/settings', label: '设置', group: '系统' },
{ to: '/admin/logs', label: '日志', group: '系统' },
]
// 路由 → 面包屑标签
const ROUTE_LABELS: Record<string, string> = {
'/admin': '仪表盘',
'/admin/collection': '数据采集',
'/admin/data-completeness': '数据完整性',
'/admin/predictions': '预测管理',
'/admin/backtest': '回测',
'/admin/monitoring': '监控',
'/admin/settings': '设置',
'/admin/logs': '日志',
'/admin/eval': '评估',
}
const NAV_SECTIONS: { title: string; items: { to: string; label: string; icon: string; end?: boolean }[] }[] = [
{
@@ -32,9 +58,8 @@ const NAV_SECTIONS: { title: string; items: { to: string; label: string; icon: s
{
title: '系统设置',
items: [
{ to: '/admin/data-sources', label: '数据源', icon: '' },
{ to: '/admin/llm-config', label: 'LLM 配置', icon: '' },
{ to: '/admin/config', label: '系统配置', icon: '◑' },
{ to: '/admin/settings', label: '设置', icon: '' },
{ to: '/admin/logs', label: '日志', icon: '' },
],
},
]
@@ -54,6 +79,7 @@ export default function AdminLayout() {
const [healthOk, setHealthOk] = useState<boolean | null>(null)
const [authed, setAuthed] = useState<boolean | null>(null)
const location = useLocation()
const palette = useCommandPalette(NAV_PAGES)
// 登录门禁:挂载时探测会话,收到 401 事件(会话过期)自动切回登录页
useEffect(() => {
@@ -159,10 +185,10 @@ export default function AdminLayout() {
to="/admin"
end
className={({ isActive }) =>
`flex min-h-[40px] items-center gap-2.5 px-3 text-sm transition-colors ${
`flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 text-sm transition-colors ${
isActive
? 'bg-press-wash/60 font-medium text-press'
: 'text-ink-500 hover:bg-paper-100 hover:text-ink-900'
? 'border-press bg-press-wash/60 font-medium text-press'
: 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900'
}`
}
>
@@ -181,19 +207,13 @@ export default function AdminLayout() {
<NavLink
to={item.to}
className={({ isActive }) =>
`flex min-h-[40px] items-center gap-2.5 px-3 text-sm transition-colors ${
`flex min-h-[40px] items-center gap-2.5 border-l-4 px-3 text-sm transition-colors ${
isActive
? 'bg-press-wash/60 font-medium text-press'
: 'text-ink-500 hover:bg-paper-100 hover:text-ink-900'
? 'border-press bg-press-wash/60 font-medium text-press'
: 'border-transparent text-ink-500 hover:bg-paper-100 hover:text-ink-900'
}`
}
>
<span
className={`inline-block h-1.5 w-1.5 flex-shrink-0 ${
location.pathname.startsWith(item.to) ? 'bg-press' : 'bg-transparent'
}`}
aria-hidden="true"
/>
<span className="text-base leading-none opacity-50" aria-hidden="true">
{item.icon}
</span>
@@ -207,7 +227,7 @@ export default function AdminLayout() {
</nav>
{/* 底部 */}
<div className="border-t border-ink-200 px-4 py-3">
<div className="border-t border-ink-200 px-4 py-3 space-y-2">
<a
href="/"
className="flex min-h-[36px] items-center gap-2 text-xs text-ink-500 transition-colors hover:text-press"
@@ -215,12 +235,13 @@ export default function AdminLayout() {
<span aria-hidden="true"></span>
</a>
<p className="text-2xs text-ink-300">Profeto v1.0</p>
</div>
</aside>
{/* ── 主内容区 ── */}
<div className="flex flex-1 flex-col overflow-hidden">
{/* 报眉:日期 + 系统状态 */}
{/* 报眉:面包屑 + 日期 + 系统状态 */}
<header className="flex h-11 flex-shrink-0 items-center justify-between border-b border-ink-200 px-4 lg:px-6">
<div className="flex items-center gap-4">
<button
@@ -232,6 +253,16 @@ export default function AdminLayout() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
{/* 面包屑 */}
<nav className="hidden items-center gap-1.5 text-2xs text-ink-400 sm:flex" aria-label="面包屑">
<a href="/admin" className="hover:text-ink-700"></a>
{location.pathname !== '/admin' && (
<>
<span aria-hidden="true">/</span>
<span className="text-ink-600">{ROUTE_LABELS[location.pathname] ?? '未知页面'}</span>
</>
)}
</nav>
<span className="hidden text-2xs text-ink-500 sm:inline">{dateLine()}</span>
</div>
@@ -262,6 +293,15 @@ export default function AdminLayout() {
</div>
</main>
</div>
{/* 命令面板(⌘K) */}
<CommandPalette
open={palette.open}
query={palette.query}
setQuery={palette.setQuery}
items={palette.items}
onClose={() => palette.setOpen(false)}
/>
</div>
)
}