详情区分运动
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
|
import { BasketballScoreTable } from "@/components/match-detail/basketball/basketball-score-table";
|
||||||
|
import { FootballScoreTable } from "@/components/match-detail/football/football-score-table";
|
||||||
import { LeagueInfo } from "@/components/match-detail/league-info";
|
import { LeagueInfo } from "@/components/match-detail/league-info";
|
||||||
import { MatchInfoCard } from "@/components/match-detail/match-info-card";
|
import { MatchInfoCard } from "@/components/match-detail/match-info-card";
|
||||||
import { MatchTabs } from "@/components/match-detail/match-tabs";
|
import { MatchTabs } from "@/components/match-detail/match-tabs";
|
||||||
import { ScoreHeader } from "@/components/match-detail/score-header";
|
import { ScoreHeader } from "@/components/match-detail/score-header";
|
||||||
import { ScoreTable } from "@/components/match-detail/score-table";
|
|
||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { ThemedView } from "@/components/themed-view";
|
import { ThemedView } from "@/components/themed-view";
|
||||||
import { Colors } from "@/constants/theme";
|
import { Colors } from "@/constants/theme";
|
||||||
import { useTheme } from "@/context/ThemeContext";
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
import { fetchMatchDetail } from "@/lib/api";
|
import { fetchMatchDetail } from "@/lib/api";
|
||||||
import { MatchDetailData } from "@/types/api";
|
import { MatchDetailData } from "@/types/api";
|
||||||
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
@@ -40,6 +41,30 @@ export default function MatchDetailScreen() {
|
|||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
// 当数据加载完成且 sportId 变化时,检查并重置 activeTab
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.match?.sportId) {
|
||||||
|
const sportId = data.match.sportId;
|
||||||
|
let validTabs: string[] = [];
|
||||||
|
|
||||||
|
if (sportId === 1) {
|
||||||
|
// 足球
|
||||||
|
validTabs = ["info", "stats", "odds", "h2h", "chat"];
|
||||||
|
} else if (sportId === 2) {
|
||||||
|
// 篮球
|
||||||
|
validTabs = ["info", "h2h", "chat"];
|
||||||
|
} else {
|
||||||
|
// 默认
|
||||||
|
validTabs = ["info", "h2h", "chat"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前 activeTab 不在有效标签列表中,重置为第一个
|
||||||
|
if (!validTabs.includes(activeTab)) {
|
||||||
|
setActiveTab(validTabs[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data?.match?.sportId]);
|
||||||
|
|
||||||
const loadMatchDetail = async () => {
|
const loadMatchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -73,13 +98,51 @@ export default function MatchDetailScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderTabContent = () => {
|
const renderTabContent = () => {
|
||||||
|
const sportId = data?.match?.sportId || 1;
|
||||||
|
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case "info":
|
case "info":
|
||||||
|
// 根据 sportId 显示不同的详情组件
|
||||||
|
if (sportId === 1) {
|
||||||
|
// 足球:显示 FootballScoreTable (半场/全场) 和 MatchInfoCard
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FootballScoreTable data={data} isDark={isDark} />
|
||||||
|
<MatchInfoCard data={data} isDark={isDark} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else if (sportId === 2) {
|
||||||
|
// 篮球:显示 BasketballScoreTable (4节) 和 MatchInfoCard
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BasketballScoreTable data={data} isDark={isDark} />
|
||||||
|
<MatchInfoCard data={data} isDark={isDark} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 默认使用足球组件
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FootballScoreTable data={data} isDark={isDark} />
|
||||||
|
<MatchInfoCard data={data} isDark={isDark} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "stats":
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={styles.emptyContent}>
|
||||||
<ScoreTable data={data} isDark={isDark} />
|
<ThemedText style={styles.emptyText}>
|
||||||
<MatchInfoCard data={data} isDark={isDark} />
|
{t("detail.empty_stats")}
|
||||||
</>
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case "odds":
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyContent}>
|
||||||
|
<ThemedText style={styles.emptyText}>
|
||||||
|
{t("detail.empty_odds")}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
case "h2h":
|
case "h2h":
|
||||||
return (
|
return (
|
||||||
@@ -115,6 +178,7 @@ export default function MatchDetailScreen() {
|
|||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
|
sportId={data.match.sportId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{renderTabContent()}
|
{renderTabContent()}
|
||||||
|
|||||||
@@ -4,45 +4,55 @@ import React from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Image, StyleSheet, View } from "react-native";
|
import { Image, StyleSheet, View } from "react-native";
|
||||||
|
|
||||||
interface ScoreTableProps {
|
interface BasketballScoreTableProps {
|
||||||
data: MatchDetailData;
|
data: MatchDetailData;
|
||||||
isDark: boolean;
|
isDark: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ScoreTable({ data, isDark }: ScoreTableProps) {
|
export function BasketballScoreTable({ data, isDark }: BasketballScoreTableProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { match } = data;
|
const { match } = data;
|
||||||
const bgColor = isDark ? "#1C1C1E" : "#FFF";
|
const bgColor = isDark ? "#1C1C1E" : "#FFF";
|
||||||
const headerTextColor = isDark ? "#666" : "#999";
|
const headerTextColor = isDark ? "#666" : "#999";
|
||||||
|
|
||||||
// Mock quarters for demo purposes as seen in screenshot
|
// 解析比分 - 篮球通常是 4 节
|
||||||
// In real app, these would come from the API (e.g. match.eventQuarter or specific fields)
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
const homeScores = parseScore(match.eventFinalResult || "-");
|
||||||
|
const awayScores = parseScore(match.eventFinalResult || "-");
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
t("detail.score_table.team"),
|
t("detail.score_table.team"),
|
||||||
t("detail.score_table.total"),
|
t("detail.score_table.total"),
|
||||||
"1",
|
"Q1",
|
||||||
"2",
|
"Q2",
|
||||||
"3",
|
"Q3",
|
||||||
"4",
|
"Q4",
|
||||||
];
|
];
|
||||||
|
|
||||||
const rows = [
|
const rows = [
|
||||||
{
|
{
|
||||||
logo: match.homeTeamLogo,
|
logo: match.homeTeamLogo,
|
||||||
name: match.eventHomeTeam,
|
name: match.eventHomeTeam,
|
||||||
total: 0,
|
total: homeScores[0],
|
||||||
q1: 0,
|
q1: homeScores[1],
|
||||||
q2: 0,
|
q2: homeScores[2],
|
||||||
q3: 0,
|
q3: homeScores[3],
|
||||||
q4: 0,
|
q4: homeScores[4],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
logo: match.awayTeamLogo,
|
logo: match.awayTeamLogo,
|
||||||
name: match.eventAwayTeam,
|
name: match.eventAwayTeam,
|
||||||
total: 0,
|
total: awayScores[0],
|
||||||
q1: 0,
|
q1: awayScores[1],
|
||||||
q2: 0,
|
q2: awayScores[2],
|
||||||
q3: 0,
|
q3: awayScores[3],
|
||||||
q4: 0,
|
q4: awayScores[4],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -70,6 +80,9 @@ export function ScoreTable({ data, isDark }: ScoreTableProps) {
|
|||||||
<View key={idx} style={[styles.row, idx === 0 && styles.rowBorder]}>
|
<View key={idx} style={[styles.row, idx === 0 && styles.rowBorder]}>
|
||||||
<View style={styles.teamCell}>
|
<View style={styles.teamCell}>
|
||||||
<Image source={{ uri: row.logo }} style={styles.teamLogo} />
|
<Image source={{ uri: row.logo }} style={styles.teamLogo} />
|
||||||
|
<ThemedText style={styles.teamName} numberOfLines={1}>
|
||||||
|
{row.name}
|
||||||
|
</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
<ThemedText style={styles.cellText}>{row.total}</ThemedText>
|
<ThemedText style={styles.cellText}>{row.total}</ThemedText>
|
||||||
<ThemedText style={styles.cellText}>{row.q1}</ThemedText>
|
<ThemedText style={styles.cellText}>{row.q1}</ThemedText>
|
||||||
@@ -87,7 +100,6 @@ const styles = StyleSheet.create({
|
|||||||
margin: 16,
|
margin: 16,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
// Shadow
|
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOffset: { width: 0, height: 2 },
|
shadowOffset: { width: 0, height: 2 },
|
||||||
@@ -117,12 +129,18 @@ const styles = StyleSheet.create({
|
|||||||
flex: 3,
|
flex: 3,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
},
|
},
|
||||||
teamLogo: {
|
teamLogo: {
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
resizeMode: "contain",
|
resizeMode: "contain",
|
||||||
},
|
},
|
||||||
|
teamName: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
cellText: {
|
cellText: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
141
components/match-detail/football/football-score-table.tsx
Normal file
141
components/match-detail/football/football-score-table.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { ThemedText } from "@/components/themed-text";
|
||||||
|
import { MatchDetailData } from "@/types/api";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Image, StyleSheet, View } from "react-native";
|
||||||
|
|
||||||
|
interface FootballScoreTableProps {
|
||||||
|
data: MatchDetailData;
|
||||||
|
isDark: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析足球比分字符串,例如 "2-1" 或 "1-0"
|
||||||
|
function parseFootballScore(scoreString: string): number[] {
|
||||||
|
if (!scoreString || scoreString === "-") return [0, 0];
|
||||||
|
const parts = scoreString.split("-");
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return [parseInt(parts[0]) || 0, parseInt(parts[1]) || 0];
|
||||||
|
}
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FootballScoreTable({ data, isDark }: FootballScoreTableProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { match } = data;
|
||||||
|
const bgColor = isDark ? "#1C1C1E" : "#FFF";
|
||||||
|
const headerTextColor = isDark ? "#666" : "#999";
|
||||||
|
|
||||||
|
// 解析足球比分
|
||||||
|
const finalScore = parseFootballScore(match.eventFinalResult || "-");
|
||||||
|
const halftimeScore = parseFootballScore(match.eventHalftimeResult || "-");
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
t("detail.score_table.team"),
|
||||||
|
t("detail.score_table.total"),
|
||||||
|
t("detail.score_table.halftime"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
{
|
||||||
|
logo: match.homeTeamLogo,
|
||||||
|
name: match.eventHomeTeam,
|
||||||
|
total: finalScore[0],
|
||||||
|
halftime: halftimeScore[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
logo: match.awayTeamLogo,
|
||||||
|
name: match.eventAwayTeam,
|
||||||
|
total: finalScore[1],
|
||||||
|
halftime: halftimeScore[1],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: bgColor }]}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
{headers.map((h, i) => (
|
||||||
|
<ThemedText
|
||||||
|
key={h}
|
||||||
|
style={[
|
||||||
|
styles.headerText,
|
||||||
|
{
|
||||||
|
color: headerTextColor,
|
||||||
|
flex: i === 0 ? 3 : 1,
|
||||||
|
textAlign: i === 0 ? "left" : "center",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{h}
|
||||||
|
</ThemedText>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{rows.map((row, idx) => (
|
||||||
|
<View key={idx} style={[styles.row, idx === 0 && styles.rowBorder]}>
|
||||||
|
<View style={styles.teamCell}>
|
||||||
|
<Image source={{ uri: row.logo }} style={styles.teamLogo} />
|
||||||
|
<ThemedText style={styles.teamName} numberOfLines={1}>
|
||||||
|
{row.name}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
<ThemedText style={styles.cellText}>{row.total}</ThemedText>
|
||||||
|
<ThemedText style={styles.cellText}>{row.halftime}</ThemedText>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
margin: 16,
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
elevation: 2,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: "row",
|
||||||
|
paddingBottom: 12,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: "rgba(150,150,150,0.2)",
|
||||||
|
},
|
||||||
|
headerText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingVertical: 12,
|
||||||
|
},
|
||||||
|
rowBorder: {
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: "rgba(150,150,150,0.1)",
|
||||||
|
},
|
||||||
|
teamCell: {
|
||||||
|
flex: 3,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
teamLogo: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
resizeMode: "contain",
|
||||||
|
},
|
||||||
|
teamName: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
cellText: {
|
||||||
|
flex: 1,
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -7,17 +7,41 @@ interface MatchTabsProps {
|
|||||||
activeTab: string;
|
activeTab: string;
|
||||||
onTabChange: (tab: string) => void;
|
onTabChange: (tab: string) => void;
|
||||||
isDark: boolean;
|
isDark: boolean;
|
||||||
|
sportId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MatchTabs({ activeTab, onTabChange, isDark }: MatchTabsProps) {
|
export function MatchTabs({ activeTab, onTabChange, isDark, sportId }: MatchTabsProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const containerBg = isDark ? "#121212" : "#F8F8F8";
|
const containerBg = isDark ? "#121212" : "#F8F8F8";
|
||||||
|
|
||||||
const tabs = [
|
// 根据 sportId 动态生成标签
|
||||||
{ id: "info", label: t("detail.tabs.info") },
|
const getTabs = () => {
|
||||||
{ id: "h2h", label: t("detail.tabs.h2h") },
|
if (sportId === 1) {
|
||||||
{ id: "chat", label: t("detail.tabs.chat") },
|
// 足球: 详情、统计数据、赔率、交锋往绩、聊天
|
||||||
];
|
return [
|
||||||
|
{ id: "info", label: t("detail.tabs.info") },
|
||||||
|
{ id: "stats", label: t("detail.tabs.stats") },
|
||||||
|
{ id: "odds", label: t("detail.tabs.odds") },
|
||||||
|
{ id: "h2h", label: t("detail.tabs.h2h") },
|
||||||
|
{ id: "chat", label: t("detail.tabs.chat") },
|
||||||
|
];
|
||||||
|
} else if (sportId === 2) {
|
||||||
|
// 篮球: 详情、交锋往绩、聊天
|
||||||
|
return [
|
||||||
|
{ id: "info", label: t("detail.tabs.info") },
|
||||||
|
{ id: "h2h", label: t("detail.tabs.h2h") },
|
||||||
|
{ id: "chat", label: t("detail.tabs.chat") },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// 默认: 详情、交锋往绩、聊天
|
||||||
|
return [
|
||||||
|
{ id: "info", label: t("detail.tabs.info") },
|
||||||
|
{ id: "h2h", label: t("detail.tabs.h2h") },
|
||||||
|
{ id: "chat", label: t("detail.tabs.chat") },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = getTabs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: containerBg }]}>
|
<View style={[styles.container, { backgroundColor: containerBg }]}>
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
"not_found": "Match data not found",
|
"not_found": "Match data not found",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"info": "Details",
|
"info": "Details",
|
||||||
|
"stats": "Statistics",
|
||||||
|
"odds": "Odds",
|
||||||
"h2h": "H2H",
|
"h2h": "H2H",
|
||||||
"chat": "Chat"
|
"chat": "Chat"
|
||||||
},
|
},
|
||||||
@@ -50,9 +52,12 @@
|
|||||||
},
|
},
|
||||||
"score_table": {
|
"score_table": {
|
||||||
"team": "Team",
|
"team": "Team",
|
||||||
"total": "Total"
|
"total": "Full Time",
|
||||||
|
"halftime": "Half Time"
|
||||||
},
|
},
|
||||||
"halftime": "Half: {{score}}",
|
"halftime": "Half: {{score}}",
|
||||||
|
"empty_stats": "No statistics data",
|
||||||
|
"empty_odds": "No odds data",
|
||||||
"empty_h2h": "No H2H data",
|
"empty_h2h": "No H2H data",
|
||||||
"empty_chat": "Chat is not available"
|
"empty_chat": "Chat is not available"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
"not_found": "未找到比赛数据",
|
"not_found": "未找到比赛数据",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"info": "详情",
|
"info": "详情",
|
||||||
|
"stats": "统计数据",
|
||||||
|
"odds": "赔率",
|
||||||
"h2h": "交锋往绩",
|
"h2h": "交锋往绩",
|
||||||
"chat": "聊天"
|
"chat": "聊天"
|
||||||
},
|
},
|
||||||
@@ -50,9 +52,12 @@
|
|||||||
},
|
},
|
||||||
"score_table": {
|
"score_table": {
|
||||||
"team": "球队",
|
"team": "球队",
|
||||||
"total": "Total"
|
"total": "全场",
|
||||||
|
"halftime": "半场"
|
||||||
},
|
},
|
||||||
"halftime": "半场: {{score}}",
|
"halftime": "半场: {{score}}",
|
||||||
|
"empty_stats": "暂无统计数据",
|
||||||
|
"empty_odds": "暂无赔率数据",
|
||||||
"empty_h2h": "暂无交锋数据",
|
"empty_h2h": "暂无交锋数据",
|
||||||
"empty_chat": "聊天功能暂未开启"
|
"empty_chat": "聊天功能暂未开启"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user