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:
co-authored by
new-provider/LongCat-2.0 <
parent
dd538d66d7
commit
fb27f6e2d2
@@ -8,6 +8,7 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { fetchStandings } from '../admin/dal'
|
||||
import type { StandingsLeague, StandingRow } from '../admin/dal'
|
||||
import { Spinner } from '../admin/components'
|
||||
|
||||
const LEAGUES = [
|
||||
{ code: 'E0', name: '英超' },
|
||||
@@ -48,6 +49,7 @@ export default function StandingsPage() {
|
||||
const [leagues, setLeagues] = useState<StandingsLeague[]>([])
|
||||
const [activeLeague, setActiveLeague] = useState<string>('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [switching, setSwitching] = useState(false) // 切换联赛中
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const load = useCallback(async (code?: string) => {
|
||||
@@ -68,6 +70,20 @@ export default function StandingsPage() {
|
||||
|
||||
useEffect(() => { load() }, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// 切换联赛(带加载态,禁用 tab 防重复点击)
|
||||
const switchLeague = async (code: string) => {
|
||||
if (code === activeLeague || switching) return
|
||||
setSwitching(true)
|
||||
setActiveLeague(code)
|
||||
try {
|
||||
await fetchStandings(code).then(data => setLeagues(data.leagues))
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '加载失败')
|
||||
} finally {
|
||||
setSwitching(false)
|
||||
}
|
||||
}
|
||||
|
||||
const active = leagues.find(l => l.league_code === activeLeague) ?? leagues[0]
|
||||
|
||||
return (
|
||||
@@ -77,8 +93,9 @@ export default function StandingsPage() {
|
||||
{LEAGUES.map(l => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => { setActiveLeague(l.code); load(l.code) }}
|
||||
className={`rounded border px-3 py-1.5 text-xs transition-colors ${
|
||||
onClick={() => switchLeague(l.code)}
|
||||
disabled={switching}
|
||||
className={`rounded border px-3 py-1.5 text-xs transition-colors disabled:opacity-50 ${
|
||||
activeLeague === l.code
|
||||
? 'border-ink-900 bg-ink-900 text-paper-50'
|
||||
: 'border-ink-200 text-ink-500 hover:border-ink-300'
|
||||
@@ -99,6 +116,13 @@ export default function StandingsPage() {
|
||||
<div className="flex justify-center py-12 text-xs text-ink-400">加载中…</div>
|
||||
)}
|
||||
|
||||
{/* 切换联赛时的轻量加载指示 */}
|
||||
{switching && !loading && (
|
||||
<div className="flex items-center gap-2 border-b border-ink-200 px-1 py-2 text-xs text-ink-400">
|
||||
<Spinner /> 切换联赛中…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !active && (
|
||||
<div className="border-y border-ink-200 py-12 text-center">
|
||||
<p className="font-serif text-sm text-ink-600">暂无积分榜数据</p>
|
||||
@@ -124,7 +148,7 @@ export default function StandingsPage() {
|
||||
|
||||
{/* 积分榜表格 */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<table className="w-full min-w-[640px] text-xs">
|
||||
<thead>
|
||||
<tr className="border-b border-ink-200 text-left text-ink-400">
|
||||
<th className="w-8 py-2 font-medium">#</th>
|
||||
|
||||
Reference in New Issue
Block a user