From 236701c302d110d6cc52e495371efd8a30d93c98 Mon Sep 17 00:00:00 2001 From: yuchenglong Date: Thu, 15 Jan 2026 09:44:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=9B=B4=E6=92=AD=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E8=B5=94=E7=8E=87=E8=87=AA=E5=8A=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B5=94?= =?UTF-8?q?=E7=8E=87=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/live-detail/[id].tsx | 26 ++++- components/live-detail/odds-card.tsx | 136 ++++++++++++++++++++++----- types/api.ts | 22 ++++- 3 files changed, 155 insertions(+), 29 deletions(-) diff --git a/app/live-detail/[id].tsx b/app/live-detail/[id].tsx index 6b7938e..6219dcd 100644 --- a/app/live-detail/[id].tsx +++ b/app/live-detail/[id].tsx @@ -40,11 +40,26 @@ export default function LiveDetailScreen() { useEffect(() => { loadLiveDetail(); + // 设置每 15 秒更新一次直播比分 + const timer = setInterval(() => { + refreshLiveDetail(); + }, 15000); + return () => clearInterval(timer); }, [id, league_id]); const loadLiveDetail = async () => { try { setLoading(true); + await refreshLiveDetail(); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }; + + const refreshLiveDetail = async () => { + try { // Fetch live scores for the league const sportId = parseInt(sport_id || "1"); const leagueId = parseInt(league_id || "0"); @@ -59,9 +74,7 @@ export default function LiveDetailScreen() { } } } catch (err) { - console.error(err); - } finally { - setLoading(false); + console.error("Refresh live detail error:", err); } }; @@ -85,15 +98,18 @@ export default function LiveDetailScreen() { } const renderTabContent = () => { + const numericSportId = parseInt(sport_id || "1"); switch (activeTab) { case "stats": return ; case "odds": - return ; + return ( + + ); case "detail": return ( <> - + diff --git a/components/live-detail/odds-card.tsx b/components/live-detail/odds-card.tsx index 503d912..74c42f0 100644 --- a/components/live-detail/odds-card.tsx +++ b/components/live-detail/odds-card.tsx @@ -1,23 +1,116 @@ import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; -import { LiveScoreMatch } from "@/types/api"; -import React from "react"; +import { fetchOdds } from "@/lib/api"; +import { LiveScoreMatch, OddsItem } from "@/types/api"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { StyleSheet, View } from "react-native"; +import { ActivityIndicator, StyleSheet, View } from "react-native"; interface OddsCardProps { match: LiveScoreMatch; isDark: boolean; + sportId: number; } -export function OddsCard({ match, isDark }: OddsCardProps) { +export function OddsCard({ match, isDark, sportId }: OddsCardProps) { const { t } = useTranslation(); + const [odds, setOdds] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + loadOdds(); + // 设置每 30 秒自动更新一次 + const interval = setInterval(loadOdds, 30000); + return () => clearInterval(interval); + }, [match.event_key, sportId]); + + const loadOdds = async () => { + try { + const data = await fetchOdds(sportId, match.event_key); + const matchOdds = data[match.event_key.toString()]; + if (matchOdds && matchOdds.data && matchOdds.data.length > 0) { + // 优先选择包含常见赔率项的博彩公司,这里暂取第一个 + setOdds(matchOdds.data[0]); + } + } catch (error) { + console.error("Load odds error:", error); + } finally { + setLoading(false); + } + }; + // 提取队名缩写或前3个字母 const homeAbbr = match.event_home_team?.substring(0, 3).toUpperCase() || "HOME"; const awayAbbr = match.event_away_team?.substring(0, 3).toUpperCase() || "AWAY"; + // 获取显示的盘口和赔率 + const renderOddsContent = () => { + if (loading && !odds) { + return ( + + + + ); + } + + // 尝试寻找亚洲盘口 (AH) 或者 主客和 (1X2) + // 这里为了演示演示,优先寻找 AH 相关数据 + const ahKey1 = Object.keys(odds || {}).find( + (k) => k.startsWith("ah") && k.endsWith("_1") + ); + const ahKey2 = ahKey1 ? ahKey1.replace("_1", "_2") : null; + + if (odds && ahKey1 && ahKey2) { + const handicapValue = ahKey1.replace("ah", "").replace("_1", ""); + return ( + + + {homeAbbr} + {odds[ahKey1] || "-"} + + + HDP + {handicapValue} + + + {awayAbbr} + {odds[ahKey2] || "-"} + + + ); + } + + // 如果没有 AH,显示 1X2 + if (odds && odds.odd_1 && odds.odd_2) { + return ( + + + {homeAbbr} + {odds.odd_1} + + + DRAW + {odds.odd_x || "-"} + + + {awayAbbr} + {odds.odd_2} + + + ); + } + + return ( + + + {t("detail.empty_odds")} + + + ); + }; + return ( @@ -25,24 +118,13 @@ export function OddsCard({ match, isDark }: OddsCardProps) { {t("detail.odds_card.title")} - bet365 + + {odds?.odd_bookmakers || "bet365"} + - - - {homeAbbr} - -0.93 - - - HDP - 0/0.5 - - - {awayAbbr} - 0.72 - - + {renderOddsContent()} {t("detail.odds_card.disclaimer")} @@ -105,12 +187,12 @@ const styles = StyleSheet.create({ backgroundColor: "rgba(255,255,255,0.05)", }, team: { - fontSize: 16, + fontSize: 14, fontWeight: "500", }, odds: { - fontSize: 16, - fontWeight: "600", + fontSize: 14, + fontWeight: "700", color: "#FF9800", }, disclaimer: { @@ -119,4 +201,14 @@ const styles = StyleSheet.create({ marginTop: 20, textAlign: "center", }, + loadingContainer: { + height: 60, + justifyContent: "center", + alignItems: "center", + }, + emptyContainer: { + height: 60, + justifyContent: "center", + alignItems: "center", + }, }); diff --git a/types/api.ts b/types/api.ts index 88fef1c..d2b731b 100644 --- a/types/api.ts +++ b/types/api.ts @@ -265,7 +265,25 @@ export interface MatchDetailData { }; } -// 实时赔率(根据 sportId 可能是 LiveOdds 或 Odds 结构,暂时使用宽泛类型) -export interface OddsData { +// 实时赔率数据项结构 +export interface OddsItem { + odd_bookmakers: string; + odd_1?: number; + odd_x?: number; + odd_2?: number; + odd_12?: number; + odd_1x?: number; + odd_x2?: number; + "ah+1_1"?: number; + "ah+1_2"?: number; + "o+2.5"?: number; + "u+2.5"?: number; [key: string]: any; } + +// 实时赔率响应数据结构 +export interface OddsData { + [match_id: string]: { + data: OddsItem[]; + }; +}