篮球整体统计数据
This commit is contained in:
@@ -69,6 +69,7 @@ export default function LiveDetailScreen() {
|
||||
if (liveData && Array.isArray(liveData)) {
|
||||
// Find the specific match
|
||||
const found = liveData.find((m) => m.event_key.toString() === id);
|
||||
console.log("found", JSON.stringify(found, null, 2));
|
||||
if (found) {
|
||||
setMatch(found);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OddsCard } from "@/components/live-detail/odds-card";
|
||||
import { BasketballOverallStats } from "@/components/match-detail/basketball/basketball-overall-stats";
|
||||
import { BasketballScoreTable } from "@/components/match-detail/basketball/basketball-score-table";
|
||||
import { BasketballStats } from "@/components/match-detail/basketball/basketball-stats";
|
||||
import { CardsCard } from "@/components/match-detail/football/cards-card";
|
||||
@@ -60,7 +61,7 @@ export default function MatchDetailScreen() {
|
||||
validTabs = ["info", "stats", "odds", "h2h", "chat"];
|
||||
} else if (sportId === 2) {
|
||||
// 篮球
|
||||
validTabs = ["info", "stats", "h2h", "chat"];
|
||||
validTabs = ["info", "stats", "overall", "h2h", "chat"];
|
||||
} else if (sportId === 3) {
|
||||
// 网球
|
||||
validTabs = ["info", "chat"];
|
||||
@@ -82,8 +83,8 @@ export default function MatchDetailScreen() {
|
||||
setError(null);
|
||||
const result = await fetchMatchDetail(id as string);
|
||||
setData(result);
|
||||
console.log("首发阵容", result.match.players?.away_team);
|
||||
console.log("红黄牌", result.events);
|
||||
// console.log("首发阵容", result.match.players?.away_team);
|
||||
// console.log("红黄牌", result.events);
|
||||
|
||||
|
||||
|
||||
@@ -177,6 +178,8 @@ export default function MatchDetailScreen() {
|
||||
</View>
|
||||
);
|
||||
}
|
||||
case "overall":
|
||||
return <BasketballOverallStats data={data} isDark={isDark} />;
|
||||
case "odds":
|
||||
// 将 MatchDetailData.match 转换为 LiveScoreMatch 格式
|
||||
const matchForOdds = {
|
||||
|
||||
285
components/match-detail/basketball/basketball-overall-stats.tsx
Normal file
285
components/match-detail/basketball/basketball-overall-stats.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -63,7 +63,7 @@ export function MatchTabs({
|
||||
},
|
||||
];
|
||||
} else if (sportId === 2) {
|
||||
// 篮球: 详情、统计数据、交锋往绩、聊天
|
||||
// 篮球: 详情、统计数据、整体统计、交锋往绩、聊天
|
||||
return [
|
||||
{
|
||||
id: "info",
|
||||
@@ -75,6 +75,11 @@ export function MatchTabs({
|
||||
label: t("detail.tabs.stats"),
|
||||
icon: "stats-chart-outline",
|
||||
},
|
||||
{
|
||||
id: "overall",
|
||||
label: t("detail.tabs.overall"),
|
||||
icon: "bar-chart-outline",
|
||||
},
|
||||
{
|
||||
id: "h2h",
|
||||
label: t("detail.tabs.h2h"),
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"fetch_failed": "Failed to fetch details",
|
||||
"not_found": "Match data not found",
|
||||
"tabs": {
|
||||
"overall": "Overall",
|
||||
"info": "Details",
|
||||
"stats": "Statistics",
|
||||
"odds": "Odds",
|
||||
@@ -166,6 +167,12 @@
|
||||
"lineups_coach": "Coach",
|
||||
"lineups_subs": "Substitutes",
|
||||
"lineups_missing": "Missing players"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "Assists",
|
||||
"total_blocks": "Blocks",
|
||||
"total_steals": "Steals",
|
||||
"total_turnovers": "Turnovers"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"fetch_failed": "विवरण प्राप्त करने में विफल",
|
||||
"not_found": "मैच डेटा नहीं मिला",
|
||||
"tabs": {
|
||||
"overall": "कुल",
|
||||
"info": "विवरण",
|
||||
"stats": "आँकड़े",
|
||||
"odds": "ऑड्स",
|
||||
@@ -162,6 +163,12 @@
|
||||
"lineups_coach": "कोच",
|
||||
"lineups_subs": "सब्स्टीट्यूट",
|
||||
"lineups_missing": "अनुपस्थित खिलाड़ी"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "असिस्ट",
|
||||
"total_blocks": "ब्लॉक",
|
||||
"total_steals": "स्टील",
|
||||
"total_turnovers": "टर्नओवर"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"fetch_failed": "Gagal mengambil data",
|
||||
"not_found": "Data pertandingan tidak ditemukan",
|
||||
"tabs": {
|
||||
"overall": "Keseluruhan",
|
||||
"info": "Detail",
|
||||
"stats": "Statistik",
|
||||
"odds": "Odds",
|
||||
@@ -162,6 +163,12 @@
|
||||
"lineups_coach": "Pelatih",
|
||||
"lineups_subs": "Cadangan",
|
||||
"lineups_missing": "Pemain Absen"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "Asisten",
|
||||
"total_blocks": "Blok",
|
||||
"total_steals": "Steal",
|
||||
"total_turnovers": "Turnover"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"fetch_failed": "Gagal mendapatkan maklumat",
|
||||
"not_found": "Data perlawanan tidak ditemui",
|
||||
"tabs": {
|
||||
"overall": "Keseluruhan",
|
||||
"info": "Maklumat",
|
||||
"stats": "Statistik",
|
||||
"odds": "Odds",
|
||||
@@ -162,6 +163,12 @@
|
||||
"lineups_coach": "Jurulatih",
|
||||
"lineups_subs": "Pemain Simpanan",
|
||||
"lineups_missing": "Pemain Tidak Tersenarai"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "Bantuan",
|
||||
"total_blocks": "Sekatan",
|
||||
"total_steals": "Curi",
|
||||
"total_turnovers": "Pusingan"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"fetch_failed": "ไม่สามารถดึงข้อมูลได้",
|
||||
"not_found": "ไม่พบข้อมูลการแข่งขัน",
|
||||
"tabs": {
|
||||
"overall": "ทั้งหมด",
|
||||
"info": "รายละเอียด",
|
||||
"stats": "สถิติ",
|
||||
"odds": "อัตราต่อรอง",
|
||||
@@ -162,6 +163,12 @@
|
||||
"lineups_coach": "โค้ช",
|
||||
"lineups_subs": "ตัวสำรอง",
|
||||
"lineups_missing": "ผู้เล่นที่ขาดหาย"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "แอสซิสต์",
|
||||
"total_blocks": "บล็อก",
|
||||
"total_steals": "สตีล",
|
||||
"total_turnovers": "เทิร์นโอเวอร์"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"fetch_failed": "Không thể tải dữ liệu",
|
||||
"not_found": "Không tìm thấy dữ liệu trận đấu",
|
||||
"tabs": {
|
||||
"overall": "Tổng",
|
||||
"info": "Thông tin",
|
||||
"stats": "Thống kê",
|
||||
"odds": "Tỷ lệ cược",
|
||||
@@ -162,6 +163,12 @@
|
||||
"lineups_coach": "Huấn luyện viên",
|
||||
"lineups_subs": "Dự bị",
|
||||
"lineups_missing": "Cầu thủ vắng mặt"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "Kiến tạo",
|
||||
"total_blocks": "Chặn bóng",
|
||||
"total_steals": "Cướp bóng",
|
||||
"total_turnovers": "Mất bóng"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"fetch_failed": "获取详情失败",
|
||||
"not_found": "未找到比赛数据",
|
||||
"tabs": {
|
||||
"overall": "全部",
|
||||
"info": "详情",
|
||||
"stats": "统计数据",
|
||||
"odds": "赔率",
|
||||
@@ -166,6 +167,12 @@
|
||||
"lineups_coach": "主教练",
|
||||
"lineups_subs": "替补球员",
|
||||
"lineups_missing": "缺席球员"
|
||||
},
|
||||
"overall_stats": {
|
||||
"total_assists": "助攻",
|
||||
"total_blocks": "盖帽",
|
||||
"total_steals": "抢断",
|
||||
"total_turnovers": "失误"
|
||||
}
|
||||
},
|
||||
"selection": {
|
||||
|
||||
Reference in New Issue
Block a user