diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 240915c..c18ef58 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -622,7 +622,9 @@ export default function HomeScreen() {
/>
) : (
)}
diff --git a/components/match-card-league.tsx b/components/match-card-league.tsx
index 88eb1f7..0c04c42 100644
--- a/components/match-card-league.tsx
+++ b/components/match-card-league.tsx
@@ -121,7 +121,7 @@ export function MatchCardLeague({
{/* 如果有状态字段 match.meta (如 'FT', 'AET'), 优先显示,否则显示时间 */}
- {(match.meta || match.time || "").toUpperCase()}
+ {(match.time || "").toUpperCase()}
diff --git a/components/matches-by-league.tsx b/components/matches-by-league.tsx
index 8b3d760..509f4b2 100644
--- a/components/matches-by-league.tsx
+++ b/components/matches-by-league.tsx
@@ -1,7 +1,8 @@
import { MatchCardLeague } from "@/components/match-card-league";
import { ThemedText } from "@/components/themed-text";
import { useTheme } from "@/context/ThemeContext";
-import { Match } from "@/types/api";
+import { fetchLeagues, fetchTodayMatches } from "@/lib/api";
+import { League, Match } from "@/types/api";
import React, { useState } from "react";
import {
Image,
@@ -23,70 +24,97 @@ if (
}
interface MatchesByLeagueProps {
- matches: Match[];
+ sportId: number;
+ date: Date;
+ timezone: string;
onFavoriteToggle?: (matchId: string, isFav: boolean) => void;
- /**
- * 是否支持折叠收起
- * @default true
- */
enableCollapsible?: boolean;
}
-
export function MatchesByLeague({
- matches,
+ sportId,
+ date,
+ timezone,
onFavoriteToggle,
enableCollapsible = true,
}: MatchesByLeagueProps) {
const { theme } = useTheme();
const isDark = theme === "dark";
- // 数据分组逻辑
- const matchesByLeague = React.useMemo(() => {
- const grouped: Record = {};
- matches.forEach((match) => {
- const league = match.league || match.leagueName || "其他";
- if (!grouped[league]) {
- grouped[league] = [];
- }
- grouped[league].push(match);
- });
- return grouped;
- }, [matches]);
-
- const leagueNames = Object.keys(matchesByLeague);
-
- // 状态:记录哪些联赛被折叠了 (Key 为联赛名称),默认全部收起
- const [collapsedSections, setCollapsedSections] = useState<
+ const [leagues, setLeagues] = useState([]);
+ const [collapsed, setCollapsed] = useState>({});
+ const [matchesByLeagueKey, setMatchesByLeagueKey] = useState<
+ Record
+ >({});
+ const [loadingLeagueKey, setLoadingLeagueKey] = useState<
Record
>({});
- // 当联赛列表变化时,确保所有联赛默认都是收起状态
+ const dateStr = React.useMemo(() => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+ }, [date]);
+
React.useEffect(() => {
- setCollapsedSections((prev) => {
- const updated: Record = {};
- leagueNames.forEach((name) => {
- updated[name] = prev[name] !== undefined ? prev[name] : true;
- });
- return updated;
- });
- }, [leagueNames.join(",")]);
+ let mounted = true;
+ fetchLeagues({
+ sportId,
+ date: dateStr,
+ page: 1,
+ pageSize: 200,
+ sortBy: "matchCount",
+ sortOrder: "desc",
+ })
+ .then((res) => {
+ if (!mounted) return;
+ setLeagues(res.list);
+ setCollapsed((prev) => {
+ const next: Record = {};
+ res.list.forEach((l) => {
+ next[l.key] = prev[l.key] !== undefined ? prev[l.key] : true;
+ });
+ return next;
+ });
+ setMatchesByLeagueKey({});
+ setLoadingLeagueKey({});
+ })
+ .catch(() => { });
+ return () => {
+ mounted = false;
+ };
+ }, [sportId, dateStr]);
- // 切换折叠状态
- const toggleCollapse = (leagueName: string) => {
+ const toggleCollapse = async (leagueKey: string) => {
if (!enableCollapsible) return;
-
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
- setCollapsedSections((prev) => ({
- ...prev,
- [leagueName]: !prev[leagueName],
- }));
+ setCollapsed((prev) => ({ ...prev, [leagueKey]: !prev[leagueKey] }));
+
+ const nextCollapsed = !collapsed[leagueKey];
+ if (nextCollapsed) return;
+ if (matchesByLeagueKey[leagueKey]) return;
+
+ setLoadingLeagueKey((prev) => ({ ...prev, [leagueKey]: true }));
+ try {
+ const res = await fetchTodayMatches({
+ sportId,
+ date: dateStr,
+ timezone,
+ leagueKey,
+ page: 1,
+ pageSize: 50,
+ });
+ setMatchesByLeagueKey((prev) => ({ ...prev, [leagueKey]: res.list }));
+ } finally {
+ setLoadingLeagueKey((prev) => ({ ...prev, [leagueKey]: false }));
+ }
};
- if (leagueNames.length === 0) {
+ if (leagues.length === 0) {
return (
- 暂无比赛
+ 暂无联赛
);
}
@@ -99,54 +127,51 @@ export function MatchesByLeague({
]}
contentContainerStyle={{ paddingBottom: 40 }}
>
- {leagueNames.map((leagueName) => {
- const leagueMatches = matchesByLeague[leagueName];
- // 取该组第一场比赛的数据作为头部信息的来源(图标、国家等)
- const firstMatch = leagueMatches[0];
- const isCollapsed = collapsedSections[leagueName];
+ {leagues.map((league) => {
+ const isCollapsed = collapsed[league.key] !== false;
+ const leagueMatches = matchesByLeagueKey[league.key] || [];
+ const isLoading = !!loadingLeagueKey[league.key];
return (
-
- {/* 联赛头部 */}
+
toggleCollapse(leagueName)}
+ activeOpacity={enableCollapsible && league.matchCount > 0 ? 0.7 : 1}
+ onPress={() => {
+ if (enableCollapsible && league.matchCount > 0) {
+ toggleCollapse(league.key);
+ }
+ }}
style={styles.leagueHeaderWrapper}
>
- {/* 联赛 Logo */}
- {/* 联赛名称 */}
- {leagueName}
+ {league.name}
- {/* 国家信息行 */}
- {firstMatch.countryName || (firstMatch as any).countryName || "International"}
+ {league.countryName || "International"}
- {/* 比赛数量 */}
- {leagueMatches.length}
+ {league.matchCount}
- {/* 折叠箭头 (仅当支持折叠时显示) */}
- {enableCollapsible && (
+ {enableCollapsible && league.matchCount > 0 && (
{isCollapsed ? "⌄" : "⌃"}
@@ -154,23 +179,74 @@ export function MatchesByLeague({
- {/* 比赛列表内容 (根据状态显示/隐藏) */}
{!isCollapsed && (
- {leagueMatches.map((match, index) => (
-
-
-
- ))}
+ {isLoading ? (
+ <>
+ {[0, 1, 2].map((i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+ >
+ ) : (
+ leagueMatches.map((match, index) => (
+
+
+
+ ))
+ )}
)}
@@ -252,13 +328,67 @@ const styles = StyleSheet.create({
marginHorizontal: 16,
overflow: "hidden",
},
+ // 布局与 MatchCardLeague 保持一致,便于骨架对齐
+ leftColumn: {
+ width: 50,
+ justifyContent: "center",
+ alignItems: "flex-start",
+ marginRight: 8,
+ },
+ teamsColumn: {
+ flex: 1,
+ justifyContent: "center",
+ paddingRight: 8,
+ },
+ rightWrapper: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
matchCardWrapper: {
- // 卡片包装器
},
matchCardDivider: {
borderBottomWidth: 0.5,
borderBottomColor: "#3A3A3C",
},
+ favoriteButton: {
+ paddingLeft: 12,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ skeletonRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ paddingHorizontal: 16,
+ paddingVertical: 14,
+ },
+ skeletonLine: {
+ height: 8,
+ borderRadius: 4,
+ backgroundColor: "#2C2C2E",
+ },
+ skeletonAvatar: {
+ width: 20,
+ height: 20,
+ borderRadius: 10,
+ backgroundColor: "#2C2C2E",
+ marginRight: 10,
+ },
+ skeletonTeamRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ skeletonScoreBox: {
+ width: 24,
+ height: 32,
+ borderRadius: 8,
+ backgroundColor: "#2C2C2E",
+ },
+ skeletonCircle: {
+ width: 16,
+ height: 16,
+ borderRadius: 8,
+ backgroundColor: "#2C2C2E",
+ },
emptyContainer: {
flex: 1,
justifyContent: "center",
diff --git a/types/api.ts b/types/api.ts
index 94c127d..aac8012 100644
--- a/types/api.ts
+++ b/types/api.ts
@@ -88,11 +88,11 @@ export interface LiveScoreMatch {
substitutes?: {
time: string;
home_scorer:
- | { in: string; out: string; in_id: number; out_id: number }
- | any[];
+ | { in: string; out: string; in_id: number; out_id: number }
+ | any[];
away_scorer:
- | { in: string; out: string; in_id: number; out_id: number }
- | any[];
+ | { in: string; out: string; in_id: number; out_id: number }
+ | any[];
info: string;
info_time: string;
score: string;
@@ -153,7 +153,7 @@ export interface League {
isActive: boolean;
createdAt: string;
updatedAt: string;
- matchCount?: number;
+ matchCount: number;
}
export interface GoalEvent {