diff --git a/app/match-detail/[id].tsx b/app/match-detail/[id].tsx
index c7b9d6c..e509f1a 100644
--- a/app/match-detail/[id].tsx
+++ b/app/match-detail/[id].tsx
@@ -1,5 +1,6 @@
import { OddsCard } from "@/components/live-detail/odds-card";
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";
import { FootballScoreTable } from "@/components/match-detail/football/football-score-table";
import { GoalsCard } from "@/components/match-detail/football/goals-card";
@@ -59,7 +60,7 @@ export default function MatchDetailScreen() {
validTabs = ["info", "stats", "odds", "h2h", "chat"];
} else if (sportId === 2) {
// 篮球
- validTabs = ["info", "h2h", "chat"];
+ validTabs = ["info", "stats", "h2h", "chat"];
} else if (sportId === 3) {
// 网球
validTabs = ["info", "chat"];
@@ -163,6 +164,9 @@ export default function MatchDetailScreen() {
>
);
+ } else if (sportId === 2) {
+ // 篮球:显示统计数据
+ return ;
} else {
// 其他运动暂时显示空状态
return (
diff --git a/components/match-detail/basketball/basketball-score-table.tsx b/components/match-detail/basketball/basketball-score-table.tsx
index 6de77ee..85c87e0 100644
--- a/components/match-detail/basketball/basketball-score-table.tsx
+++ b/components/match-detail/basketball/basketball-score-table.tsx
@@ -16,22 +16,81 @@ export function BasketballScoreTable({
const { t } = useTranslation();
const { match } = data;
const bgColor = isDark ? "#1C1C1E" : "#FFF";
+ const borderColor = isDark ? "#2C2C2E" : "rgba(0,0,0,0.06)";
const headerTextColor = isDark ? "#666" : "#999";
+ const textColor = isDark ? "#FFF" : "#000";
- // 解析比分 - 篮球通常是 4 节
- const parseScore = (scoreString: string) => {
- if (!scoreString || scoreString === "-") return [0, 0, 0, 0, 0];
- // 假设格式可能是 "25-20,30-28,22-25,28-26" 或类似
- // 这里简化处理,实际需要根据 API 返回格式解析
- return [0, 0, 0, 0, 0]; // total, q1, q2, q3, q4
+ // 解析篮球比分 - 从 scores 对象获取四节比分
+ const parseBasketballScores = () => {
+ const scores = match.scores as any;
+ if (!scores || typeof scores !== "object") {
+ // 如果没有 scores 对象,尝试从 eventFinalResult 解析
+ const finalResult = match.eventFinalResult || "-";
+ const matchResult = finalResult.match(/(\d+)\s*[-–]\s*(\d+)/);
+ if (matchResult) {
+ return {
+ home: {
+ total: parseInt(matchResult[1], 10),
+ q1: 0,
+ q2: 0,
+ q3: 0,
+ q4: 0,
+ },
+ away: {
+ total: parseInt(matchResult[2], 10),
+ q1: 0,
+ q2: 0,
+ q3: 0,
+ q4: 0,
+ },
+ };
+ }
+ return {
+ home: { total: 0, q1: 0, q2: 0, q3: 0, q4: 0 },
+ away: { total: 0, q1: 0, q2: 0, q3: 0, q4: 0 },
+ };
+ }
+
+ const q1 = scores["1stQuarter"]?.[0] || { score_home: "0", score_away: "0" };
+ const q2 = scores["2ndQuarter"]?.[0] || { score_home: "0", score_away: "0" };
+ const q3 = scores["3rdQuarter"]?.[0] || { score_home: "0", score_away: "0" };
+ const q4 = scores["4thQuarter"]?.[0] || { score_home: "0", score_away: "0" };
+
+ const homeQ1 = parseInt(q1.score_home || "0", 10);
+ const homeQ2 = parseInt(q2.score_home || "0", 10);
+ const homeQ3 = parseInt(q3.score_home || "0", 10);
+ const homeQ4 = parseInt(q4.score_home || "0", 10);
+ const homeTotal = homeQ1 + homeQ2 + homeQ3 + homeQ4;
+
+ const awayQ1 = parseInt(q1.score_away || "0", 10);
+ const awayQ2 = parseInt(q2.score_away || "0", 10);
+ const awayQ3 = parseInt(q3.score_away || "0", 10);
+ const awayQ4 = parseInt(q4.score_away || "0", 10);
+ const awayTotal = awayQ1 + awayQ2 + awayQ3 + awayQ4;
+
+ return {
+ home: {
+ total: homeTotal,
+ q1: homeQ1,
+ q2: homeQ2,
+ q3: homeQ3,
+ q4: homeQ4,
+ },
+ away: {
+ total: awayTotal,
+ q1: awayQ1,
+ q2: awayQ2,
+ q3: awayQ3,
+ q4: awayQ4,
+ },
+ };
};
- const homeScores = parseScore(match.eventFinalResult || "-");
- const awayScores = parseScore(match.eventFinalResult || "-");
+ const scoreData = parseBasketballScores();
const headers = [
- t("detail.score_table.team"),
- t("detail.score_table.total"),
+ t("detail.score_table.team") || "Team",
+ t("detail.score_table.total") || "Total",
"1",
"2",
"3",
@@ -42,26 +101,26 @@ export function BasketballScoreTable({
{
logo: match.homeTeamLogo,
name: match.eventHomeTeam,
- total: homeScores[0],
- q1: homeScores[1],
- q2: homeScores[2],
- q3: homeScores[3],
- q4: homeScores[4],
+ total: scoreData.home.total,
+ q1: scoreData.home.q1,
+ q2: scoreData.home.q2,
+ q3: scoreData.home.q3,
+ q4: scoreData.home.q4,
},
{
logo: match.awayTeamLogo,
name: match.eventAwayTeam,
- total: awayScores[0],
- q1: awayScores[1],
- q2: awayScores[2],
- q3: awayScores[3],
- q4: awayScores[4],
+ total: scoreData.away.total,
+ q1: scoreData.away.q1,
+ q2: scoreData.away.q2,
+ q3: scoreData.away.q3,
+ q4: scoreData.away.q4,
},
];
return (
-
-
+
+
{headers.map((h, i) => (
{rows.map((row, idx) => (
-
+
-
-
+ {row.logo && row.logo.trim() !== "" && !row.logo.includes("placehold") ? (
+
+ ) : null}
+
{row.name}
-
+
{row.total}
- {row.q1}
- {row.q2}
- {row.q3}
- {row.q4}
+ {row.q1}
+ {row.q2}
+ {row.q3}
+ {row.q4}
))}
@@ -103,20 +164,16 @@ export function BasketballScoreTable({
const styles = StyleSheet.create({
container: {
marginHorizontal: 16,
- marginBottom: 16,
+ marginTop: 12,
borderRadius: 16,
padding: 20,
- elevation: 2,
- shadowColor: "#000",
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.05,
- shadowRadius: 8,
+ borderWidth: 1,
+ overflow: "hidden",
},
header: {
flexDirection: "row",
paddingBottom: 16,
borderBottomWidth: 1,
- borderBottomColor: "rgba(150,150,150,0.1)",
},
headerText: {
fontSize: 12,
@@ -128,10 +185,6 @@ const styles = StyleSheet.create({
alignItems: "center",
paddingVertical: 14,
},
- rowBorder: {
- borderBottomWidth: 1,
- borderBottomColor: "rgba(150,150,150,0.1)",
- },
teamCell: {
flex: 3,
flexDirection: "row",
@@ -146,7 +199,7 @@ const styles = StyleSheet.create({
teamName: {
flex: 1,
fontSize: 14,
- fontWeight: "700",
+ fontWeight: "600",
},
cellText: {
flex: 1,
diff --git a/components/match-detail/basketball/basketball-stats.tsx b/components/match-detail/basketball/basketball-stats.tsx
new file mode 100644
index 0000000..8dfc759
--- /dev/null
+++ b/components/match-detail/basketball/basketball-stats.tsx
@@ -0,0 +1,407 @@
+import { ThemedText } from "@/components/themed-text";
+import { IconSymbol } from "@/components/ui/icon-symbol";
+import { getInitials, getLogoGradient } from "@/lib/avatar-utils";
+import { MatchDetailData } from "@/types/api";
+import { Image } from "expo-image";
+import { LinearGradient } from "expo-linear-gradient";
+import React, { useMemo, useState } from "react";
+import {
+ ScrollView,
+ StyleSheet,
+ Switch,
+ TouchableOpacity,
+ View
+} from "react-native";
+
+interface BasketballStatsProps {
+ data: MatchDetailData;
+ isDark: boolean;
+}
+
+// 定义固定高度以保证左右对齐
+const HEADER_HEIGHT = 40;
+const ROW_HEIGHT = 60;
+const LEFT_COLUMN_WIDTH = 150;
+
+const COLUMNS_BASIC = [
+ { key: "pts", label: "PTS", width: 50 },
+ { key: "reb", label: "REB", width: 50 },
+ { key: "ast", label: "AST", width: 50 },
+];
+
+const COLUMNS_DETAILED = [
+ { key: "min", label: "MIN", width: 50 },
+ { key: "pts", label: "PTS", width: 50 },
+ { key: "reb", label: "REB", width: 50 },
+ { key: "ast", label: "AST", width: 50 },
+ { key: "blk", label: "BLK", width: 50 },
+ { key: "stl", label: "STL", width: 50 },
+ { key: "to", label: "TO", width: 50 },
+ { key: "pf", label: "PF", width: 50 },
+];
+
+export function BasketballStats({ data, isDark }: BasketballStatsProps) {
+ const { match } = data;
+ const [activeTab, setActiveTab] = useState<"home" | "away">("home");
+ const [isDetailed, setIsDetailed] = useState(false);
+
+ // 获取当前展示的球员列表
+ const currentPlayers = useMemo(() => {
+ if (!match.players) return [];
+ return activeTab === "home"
+ ? match.players.home_team
+ : match.players.away_team;
+ }, [match.players, activeTab]);
+
+ const columns = isDetailed ? COLUMNS_DETAILED : COLUMNS_BASIC;
+
+ const getPlayerStat = (player: any, statKey: string): string => {
+ const statMap: Record = {
+ pts: player.player_points || "0",
+ reb: player.player_total_rebounds || "0",
+ ast: player.player_assists || "0",
+ min: player.player_minutes || "0",
+ blk: player.player_blocks || "0",
+ stl: player.player_steals || "0",
+ to: player.player_turnovers || "0",
+ pf: player.player_personal_fouls || "0",
+ };
+ const value = statMap[statKey] || "0";
+ return value === "-" || value === "" ? "0" : value;
+ };
+
+ const bgColor = isDark ? "#1C1C1E" : "#FFF";
+ const borderColor = isDark ? "#2C2C2E" : "rgba(0,0,0,0.06)";
+ const textColor = isDark ? "#FFF" : "#000";
+ const secondaryText = isDark ? "#8E8E93" : "#6B7280";
+
+ // --- 渲染组件 ---
+
+ // 1. 左上角:Player 表头
+ const renderLeftHeader = () => (
+
+
+ Player
+
+
+
+ );
+
+ // 2. 左侧列:球员信息行
+ const renderLeftPlayerRow = (player: any) => {
+ const playerName = player.player || "";
+ const playerPosition = player.player_position || "";
+ const gradient = getLogoGradient(playerName);
+ const initials = getInitials(playerName);
+
+ return (
+
+
+
+ {initials}
+
+
+
+
+ {playerName}
+
+ {playerPosition && (
+
+ {playerPosition}
+
+ )}
+
+
+ );
+ };
+
+ // 3. 右上角:数据表头
+ const renderRightHeader = () => (
+
+ {columns.map((col) => (
+
+
+ {col.label}
+
+
+ ))}
+
+ );
+
+ // 4. 右侧列:数据行
+ const renderRightDataRow = (player: any) => (
+
+ {columns.map((col) => (
+
+
+ {getPlayerStat(player, col.key)}
+
+
+ ))}
+
+ );
+
+ // 队名/Logo逻辑
+ const homeTeamName = match.eventHomeTeam || "";
+ const awayTeamName = match.eventAwayTeam || "";
+ const homeTeamLogo = match.homeTeamLogo || "";
+ const awayTeamLogo = match.awayTeamLogo || "";
+ const hasHomeLogo = homeTeamLogo && homeTeamLogo.trim() !== "" && !homeTeamLogo.includes("placehold");
+ const hasAwayLogo = awayTeamLogo && awayTeamLogo.trim() !== "" && !awayTeamLogo.includes("placehold");
+ const homeGradient = getLogoGradient(homeTeamName);
+ const awayGradient = getLogoGradient(awayTeamName);
+ const homeInitials = getInitials(homeTeamName);
+ const awayInitials = getInitials(awayTeamName);
+
+ return (
+
+ {/* 顶部控制栏 */}
+
+
+
+ 统计外观
+
+
+ 详细视图
+
+
+
+
+
+ {/* 队伍切换 Tab */}
+
+ setActiveTab("home")}
+ >
+ {hasHomeLogo ? (
+
+ ) : (
+
+ {homeInitials}
+
+ )}
+
+ setActiveTab("away")}
+ >
+ {hasAwayLogo ? (
+
+ ) : (
+
+ {awayInitials}
+
+ )}
+
+
+
+ {/* 核心表格区域:Vertical ScrollView 包裹整个表格 */}
+
+
+
+ {/* 左侧区域:固定不横向滚动 */}
+
+ {/* Header */}
+ {renderLeftHeader()}
+ {/* Players List */}
+
+ {currentPlayers?.map((player, index) => (
+
+ {renderLeftPlayerRow(player)}
+
+ ))}
+
+
+
+ {/* 右侧区域:整体横向滚动 */}
+
+
+ {/* Header */}
+ {renderRightHeader()}
+ {/* Data List */}
+
+ {currentPlayers?.map((player, index) => (
+
+ {renderRightDataRow(player)}
+
+ ))}
+
+
+
+
+
+
+ {/* Empty State */}
+ {(!currentPlayers || currentPlayers.length === 0) && (
+
+ 暂无球员数据
+
+ )}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ paddingTop: 12,
+ },
+ controlsHeader: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingHorizontal: 16,
+ marginBottom: 12,
+ },
+ controlTitle: {
+ fontSize: 14,
+ fontWeight: "600",
+ },
+ controlSubtitle: {
+ fontSize: 12,
+ marginTop: 2,
+ },
+ teamTabsContainer: {
+ flexDirection: "row",
+ marginHorizontal: 16,
+ marginBottom: 12,
+ borderRadius: 12,
+ height: 44,
+ borderWidth: 1,
+ overflow: "hidden",
+ },
+ teamTab: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ teamTabLogo: {
+ width: 24,
+ height: 24,
+ },
+ teamTabLogoGradient: {
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ teamTabLogoText: {
+ fontSize: 10,
+ fontWeight: "700",
+ color: "rgba(255, 255, 255, 0.92)",
+ },
+ tableContainer: {
+ marginHorizontal: 16,
+ marginBottom: 20,
+ borderRadius: 16,
+ borderWidth: 1,
+ overflow: "hidden", // 确保圆角生效
+ },
+ // 左侧样式
+ headerCell: {
+ flexDirection: "row",
+ alignItems: "center",
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ paddingHorizontal: 12,
+ width: "100%",
+ },
+ playerRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ paddingHorizontal: 8,
+ width: "100%",
+ },
+ // 右侧样式
+ dataHeaderRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ paddingHorizontal: 16, // 给右侧表头一些左右padding,美观
+ },
+ dataRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ paddingHorizontal: 16,
+ },
+ // 通用文本
+ headerText: {
+ fontSize: 12,
+ fontWeight: "700",
+ },
+ statText: {
+ fontSize: 14,
+ fontWeight: "500",
+ },
+ // 球员头像与信息
+ avatarContainer: {
+ marginRight: 8,
+ },
+ avatarGradient: {
+ width: 32,
+ height: 32,
+ borderRadius: 16,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ avatarText: {
+ fontSize: 12,
+ fontWeight: "700",
+ color: "rgba(255, 255, 255, 0.92)",
+ },
+ playerInfo: {
+ flex: 1,
+ justifyContent: "center",
+ },
+ playerName: {
+ fontSize: 13,
+ fontWeight: "600",
+ marginBottom: 2,
+ },
+ playerNumPos: {
+ fontSize: 11,
+ },
+ emptyState: {
+ padding: 40,
+ alignItems: "center",
+ },
+});
\ No newline at end of file
diff --git a/components/match-detail/match-tabs.tsx b/components/match-detail/match-tabs.tsx
index bcb1928..db29fa8 100644
--- a/components/match-detail/match-tabs.tsx
+++ b/components/match-detail/match-tabs.tsx
@@ -63,8 +63,29 @@ export function MatchTabs({
},
];
} else if (sportId === 2) {
- // 篮球
- return commonTabs;
+ // 篮球: 详情、统计数据、交锋往绩、聊天
+ return [
+ {
+ id: "info",
+ label: t("detail.tabs.info"),
+ icon: "document-text-outline",
+ },
+ {
+ id: "stats",
+ label: t("detail.tabs.stats"),
+ icon: "stats-chart-outline",
+ },
+ {
+ id: "h2h",
+ label: t("detail.tabs.h2h"),
+ icon: "swap-horizontal-outline",
+ },
+ {
+ id: "chat",
+ label: t("detail.tabs.chat"),
+ icon: "chatbubbles-outline",
+ },
+ ];
} else if (sportId === 3) {
// 网球: 详情、聊天
return [
diff --git a/types/api.ts b/types/api.ts
index 1ffb97d..c31bfe2 100644
--- a/types/api.ts
+++ b/types/api.ts
@@ -213,6 +213,26 @@ export interface Player {
number?: number | string;
player_type?: string; // 位置/角色,例如 Goalkeepers
position?: string; // 兜底位置字段
+ // 篮球相关字段
+ player_id?: number;
+ player_points?: string;
+ player_assists?: string;
+ player_minutes?: string;
+ player_blocks?: string;
+ player_steals?: string;
+ player_turnovers?: string;
+ player_plus_minus?: string;
+ player_personal_fouls?: string;
+ player_total_rebounds?: string;
+ player_defense_rebounds?: string;
+ player_offence_rebounds?: string;
+ player_field_goals_made?: string;
+ player_field_goals_attempts?: string;
+ player_freethrows_goals_made?: string;
+ player_freethrows_goals_attempts?: string;
+ player_threepoint_goals_made?: string;
+ player_threepoint_goals_attempts?: string;
+ player_oncourt?: string;
}
export interface UpcomingMatch {
@@ -312,7 +332,17 @@ export interface MatchDetailData {
firstPlayerKey?: string;
secondPlayerKey?: string;
eventGameResult?: string;
- scores?: any[];
+ scores?: any[] | {
+ "1stQuarter"?: Array<{ score_home: string; score_away: string }>;
+ "2ndQuarter"?: Array<{ score_home: string; score_away: string }>;
+ "3rdQuarter"?: Array<{ score_home: string; score_away: string }>;
+ "4thQuarter"?: Array<{ score_home: string; score_away: string }>;
+ };
+ stats?: Array<{
+ type: string;
+ home: string;
+ away: string;
+ }>;
events?: MatchEvents;
players?: {
home_team?: Player[];