From 6dc1170d1dd3e89303f15c413024ec7688f4915b Mon Sep 17 00:00:00 2001 From: xianyi Date: Thu, 22 Jan 2026 18:05:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AF=AE=E7=90=83=E6=95=B4=E4=BD=93=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/live-detail/[id].tsx | 1 + app/match-detail/[id].tsx | 9 +- .../basketball/basketball-overall-stats.tsx | 285 ++++++++++++++++++ components/match-detail/match-tabs.tsx | 7 +- i18n/locales/en.json | 7 + i18n/locales/hi.json | 7 + i18n/locales/id.json | 7 + i18n/locales/ms.json | 7 + i18n/locales/th.json | 7 + i18n/locales/vi.json | 7 + i18n/locales/zh.json | 7 + 11 files changed, 347 insertions(+), 4 deletions(-) create mode 100644 components/match-detail/basketball/basketball-overall-stats.tsx diff --git a/app/live-detail/[id].tsx b/app/live-detail/[id].tsx index 6219dcd..e661e01 100644 --- a/app/live-detail/[id].tsx +++ b/app/live-detail/[id].tsx @@ -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); } diff --git a/app/match-detail/[id].tsx b/app/match-detail/[id].tsx index e509f1a..62b7dcf 100644 --- a/app/match-detail/[id].tsx +++ b/app/match-detail/[id].tsx @@ -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() { ); } + case "overall": + return ; case "odds": // 将 MatchDetailData.match 转换为 LiveScoreMatch 格式 const matchForOdds = { diff --git a/components/match-detail/basketball/basketball-overall-stats.tsx b/components/match-detail/basketball/basketball-overall-stats.tsx new file mode 100644 index 0000000..40c2c3b --- /dev/null +++ b/components/match-detail/basketball/basketball-overall-stats.tsx @@ -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 ( + + + {t("detail.empty_stats")} + + + ); + } + + return ( + + {/* Card */} + + {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 ( + + + + + + {getStatLabel(item.type)} + + + + + + + + ); + })} + + + ); +} + +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 ( + + {text} + + ); +} + +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 ( + + + + + + + + + + + ); +} + +/* ----------------- 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, + }, +}); diff --git a/components/match-detail/match-tabs.tsx b/components/match-detail/match-tabs.tsx index db29fa8..498c228 100644 --- a/components/match-detail/match-tabs.tsx +++ b/components/match-detail/match-tabs.tsx @@ -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"), diff --git a/i18n/locales/en.json b/i18n/locales/en.json index fe0d285..6b821dd 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -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": { diff --git a/i18n/locales/hi.json b/i18n/locales/hi.json index 1cd85fe..6d145a1 100644 --- a/i18n/locales/hi.json +++ b/i18n/locales/hi.json @@ -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": { diff --git a/i18n/locales/id.json b/i18n/locales/id.json index 05fdc9c..3a93703 100644 --- a/i18n/locales/id.json +++ b/i18n/locales/id.json @@ -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": { diff --git a/i18n/locales/ms.json b/i18n/locales/ms.json index fb7df10..69784a4 100644 --- a/i18n/locales/ms.json +++ b/i18n/locales/ms.json @@ -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": { diff --git a/i18n/locales/th.json b/i18n/locales/th.json index 7b16919..eb02df7 100644 --- a/i18n/locales/th.json +++ b/i18n/locales/th.json @@ -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": { diff --git a/i18n/locales/vi.json b/i18n/locales/vi.json index e79cbbb..fcc9642 100644 --- a/i18n/locales/vi.json +++ b/i18n/locales/vi.json @@ -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": { diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index 478afcf..1f7018d 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -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": {