交锋往绩
This commit is contained in:
@@ -5,6 +5,7 @@ import { FootballScoreTable } from "@/components/match-detail/football/football-
|
|||||||
import { GoalsCard } from "@/components/match-detail/football/goals-card";
|
import { GoalsCard } from "@/components/match-detail/football/goals-card";
|
||||||
import { LineupsCard } from "@/components/match-detail/football/lineups-card";
|
import { LineupsCard } from "@/components/match-detail/football/lineups-card";
|
||||||
import { SubstitutesCard } from "@/components/match-detail/football/substitutes-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 { LeagueInfo } from "@/components/match-detail/league-info";
|
||||||
import { MatchInfoCard } from "@/components/match-detail/match-info-card";
|
import { MatchInfoCard } from "@/components/match-detail/match-info-card";
|
||||||
import { MatchTabs } from "@/components/match-detail/match-tabs";
|
import { MatchTabs } from "@/components/match-detail/match-tabs";
|
||||||
@@ -187,13 +188,7 @@ export default function MatchDetailScreen() {
|
|||||||
};
|
};
|
||||||
return <OddsCard sportId={sportId} match={matchForOdds} isDark={isDark} />;
|
return <OddsCard sportId={sportId} match={matchForOdds} isDark={isDark} />;
|
||||||
case "h2h":
|
case "h2h":
|
||||||
return (
|
return <H2H data={data} isDark={isDark} />;
|
||||||
<View style={styles.emptyContent}>
|
|
||||||
<ThemedText style={styles.emptyText}>
|
|
||||||
{t("detail.empty_h2h")}
|
|
||||||
</ThemedText>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
case "chat":
|
case "chat":
|
||||||
return (
|
return (
|
||||||
<View style={styles.emptyContent}>
|
<View style={styles.emptyContent}>
|
||||||
|
|||||||
377
components/match-detail/h2h.tsx
Normal file
377
components/match-detail/h2h.tsx
Normal file
@@ -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<H2HData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(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 (
|
||||||
|
<View
|
||||||
|
key={item.event_key ?? index}
|
||||||
|
style={[styles.matchRow, { borderBottomColor: borderColor }]}
|
||||||
|
>
|
||||||
|
{/* 左侧:日期状态 */}
|
||||||
|
<View style={styles.leftCol}>
|
||||||
|
<ThemedText style={styles.yearText}>{matchYear}</ThemedText>
|
||||||
|
<ThemedText style={styles.ftText}>FT</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 中间:球队对阵 */}
|
||||||
|
<View style={styles.centerCol}>
|
||||||
|
<View style={styles.teamLine}>
|
||||||
|
<ThemedText
|
||||||
|
style={[
|
||||||
|
styles.teamName,
|
||||||
|
homeScore > awayScore && [
|
||||||
|
styles.boldText,
|
||||||
|
{ color: isDark ? "#FFF" : "#000" }
|
||||||
|
]
|
||||||
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{item.event_home_team}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
<View style={styles.teamLine}>
|
||||||
|
<ThemedText
|
||||||
|
style={[
|
||||||
|
styles.teamName,
|
||||||
|
awayScore > homeScore && [
|
||||||
|
styles.boldText,
|
||||||
|
{ color: isDark ? "#FFF" : "#000" }
|
||||||
|
]
|
||||||
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{item.event_away_team}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 右侧:比分与胜负角标 */}
|
||||||
|
<View style={styles.rightCol}>
|
||||||
|
<View style={styles.scoreContainer}>
|
||||||
|
<ThemedText style={[styles.scoreText, homeScore > awayScore && { color: colors.scoreHighlight }]}>
|
||||||
|
{homeScore}
|
||||||
|
</ThemedText>
|
||||||
|
<ThemedText style={[styles.scoreText, awayScore > homeScore && { color: colors.scoreHighlight }]}>
|
||||||
|
{awayScore}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
{label !== "" && (
|
||||||
|
<View style={[styles.resultBadge, { backgroundColor: color }]}>
|
||||||
|
<ThemedText style={styles.resultBadgeText}>{label}</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 状态处理 (Loading, Error, Empty) 保持原样但应用新背景色
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: colors.card }]}>
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="small" color={isDark ? "#FFF" : "#000"} />
|
||||||
|
<ThemedText style={styles.loadingText}>
|
||||||
|
{t("detail.h2h.loading")}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: colors.card }]}>
|
||||||
|
<View style={styles.errorContainer}>
|
||||||
|
<ThemedText style={styles.errorText}>{error}</ThemedText>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.retryButton, { backgroundColor: isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)" }]}
|
||||||
|
onPress={loadH2H}
|
||||||
|
>
|
||||||
|
<ThemedText style={styles.retryText}>
|
||||||
|
{t("detail.h2h.retry")}
|
||||||
|
</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h2hData) {
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: colors.card }]}>
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<ThemedText style={styles.emptyText}>
|
||||||
|
{t("detail.h2h.no_data")}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentData = activeSection === "h2h" ? h2hData?.H2H :
|
||||||
|
activeSection === "first" ? h2hData?.firstTeamResults : h2hData?.secondTeamResults;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: colors.card }]}>
|
||||||
|
{/* 选项卡 */}
|
||||||
|
<View style={[styles.tabs, { borderBottomColor: borderColor }]}>
|
||||||
|
{[
|
||||||
|
{ id: "h2h", label: t("detail.h2h.h2h") },
|
||||||
|
{ id: "first", label: match.eventHomeTeam },
|
||||||
|
{ id: "second", label: match.eventAwayTeam }
|
||||||
|
].map((tab) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={tab.id}
|
||||||
|
style={[styles.tab, activeSection === tab.id && styles.tabActive, { borderBottomColor: activeSection === tab.id ? colors.scoreHighlight : 'transparent' }]}
|
||||||
|
onPress={() => setActiveSection(tab.id as any)}
|
||||||
|
>
|
||||||
|
<ThemedText style={[styles.tabText, activeSection === tab.id && styles.tabTextActive]}>
|
||||||
|
{tab.label}
|
||||||
|
</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 列表内容 */}
|
||||||
|
<View style={styles.listContent}>
|
||||||
|
{currentData && currentData.length > 0 ? (
|
||||||
|
currentData.map((item, index) => renderMatchItem({ item, index }))
|
||||||
|
) : (
|
||||||
|
<ThemedText style={styles.emptyText}>
|
||||||
|
{t("detail.h2h.no_data")}
|
||||||
|
</ThemedText>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -70,6 +70,13 @@
|
|||||||
"empty_odds": "No odds data",
|
"empty_odds": "No odds data",
|
||||||
"empty_h2h": "No H2H data",
|
"empty_h2h": "No H2H data",
|
||||||
"empty_chat": "Chat is not available",
|
"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": {
|
"odds_card": {
|
||||||
"title": "Match Odds",
|
"title": "Match Odds",
|
||||||
"disclaimer": "18+. Please gamble responsibly. Odds are subject to change."
|
"disclaimer": "18+. Please gamble responsibly. Odds are subject to change."
|
||||||
|
|||||||
@@ -70,6 +70,13 @@
|
|||||||
"empty_odds": "暂无赔率数据",
|
"empty_odds": "暂无赔率数据",
|
||||||
"empty_h2h": "暂无交锋数据",
|
"empty_h2h": "暂无交锋数据",
|
||||||
"empty_chat": "聊天功能暂未开启",
|
"empty_chat": "聊天功能暂未开启",
|
||||||
|
"h2h": {
|
||||||
|
"h2h": "历史对战",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"error": "加载失败",
|
||||||
|
"retry": "重试",
|
||||||
|
"no_data": "暂无数据"
|
||||||
|
},
|
||||||
"odds_card": {
|
"odds_card": {
|
||||||
"title": "比赛赔率",
|
"title": "比赛赔率",
|
||||||
"disclaimer": "18+. 请负责任地赌博。赔率可能会变动。"
|
"disclaimer": "18+. 请负责任地赌博。赔率可能会变动。"
|
||||||
|
|||||||
36
types/api.ts
36
types/api.ts
@@ -404,23 +404,25 @@ export interface SearchResult {
|
|||||||
|
|
||||||
// H2H 历史对战比赛项
|
// H2H 历史对战比赛项
|
||||||
export interface H2HMatch {
|
export interface H2HMatch {
|
||||||
awayTeamKey: string;
|
away_team_key: number;
|
||||||
countryName: string;
|
away_team_logo?: string;
|
||||||
eventAwayTeam: string;
|
country_name: string;
|
||||||
eventCountryKey: string;
|
event_away_team: string;
|
||||||
eventDate: string;
|
event_country_key: number;
|
||||||
eventFinalResult: string;
|
event_date: string;
|
||||||
eventHalftimeResult: string;
|
event_final_result: string;
|
||||||
eventHomeTeam: string;
|
event_halftime_result: string;
|
||||||
eventKey: string;
|
event_home_team: string;
|
||||||
eventLive: string;
|
event_key: number;
|
||||||
eventStatus: string;
|
event_live: string;
|
||||||
eventTime: string;
|
event_status: string;
|
||||||
homeTeamKey: string;
|
event_time: string;
|
||||||
leagueKey: string;
|
home_team_key: number;
|
||||||
leagueName: string;
|
home_team_logo?: string;
|
||||||
leagueRound: string;
|
league_key: number;
|
||||||
leagueSeason: string;
|
league_name: string;
|
||||||
|
league_round: string;
|
||||||
|
league_season: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// H2H 历史对战数据
|
// H2H 历史对战数据
|
||||||
|
|||||||
Reference in New Issue
Block a user