diff --git a/app/match-detail/[id].tsx b/app/match-detail/[id].tsx index 5ef57a9..4b5cf10 100644 --- a/app/match-detail/[id].tsx +++ b/app/match-detail/[id].tsx @@ -5,6 +5,7 @@ import { FootballScoreTable } from "@/components/match-detail/football/football- import { GoalsCard } from "@/components/match-detail/football/goals-card"; import { LineupsCard } from "@/components/match-detail/football/lineups-card"; import { SubstitutesCard } from "@/components/match-detail/football/substitutes-card"; +import { H2H } from "@/components/match-detail/h2h"; import { LeagueInfo } from "@/components/match-detail/league-info"; import { MatchInfoCard } from "@/components/match-detail/match-info-card"; import { MatchTabs } from "@/components/match-detail/match-tabs"; @@ -187,13 +188,7 @@ export default function MatchDetailScreen() { }; return ; case "h2h": - return ( - - - {t("detail.empty_h2h")} - - - ); + return ; case "chat": return ( diff --git a/components/match-detail/h2h.tsx b/components/match-detail/h2h.tsx new file mode 100644 index 0000000..e2d0aab --- /dev/null +++ b/components/match-detail/h2h.tsx @@ -0,0 +1,377 @@ +import { ThemedText } from "@/components/themed-text"; +import { fetchH2H } from "@/lib/api"; +import { H2HData, H2HMatch, MatchDetailData } from "@/types/api"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + ActivityIndicator, + StyleSheet, + TouchableOpacity, + View, +} from "react-native"; + +interface H2HProps { + data: MatchDetailData; + isDark: boolean; +} + +export function H2H({ data, isDark }: H2HProps) { + const { t } = useTranslation(); + const { match } = data; + const [h2hData, setH2hData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [activeSection, setActiveSection] = useState<"h2h" | "first" | "second">("h2h"); + + const bgColor = isDark ? "#1C1C1E" : "#FFF"; + const borderColor = isDark ? "rgba(150,150,150,0.2)" : "rgba(150,150,150,0.2)"; + + // 颜色常量配置 + const colors = { + bg: isDark ? "#000000" : "#F8F8F8", + card: bgColor, + border: borderColor, + textMain: isDark ? "#FFFFFF" : "#000000", + textSecondary: isDark ? "#8E8E93" : "#8E8E93", + win: "#4CAF50", + loss: "#F44336", + draw: "#2196F3", + scoreHighlight: "#FFB800", + }; + + useEffect(() => { + loadH2H(); + }, []); + + const loadH2H = async () => { + try { + setLoading(true); + setError(null); + const sportId = match.sportId; + const options: any = {}; + + if (sportId === 3) { + options.firstPlayerId = parseInt(match.homeTeamKey); + options.secondPlayerId = parseInt(match.awayTeamKey); + } else { + options.firstTeamId = parseInt(match.homeTeamKey); + options.secondTeamId = parseInt(match.awayTeamKey); + } + + const result = await fetchH2H(sportId, options); + setH2hData(result); + } catch (err: any) { + setError(err?.message || t("detail.h2h.error")); + } finally { + setLoading(false); + } + }; + + // 核心逻辑:计算胜负状态 + const getMatchResult = (item: H2HMatch) => { + const homeScore = parseInt(item.event_final_result?.split("-")[0] || "0"); + const awayScore = parseInt(item.event_final_result?.split("-")[1] || "0"); + + // 确定当前视角球队名称 + const perspectiveTeam = activeSection === "first" ? match.eventHomeTeam : + activeSection === "second" ? match.eventAwayTeam : null; + + if (!perspectiveTeam) return { label: "", color: "transparent" }; + + const isHome = item.event_home_team === perspectiveTeam; + if (homeScore === awayScore) return { label: "D", color: colors.draw }; + + const isWin = isHome ? homeScore > awayScore : awayScore > homeScore; + return isWin ? { label: "W", color: colors.win } : { label: "L", color: colors.loss }; + }; + + const renderMatchItem = ({ item, index }: { item: H2HMatch; index: number }) => { + const homeScore = parseInt(item.event_final_result?.split("-")[0] || "0"); + const awayScore = parseInt(item.event_final_result?.split("-")[1] || "0"); + const { label, color } = getMatchResult(item); + const matchYear = item.event_date?.split("-")[0] || ""; + + return ( + + {/* 左侧:日期状态 */} + + {matchYear} + FT + + + {/* 中间:球队对阵 */} + + + awayScore && [ + styles.boldText, + { color: isDark ? "#FFF" : "#000" } + ] + ]} + numberOfLines={1} + > + {item.event_home_team} + + + + homeScore && [ + styles.boldText, + { color: isDark ? "#FFF" : "#000" } + ] + ]} + numberOfLines={1} + > + {item.event_away_team} + + + + + {/* 右侧:比分与胜负角标 */} + + + awayScore && { color: colors.scoreHighlight }]}> + {homeScore} + + homeScore && { color: colors.scoreHighlight }]}> + {awayScore} + + + {label !== "" && ( + + {label} + + )} + + + ); + }; + + // 状态处理 (Loading, Error, Empty) 保持原样但应用新背景色 + if (loading) { + return ( + + + + + {t("detail.h2h.loading")} + + + + ); + } + + if (error) { + return ( + + + {error} + + + {t("detail.h2h.retry")} + + + + + ); + } + + if (!h2hData) { + return ( + + + + {t("detail.h2h.no_data")} + + + + ); + } + + const currentData = activeSection === "h2h" ? h2hData?.H2H : + activeSection === "first" ? h2hData?.firstTeamResults : h2hData?.secondTeamResults; + + return ( + + {/* 选项卡 */} + + {[ + { id: "h2h", label: t("detail.h2h.h2h") }, + { id: "first", label: match.eventHomeTeam }, + { id: "second", label: match.eventAwayTeam } + ].map((tab) => ( + setActiveSection(tab.id as any)} + > + + {tab.label} + + + ))} + + + {/* 列表内容 */} + + {currentData && currentData.length > 0 ? ( + currentData.map((item, index) => renderMatchItem({ item, index })) + ) : ( + + {t("detail.h2h.no_data")} + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + margin: 16, + marginTop: 0, + borderRadius: 12, + padding: 16, + elevation: 2, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + }, + loadingContainer: { + padding: 40, + alignItems: "center", + gap: 12, + }, + loadingText: { + fontSize: 14, + opacity: 0.6, + }, + errorContainer: { + padding: 40, + alignItems: "center", + gap: 16, + }, + errorText: { + fontSize: 14, + opacity: 0.7, + textAlign: "center", + }, + retryButton: { + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + retryText: { + fontSize: 13, + fontWeight: "600", + }, + emptyContainer: { + padding: 40, + alignItems: "center", + }, + tabs: { + flexDirection: "row", + marginBottom: 12, + paddingBottom: 12, + borderBottomWidth: StyleSheet.hairlineWidth, + }, + tab: { + paddingVertical: 12, + marginRight: 20, + borderBottomWidth: 2, + }, + tabActive: {}, + tabText: { + fontSize: 14, + opacity: 0.6, + }, + tabTextActive: { + fontWeight: "bold", + opacity: 1, + }, + listContent: { + paddingBottom: 20, + }, + matchRow: { + flexDirection: "row", + paddingVertical: 12, + borderBottomWidth: StyleSheet.hairlineWidth, + alignItems: "center", + }, + leftCol: { + width: 50, + alignItems: "flex-start", + }, + yearText: { + fontSize: 12, + color: "#8E8E93", + }, + ftText: { + fontSize: 11, + fontWeight: "bold", + marginTop: 2, + color: "#666", + }, + centerCol: { + flex: 1, + paddingLeft: 4, + }, + teamLine: { + height: 24, + justifyContent: "center", + }, + teamName: { + fontSize: 14, + color: "#BBB", + }, + boldText: { + fontWeight: "600", + }, + rightCol: { + flexDirection: "row", + alignItems: "center", + }, + scoreContainer: { + backgroundColor: "#1C1C1E", + borderRadius: 6, + paddingHorizontal: 10, + paddingVertical: 4, + minWidth: 40, + alignItems: "center", + marginRight: 10, + }, + scoreText: { + fontSize: 14, + fontWeight: "bold", + lineHeight: 20, + color: "#FFF", + }, + resultBadge: { + width: 22, + height: 38, + borderRadius: 4, + justifyContent: "center", + alignItems: "center", + }, + resultBadgeText: { + color: "#FFF", + fontSize: 13, + fontWeight: "bold", + }, + emptyText: { + textAlign: "center", + marginTop: 40, + opacity: 0.5, + }, +}); \ No newline at end of file diff --git a/i18n/locales/en.json b/i18n/locales/en.json index a0e0d41..3afc9f3 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -70,6 +70,13 @@ "empty_odds": "No odds data", "empty_h2h": "No H2H data", "empty_chat": "Chat is not available", + "h2h": { + "h2h": "Head-to-Head", + "loading": "Loading...", + "error": "Failed to load", + "retry": "Retry", + "no_data": "No data available" + }, "odds_card": { "title": "Match Odds", "disclaimer": "18+. Please gamble responsibly. Odds are subject to change." diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index e0e512d..54289e6 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -70,6 +70,13 @@ "empty_odds": "暂无赔率数据", "empty_h2h": "暂无交锋数据", "empty_chat": "聊天功能暂未开启", + "h2h": { + "h2h": "历史对战", + "loading": "加载中...", + "error": "加载失败", + "retry": "重试", + "no_data": "暂无数据" + }, "odds_card": { "title": "比赛赔率", "disclaimer": "18+. 请负责任地赌博。赔率可能会变动。" diff --git a/types/api.ts b/types/api.ts index 4cd4d17..3bb3016 100644 --- a/types/api.ts +++ b/types/api.ts @@ -404,23 +404,25 @@ export interface SearchResult { // H2H 历史对战比赛项 export interface H2HMatch { - awayTeamKey: string; - countryName: string; - eventAwayTeam: string; - eventCountryKey: string; - eventDate: string; - eventFinalResult: string; - eventHalftimeResult: string; - eventHomeTeam: string; - eventKey: string; - eventLive: string; - eventStatus: string; - eventTime: string; - homeTeamKey: string; - leagueKey: string; - leagueName: string; - leagueRound: string; - leagueSeason: string; + away_team_key: number; + away_team_logo?: string; + country_name: string; + event_away_team: string; + event_country_key: number; + event_date: string; + event_final_result: string; + event_halftime_result: string; + event_home_team: string; + event_key: number; + event_live: string; + event_status: string; + event_time: string; + home_team_key: number; + home_team_logo?: string; + league_key: number; + league_name: string; + league_round: string; + league_season: string; } // H2H 历史对战数据