Files
physical-expo/components/match-detail/basketball/basketball-overall-stats.tsx
2026-01-22 18:05:04 +08:00

286 lines
6.7 KiB
TypeScript

import { ThemedText } from "@/components/themed-text";
import { MatchDetailData } from "@/types/api";
import { LinearGradient } from "expo-linear-gradient";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { StyleSheet, View } from "react-native";
interface BasketballOverallStatsProps {
data: MatchDetailData;
isDark: boolean;
}
type StatItem = {
type: string;
home: string | number;
away: string | number;
};
function toNumber(v: unknown): number {
if (typeof v === "number") return Number.isFinite(v) ? v : 0;
if (typeof v === "string") {
const cleaned = v.trim().replace("%", "");
const n = Number(cleaned);
return Number.isFinite(n) ? n : 0;
}
return 0;
}
export function BasketballOverallStats({ data, isDark }: BasketballOverallStatsProps) {
const { t } = useTranslation();
useEffect(() => {
console.log("=== Basketball Overall Stats Loaded ===");
console.log(JSON.stringify(data.match.stats, null, 2));
}, [data.match.stats]);
const stats = (data.match.stats || []) as StatItem[];
const getStatLabel = (type: string): string => {
const key = `detail.overall_stats.${type.toLowerCase().replace(/\s+/g, "_")}`;
const translated = t(key);
return translated !== key ? translated : type;
};
if (!Array.isArray(stats) || stats.length === 0) {
return (
<View style={styles.empty}>
<ThemedText style={[styles.emptyText, { color: isDark ? "#A1A1AA" : "#6B7280" }]}>
{t("detail.empty_stats")}
</ThemedText>
</View>
);
}
return (
<View
style={[
styles.wrap,
{ backgroundColor: isDark ? "#151517" : "#F3F4F6" },
]}
>
{/* Card */}
<View
style={[
styles.card,
{ backgroundColor: isDark ? "#1C1C1E" : "#FFFFFF" },
]}
>
{stats.map((item, index) => {
const home = toNumber(item.home);
const away = toNumber(item.away);
const maxV = Math.max(home, away, 1);
const homeLeading = home >= away;
const awayLeading = away > home;
return (
<View
key={`${item.type}-${index}`}
style={[
styles.row,
index !== stats.length - 1 && {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
},
]}
>
<View style={styles.rowHeader}>
<ValuePill
text={`${home}`}
variant="home"
isDark={isDark}
/>
<ThemedText
style={[
styles.title,
{ color: isDark ? "#EAEAF0" : "#111827" },
]}
numberOfLines={1}
>
{getStatLabel(item.type)}
</ThemedText>
<ValuePill
text={`${away}`}
variant={awayLeading ? "awayLeading" : "away"}
isDark={isDark}
/>
</View>
<CompareBar
home={home}
away={away}
maxV={maxV}
isDark={isDark}
homeLeading={homeLeading}
awayLeading={awayLeading}
/>
</View>
);
})}
</View>
</View>
);
}
function ValuePill({
text,
variant,
isDark,
}: {
text: string;
variant: "home" | "away" | "awayLeading";
isDark: boolean;
}) {
const common = {
start: { x: 0, y: 0 },
end: { x: 1, y: 0 },
style: styles.pill,
} as const;
// 蓝色:主队/默认;金色:客队领先
const blue = isDark
? ["#0B2A73", "#0E4BFF"]
: ["#1D4ED8", "#2563EB"];
const gold = isDark
? ["#3A2A00", "#C08B00"]
: ["#B45309", "#D97706"];
const colors =
variant === "awayLeading"
? gold
: blue;
return (
<LinearGradient colors={[...colors] as [string, string]} {...common}>
<ThemedText style={styles.pillText}>{text}</ThemedText>
</LinearGradient>
);
}
function CompareBar({
home,
away,
maxV,
isDark,
homeLeading,
awayLeading,
}: {
home: number;
away: number;
maxV: number;
isDark: boolean;
homeLeading: boolean;
awayLeading: boolean;
}) {
const SIDE_MAX = 150;
const GAP = 10;
const homeW = Math.max(2, Math.round((home / maxV) * SIDE_MAX));
const awayW = Math.max(2, Math.round((away / maxV) * SIDE_MAX));
const track = isDark ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.10)";
const muted = isDark ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.18)";
const blue = "#1F5BFF";
const gold = "#C08B00";
const homeColor = homeLeading ? blue : muted;
const awayColor = awayLeading ? gold : muted;
return (
<View style={[styles.barWrap, { width: SIDE_MAX * 2 + GAP }]}>
<View style={[styles.barTrack, { backgroundColor: track }]} />
<View style={[styles.barSide, { width: SIDE_MAX, justifyContent: "flex-end" }]}>
<View style={[styles.barFill, { width: homeW, backgroundColor: homeColor }]} />
</View>
<View style={{ width: GAP }} />
<View style={[styles.barSide, { width: SIDE_MAX, justifyContent: "flex-start" }]}>
<View style={[styles.barFill, { width: awayW, backgroundColor: awayColor }]} />
</View>
</View>
);
}
/* ----------------- Styles ----------------- */
const styles = StyleSheet.create({
wrap: {
paddingHorizontal: 14,
paddingTop: 12,
paddingBottom: 16,
},
card: {
borderRadius: 18,
paddingVertical: 6,
overflow: "hidden",
},
row: {
paddingHorizontal: 14,
paddingVertical: 14,
},
rowHeader: {
flexDirection: "row",
alignItems: "center",
},
pill: {
minWidth: 54,
height: 30,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 10,
},
pillText: {
color: "#FFFFFF",
fontSize: 14,
fontWeight: "800",
letterSpacing: 0.3,
},
title: {
flex: 1,
textAlign: "center",
fontSize: 15,
fontWeight: "700",
paddingHorizontal: 10,
},
barWrap: {
alignSelf: "center",
flexDirection: "row",
alignItems: "center",
marginTop: 10,
height: 8,
position: "relative",
},
barTrack: {
position: "absolute",
left: 0,
right: 0,
height: 6,
borderRadius: 999,
},
barSide: {
height: 8,
flexDirection: "row",
alignItems: "center",
},
barFill: {
height: 6,
borderRadius: 999,
},
empty: {
padding: 40,
alignItems: "center",
},
emptyText: {
fontSize: 14,
opacity: 0.85,
},
});