预测历史:比赛信息内嵌到预测API + 实际比分自动填充
后端: - PredictionOut 新增 match 字段(内嵌比赛信息) - list_predictions 返回完整比赛数据(日期/队名/赛果/主客徽标) - 新增 _match_dict() 辅助函数序列化比赛对象 前端: - 移除独立的 fetchMatches 调用,直接使用 p.match - 实际比分自动从比赛赛果填充 - 显示:比赛日(M/D) + 主客队徽标 + 队伍中文名 - 赛后一键结算(使用比赛实际赛果) Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>> )
This commit is contained in:
@@ -8,8 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { fetchPredictions, settlePrediction, fetchMatches } from '../dal'
|
import { fetchPredictions, settlePrediction } from '../dal'
|
||||||
import type { Prediction, Match } from '../types'
|
import type { Prediction } from '../types'
|
||||||
import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner } from '../components'
|
import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner } from '../components'
|
||||||
import TeamSideTag from '../../components/TeamSideTag'
|
import TeamSideTag from '../../components/TeamSideTag'
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ function fmtDate(s?: string | null): string {
|
|||||||
|
|
||||||
export default function PredictionHistoryPage() {
|
export default function PredictionHistoryPage() {
|
||||||
const [predictions, setPredictions] = useState<Prediction[]>([])
|
const [predictions, setPredictions] = useState<Prediction[]>([])
|
||||||
const [matches, setMatches] = useState<Map<number, Match>>(new Map())
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [filter, setFilter] = useState<'all' | 'settled' | 'unsettled'>('all')
|
const [filter, setFilter] = useState<'all' | 'settled' | 'unsettled'>('all')
|
||||||
@@ -34,15 +33,8 @@ export default function PredictionHistoryPage() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
const list = await fetchPredictions(100)
|
const list = await fetchPredictions(200)
|
||||||
setPredictions(list)
|
setPredictions(list)
|
||||||
const matchIds = [...new Set(list.map(p => p.match_id))]
|
|
||||||
if (matchIds.length > 0) {
|
|
||||||
const matchesData = await fetchMatches({ limit: 500 })
|
|
||||||
const matchMap = new Map<number, Match>()
|
|
||||||
matchesData.items.forEach(m => matchMap.set(m.id, m))
|
|
||||||
setMatches(matchMap)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : '加载失败')
|
setError(err instanceof Error ? err.message : '加载失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -53,7 +45,7 @@ export default function PredictionHistoryPage() {
|
|||||||
useEffect(() => { load() }, [load])
|
useEffect(() => { load() }, [load])
|
||||||
|
|
||||||
const handleSettle = async (p: Prediction) => {
|
const handleSettle = async (p: Prediction) => {
|
||||||
const match = matches.get(p.match_id)
|
const match = (p as any).match
|
||||||
if (!match || match.home_goals == null || match.away_goals == null) return
|
if (!match || match.home_goals == null || match.away_goals == null) return
|
||||||
setSettlingId(p.id)
|
setSettlingId(p.id)
|
||||||
try {
|
try {
|
||||||
@@ -162,7 +154,7 @@ export default function PredictionHistoryPage() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filtered.map(p => {
|
{filtered.map(p => {
|
||||||
const match = matches.get(p.match_id)
|
const match = (p as any).match
|
||||||
const matchDate = match?.match_date
|
const matchDate = match?.match_date
|
||||||
const homeName = match?.home_team_zh || match?.home_team || '?'
|
const homeName = match?.home_team_zh || match?.home_team || '?'
|
||||||
const awayName = match?.away_team_zh || match?.away_team || '?'
|
const awayName = match?.away_team_zh || match?.away_team || '?'
|
||||||
@@ -254,7 +246,7 @@ export default function PredictionHistoryPage() {
|
|||||||
{expandedId && (() => {
|
{expandedId && (() => {
|
||||||
const p = predictions.find(pr => pr.id === expandedId)
|
const p = predictions.find(pr => pr.id === expandedId)
|
||||||
if (!p) return null
|
if (!p) return null
|
||||||
const match = matches.get(p.match_id)
|
const match = (p as any).match
|
||||||
const reports = p.agent_outputs ?? []
|
const reports = p.agent_outputs ?? []
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -167,11 +167,31 @@ async def list_predictions(
|
|||||||
actual_home_goals=p.actual_home_goals,
|
actual_home_goals=p.actual_home_goals,
|
||||||
actual_away_goals=p.actual_away_goals,
|
actual_away_goals=p.actual_away_goals,
|
||||||
settled=p.settled,
|
settled=p.settled,
|
||||||
|
match=_match_dict(p.match) if p.match else None,
|
||||||
)
|
)
|
||||||
for p in rows
|
for p in rows
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _match_dict(m) -> dict | None:
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"id": m.id,
|
||||||
|
"league_code": m.league.code if m.league else None,
|
||||||
|
"season": m.season,
|
||||||
|
"home_team": m.home_team.name if m.home_team else "?",
|
||||||
|
"away_team": m.away_team.name if m.away_team else "?",
|
||||||
|
"home_team_zh": m.home_team.name_zh if m.home_team else None,
|
||||||
|
"away_team_zh": m.away_team.name_zh if m.away_team else None,
|
||||||
|
"match_date": m.match_date.isoformat() if m.match_date else None,
|
||||||
|
"match_status": m.match_status,
|
||||||
|
"home_goals": m.home_goals,
|
||||||
|
"away_goals": m.away_goals,
|
||||||
|
"match_stage": m.match_stage,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/predictions/{prediction_id}", response_model=PredictionOut, dependencies=[Depends(require_admin)])
|
@router.get("/predictions/{prediction_id}", response_model=PredictionOut, dependencies=[Depends(require_admin)])
|
||||||
async def get_prediction(prediction_id: int, db: AsyncSession = Depends(get_db_read)):
|
async def get_prediction(prediction_id: int, db: AsyncSession = Depends(get_db_read)):
|
||||||
p = await db.get(Prediction, prediction_id)
|
p = await db.get(Prediction, prediction_id)
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ class PredictionOut(BaseModel):
|
|||||||
actual_home_goals: int | None
|
actual_home_goals: int | None
|
||||||
actual_away_goals: int | None
|
actual_away_goals: int | None
|
||||||
settled: bool
|
settled: bool
|
||||||
|
# 比赛信息(可选,列表接口不返回以减少 payload)
|
||||||
|
match: dict | None = None
|
||||||
|
|
||||||
|
|
||||||
class IngestBzzoiroRequest(BaseModel):
|
class IngestBzzoiroRequest(BaseModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user