Files
physical-expo/components/match-detail/football/football-events.tsx
2026-01-13 16:46:16 +08:00

403 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ThemedText } from "@/components/themed-text";
import { IconSymbol } from "@/components/ui/icon-symbol";
import { CardEvent, GoalEvent, MatchDetailData, Player, SubstituteEvent } from "@/types/api";
import React from "react";
import { useTranslation } from "react-i18next";
import { StyleSheet, View } from "react-native";
interface FootballEventsProps {
data: MatchDetailData;
isDark: boolean;
}
export function FootballEvents({ data, isDark }: FootballEventsProps) {
const { t } = useTranslation();
const { match } = data;
const bgColor = isDark ? "#1C1C1E" : "#FFF";
const borderColor = isDark ? "#2C2C2E" : "#EEE";
// events 可能在 data.events 或 match.events 中
const events = (data as any).events || (match as any).events;
// 调试:打印 events 数据
if (__DEV__) {
console.log("FootballEvents - events data:", events);
}
if (!events || typeof events !== "object") {
if (__DEV__) {
console.log("FootballEvents - No events data found");
}
return null;
}
// 处理数组格式的 events如果 events 是数组)
let eventsObj = events;
if (Array.isArray(events) && events.length > 0) {
// 如果 events 是数组,取第一个元素
eventsObj = events[0];
}
const goalscorers = eventsObj?.goalscorers || [];
const cards = eventsObj?.cards || [];
const substitutes = eventsObj?.substitutes || [];
const players = (match as any).players || null;
if (__DEV__) {
console.log("FootballEvents - goalscorers:", goalscorers.length, "cards:", cards.length, "substitutes:", substitutes.length);
}
const hasEvents = goalscorers.length > 0 || cards.length > 0 || substitutes.length > 0;
if (!hasEvents) {
if (__DEV__) {
console.log("FootballEvents - No events to display");
}
return null;
}
const renderGoalEvent = (goal: GoalEvent, index: number) => {
const isHome = !!goal.home_scorer;
const scorerRaw = goal.home_scorer || goal.away_scorer || "";
const isPenalty = scorerRaw.includes("(pen.)");
const playerName = scorerRaw.replace("(pen.)", "").trim();
return (
<View key={index} style={[styles.eventRow, { borderBottomColor: borderColor }]}>
<View style={styles.eventTime}>
<ThemedText style={styles.timeText}>
{goal.time ? `${goal.time}'` : ""}
</ThemedText>
</View>
<View style={[styles.eventContent, isHome ? styles.homeEvent : styles.awayEvent]}>
<IconSymbol name="football" size={16} color={isDark ? "#4CAF50" : "#2E7D32"} />
<View style={styles.goalContent}>
{playerName && (
<ThemedText style={styles.eventText} numberOfLines={1}>
{playerName}
</ThemedText>
)}
{isPenalty && (
<View style={styles.penaltyBadge}>
<ThemedText style={styles.penaltyText}>P</ThemedText>
</View>
)}
</View>
{goal.score && (
<ThemedText style={styles.scoreText}>{goal.score}</ThemedText>
)}
</View>
</View>
);
};
const renderCardEvent = (card: CardEvent, index: number) => {
const isHome = !!card.home_fault;
const cardType = card.card?.toLowerCase() || "";
const isRed = cardType.includes("red");
const isYellow = cardType.includes("yellow");
const cardColor = isRed ? "#F44336" : isYellow ? "#FFC107" : "#9E9E9E";
const playerName = card.home_fault || card.away_fault || "";
return (
<View key={`card-${index}`} style={[styles.eventRow, { borderBottomColor: borderColor }]}>
<View style={styles.eventTime}>
<ThemedText style={styles.timeText}>
{card.time ? `${card.time}'` : ""}
</ThemedText>
</View>
<View style={[styles.eventContent, isHome ? styles.homeEvent : styles.awayEvent]}>
<View style={[styles.cardIcon, { backgroundColor: cardColor }]} />
<ThemedText style={styles.eventText} numberOfLines={1}>
{playerName}
</ThemedText>
{card.card && (
<View style={[styles.cardBadge, { backgroundColor: cardColor + "20" }]}>
<ThemedText style={[styles.cardTypeText, { color: cardColor }]}>
{isRed ? t("detail.events.red_card") : isYellow ? t("detail.events.yellow_card") : card.card}
</ThemedText>
</View>
)}
</View>
</View>
);
};
const renderSubstituteEvent = (sub: SubstituteEvent, index: number) => {
const isHome = !!sub.home_scorer || !!sub.home_assist;
// 换人数据可能包含home_scorer (换入), away_scorer (换出) 或相反
// 也可能有单独的字段如 home_assist, away_assist 等
const playerIn = sub.home_scorer || sub.home_assist || sub.away_scorer || "";
const playerOut = sub.away_scorer || sub.away_assist || sub.home_scorer_out || sub.away_scorer_out || "";
return (
<View key={`sub-${index}`} style={[styles.eventRow, { borderBottomColor: borderColor }]}>
<View style={styles.eventTime}>
<ThemedText style={styles.timeText}>
{sub.time ? `${sub.time}'` : ""}
</ThemedText>
</View>
<View style={[styles.eventContent, isHome ? styles.homeEvent : styles.awayEvent]}>
<IconSymbol name="swap-horizontal" size={16} color={isDark ? "#2196F3" : "#1976D2"} />
<View style={styles.substituteContent}>
{playerOut ? (
<>
<ThemedText style={[styles.substituteIn, { color: isDark ? "#4CAF50" : "#2E7D32" }]} numberOfLines={1}>
{playerIn}
</ThemedText>
<ThemedText style={styles.substituteArrow}></ThemedText>
<ThemedText style={styles.substituteOut} numberOfLines={1}>
{playerOut}
</ThemedText>
</>
) : (
<ThemedText style={styles.eventText} numberOfLines={1}>
{playerIn}
</ThemedText>
)}
</View>
</View>
</View>
);
};
return (
<View style={[styles.container, { backgroundColor: bgColor, borderColor }]}>
{goalscorers.length > 0 && (
<View style={styles.section}>
<ThemedText style={styles.sectionTitle}>
{t("detail.events.goals")}
</ThemedText>
{goalscorers
.filter((goal: GoalEvent) => goal.home_scorer || goal.away_scorer)
.map((goal: GoalEvent, index: number) => renderGoalEvent(goal, index))}
</View>
)}
{cards.length > 0 && (
<View style={styles.section}>
<ThemedText style={styles.sectionTitle}>
{t("detail.events.cards")}
</ThemedText>
{cards
.filter((card: CardEvent) => card.home_fault || card.away_fault)
.map((card: CardEvent, index: number) => renderCardEvent(card, index))}
</View>
)}
{substitutes.length > 0 && (
<View style={styles.section}>
<ThemedText style={styles.sectionTitle}>
{t("detail.events.substitutes")}
</ThemedText>
{substitutes
.filter((sub: SubstituteEvent) => sub.home_scorer || sub.away_scorer || sub.home_assist || sub.away_assist)
.map((sub: SubstituteEvent, index: number) => renderSubstituteEvent(sub, index))}
</View>
)}
{players && (players.home_team || players.away_team) && (
<View style={styles.section}>
<ThemedText style={styles.sectionTitle}>
{t("detail.events.lineups")}
</ThemedText>
{players.home_team && Array.isArray(players.home_team) && players.home_team.length > 0 && (
<View style={styles.teamLineup}>
<ThemedText style={styles.teamName}>
{match.eventHomeTeam}
</ThemedText>
{players.home_team.slice(0, 11).map((player: Player, idx: number) => (
<View key={idx} style={styles.playerRow}>
<ThemedText style={styles.playerNumber}>
{player.player_number || player.number || idx + 1}
</ThemedText>
<ThemedText style={styles.playerName} numberOfLines={1}>
{player.player_name || player.player || player.name || ""}
</ThemedText>
{player.position && (
<ThemedText style={styles.playerPosition}>
{player.position}
</ThemedText>
)}
</View>
))}
</View>
)}
{players.away_team && Array.isArray(players.away_team) && players.away_team.length > 0 && (
<View style={[styles.teamLineup, { marginTop: 16 }]}>
<ThemedText style={styles.teamName}>
{match.eventAwayTeam}
</ThemedText>
{players.away_team.slice(0, 11).map((player: Player, idx: number) => (
<View key={idx} style={styles.playerRow}>
<ThemedText style={styles.playerNumber}>
{player.player_number || player.number || idx + 1}
</ThemedText>
<ThemedText style={styles.playerName} numberOfLines={1}>
{player.player_name || player.player || player.name || ""}
</ThemedText>
{player.position && (
<ThemedText style={styles.playerPosition}>
{player.position}
</ThemedText>
)}
</View>
))}
</View>
)}
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
margin: 16,
marginTop: 0,
borderRadius: 12,
padding: 16,
borderWidth: 1,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
section: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 14,
fontWeight: "600",
marginBottom: 12,
opacity: 0.7,
},
eventRow: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 8,
borderBottomWidth: StyleSheet.hairlineWidth,
},
eventTime: {
width: 40,
alignItems: "center",
},
timeText: {
fontSize: 12,
fontWeight: "500",
opacity: 0.6,
},
eventContent: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: 8,
paddingLeft: 12,
},
homeEvent: {
justifyContent: "flex-start",
},
awayEvent: {
justifyContent: "flex-end",
flexDirection: "row-reverse",
},
eventText: {
fontSize: 13,
fontWeight: "500",
flex: 1,
},
scoreText: {
fontSize: 12,
fontWeight: "600",
opacity: 0.7,
},
cardIcon: {
width: 12,
height: 16,
borderRadius: 2,
},
cardTypeText: {
fontSize: 10,
fontWeight: "600",
textTransform: "uppercase",
},
cardBadge: {
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
},
goalContent: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: 6,
},
penaltyBadge: {
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: "#FF9800",
alignItems: "center",
justifyContent: "center",
},
penaltyText: {
fontSize: 10,
fontWeight: "700",
color: "#FFF",
},
substituteContent: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: 6,
},
substituteIn: {
fontSize: 13,
fontWeight: "500",
},
substituteArrow: {
fontSize: 12,
opacity: 0.5,
},
substituteOut: {
fontSize: 13,
fontWeight: "500",
opacity: 0.6,
textDecorationLine: "line-through",
},
teamLineup: {
marginTop: 8,
},
teamName: {
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
opacity: 0.8,
},
playerRow: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 4,
gap: 8,
},
playerNumber: {
fontSize: 12,
fontWeight: "600",
width: 24,
textAlign: "center",
opacity: 0.6,
},
playerName: {
fontSize: 13,
fontWeight: "500",
flex: 1,
},
playerPosition: {
fontSize: 11,
fontWeight: "500",
opacity: 0.5,
textTransform: "uppercase",
},
});