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 历史对战数据