Files
Profeto/frontend/src/api/public.ts
T
shangfangjian f71f29b4cb fix(frontend): 按审计报告修复全部17项问题,数据层迁至 src/api/
P0(4): 假数据清除(avg_latency_ms:2400→null,无端点字段改null)、假进度条改诚实的不确定态、
  公共页不再反向依赖 admin(api/public.ts)、PredictProgress 重写
P1(6): 23处 any 归零(对齐后端 Pydantic 契约新增 PredictionMatchRef/HealthProbe 等)、
  a11y(aria-live 0→4,htmlFor 1→17,aria-describedby/invalid 补齐)、App.tsx 抽 SiteLayout、
  路由级 lazy+代码分割(首屏 315KB→238KB)、index.html 补 SEO/favicon/OG、死代码清理(AdminIcon 抽出)
P2(7): Login/index.css 裸色值令牌化、groupByDate useMemo、滚动监听统一、原生控件基元化、
  useLeagues 静默失败补告警、路由级 ErrorBoundary

另修复审计未列问题:
- Monitoring todos 过滤器 t!==false 放行 null 导致整页崩溃 → Boolean(t) 真值过滤
- Collection 渲染期 Date.now()(react-hooks/purity 捕获)→ 计时器 effect
- bg-press-wash/60 透明度修饰符静默失效 → RGB 三元组 + 构建期令牌守卫(下个提交接入)

工程化:数据层 dal/api/types(1047行)git mv 至 src/api/,admin 留 @deprecated 兼容壳,
  21 个引用方直指新路径;tsconfig 开启 noUnusedLocals/noUnusedParameters(清理9处存量)
2026-09-22 23:45:06 +08:00

68 lines
2.5 KiB
TypeScript

/**
* 前台公开端点 —— 预测主站与积分榜所需的最小数据访问。
*
* 为什么要单独成文件(而不是继续挂在 `admin/dal.ts` 里):
*
* 1. **分层方向错了。** 面向公众的页面反向 import `../admin/dal`,
* 意味着想重构后台就会牵连前台,目录名也持续误导人以为
* `admin/` 是可独立删除的包。公开端点的归属本就不该是 admin。
*
* 2. **它真的会进用户的首屏。** `dal.ts` 是单个大模块,内部函数
* 相互引用,打包器无法按调用点做 tree-shaking —— 实测
* Matches 只用 2 个函数,但 `/admin/ingest/jobs`、`/admin/llm/models`、
* `/admin/eval`、`/admin/backtest`、`/admin/logs` 这些字符串全部
* 留在了公开页所在 chunk 里。抽出来后可省下约 55 kB 首屏 JS。
*
* 本文件只放**无需登录即可访问**的端点。任何需要鉴权的端点继续留在
* `./dal.ts`。新增公开端点时请加在这里,不要加回 admin。
*/
import { api, API_BASE } from './api'
import type { MatchDetailOut, MatchContextOut } from './types'
// ── 积分榜 ──────────────────────────────────────────────────────
export interface StandingRow {
position: number
team: string
team_en: string
played: number
won: number
drawn: number
lost: number
goals_for: number
goals_against: number
goal_diff: number
points: number
xg_for: number | null
xg_against: number | null
form: string | null
zone: string | null
}
export interface StandingsLeague {
league_code: string
league_name: string
season: string
retrieved_at: string | null
rows: StandingRow[]
}
export async function fetchStandings(league?: string, season?: string): Promise<{ leagues: StandingsLeague[] }> {
const sp = new URLSearchParams()
if (league) sp.set('league', league)
if (season) sp.set('season', season)
const qs = sp.toString()
return api.get<{ leagues: StandingsLeague[] }>(`${API_BASE}/standings${qs ? `?${qs}` : ''}`)
}
// ── 比赛详情 ────────────────────────────────────────────────────
export function fetchMatchDetail(id: number): Promise<MatchDetailOut> {
return api.get<MatchDetailOut>(`${API_BASE}/matches/${id}`)
}
export function fetchMatchContext(id: number): Promise<MatchContextOut> {
return api.get<MatchContextOut>(`${API_BASE}/matches/${id}/context`)
}