perf+unify: matches 列表查询优化 + Standings 统一数据源

matches 列表:selectinload 加 load_only 限定列(League.code/Team.name,name_zh/
MatchStats.home_xg,away_xg),补 stats 加载消除 N+1(m.stats 此前懒加载)。
详情接口保持完整 options;游标分页/响应字段/空值语义不变。

Standings 改用 useLeagues() 统一数据源(API 优先,失败回退本地常量),
LEAGUES 常量扩展 CL/EL;无数据联赛显示虚线 tab + 空态(非隐藏)。
This commit is contained in:
shangfangjian
2026-09-22 01:35:06 +08:00
parent 92f50fa5d4
commit a00364d4a7
3 changed files with 45 additions and 32 deletions
+27 -28
View File
@@ -8,18 +8,9 @@
import { useEffect, useState, useCallback } from 'react'
import { fetchStandings } from '../admin/dal'
import type { StandingsLeague, StandingRow } from '../admin/dal'
import { useLeagues } from './matches/hooks/useLeagues'
import { Spinner } from '../admin/components'
const LEAGUES = [
{ code: 'E0', name: '英超' },
{ code: 'SP1', name: '西甲' },
{ code: 'D1', name: '德甲' },
{ code: 'I1', name: '意甲' },
{ code: 'F1', name: '法甲' },
{ code: 'CL', name: '欧冠' },
{ code: 'EL', name: '欧联' },
]
const ZONE_META: Record<string, { label: string; cls: string }> = {
// 欧战资格
'Champions League': { label: '欧冠区', cls: 'bg-emerald-100 text-emerald-700' },
@@ -64,7 +55,9 @@ function FormDots({ form }: { form?: string | null }) {
}
export default function StandingsPage() {
const [leagues, setLeagues] = useState<StandingsLeague[]>([])
// 统一数据源:复用 useLeagues hook(优先 API,失败回退本地常量)
const leagues = useLeagues()
const [standings, setStandings] = useState<StandingsLeague[]>([])
const [activeLeague, setActiveLeague] = useState<string>('')
const [loading, setLoading] = useState(true)
const [switching, setSwitching] = useState(false) // 切换联赛中
@@ -85,7 +78,7 @@ export default function StandingsPage() {
setError(null)
try {
const data = await fetchStandings(code)
setLeagues(data.leagues)
setStandings(data.leagues)
if (!activeLeague && data.leagues.length > 0) {
setActiveLeague(data.leagues[0].league_code)
}
@@ -104,7 +97,7 @@ export default function StandingsPage() {
setSwitching(true)
setActiveLeague(code)
try {
await fetchStandings(code).then(data => setLeagues(data.leagues))
await fetchStandings(code).then(data => setStandings(data.leagues))
} catch (err) {
setError(err instanceof Error ? err.message : '加载失败')
} finally {
@@ -112,26 +105,32 @@ export default function StandingsPage() {
}
}
const active = leagues.find(l => l.league_code === activeLeague) ?? leagues[0]
const active = standings.find(l => l.league_code === activeLeague) ?? standings[0]
return (
<div className="space-y-6">
{/* 联赛切换 */}
<div className="flex flex-wrap gap-2">
{LEAGUES.map(l => (
<button
key={l.code}
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'
}`}
>
{l.name}
</button>
))}
{leagues.map(l => {
// 标记该联赛是否有积分榜数据:有数据可正常切换,无数据也可选中但显示空态
const hasData = standings.some(s => s.league_code === l.code)
const isEmpty = activeLeague === l.code && !hasData
return (
<button
key={l.code}
onClick={() => switchLeague(l.code)}
disabled={switching}
title={hasData ? undefined : '暂无积分榜数据'}
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'
} ${!hasData ? 'border-dashed' : ''}`}
>
{l.name}
</button>
)
})}
</div>
{error && (
+2
View File
@@ -78,6 +78,8 @@ export const LEAGUES = [
{ code: 'D1', name: '德甲' },
{ code: 'I1', name: '意甲' },
{ code: 'F1', name: '法甲' },
{ code: 'CL', name: '欧冠' },
{ code: 'EL', name: '欧联' },
]
/** 汉字编号,给专家意见排版用 */