diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 42108ca..240915c 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -49,6 +49,9 @@ export default function HomeScreen() { const [liveLeagueIdByMatchId, setLiveLeagueIdByMatchId] = useState< Record >({}); + const [page, setPage] = useState(1); + const [total, setTotal] = useState(0); + const [loadingMore, setLoadingMore] = useState(false); const deviceTimeZone = useMemo(() => { try { @@ -96,6 +99,11 @@ export default function HomeScreen() { } }, [selectedSportId, selectedDate]); + useEffect(() => { + setPage(1); + setTotal(0); + }, [selectedSportId, selectedDate, state.selectedLeagueKey]); + const timezoneLabel = useMemo(() => { // 仅展示 UTC 偏移(不展示时区名) const offsetMin = -now.getTimezoneOffset(); @@ -303,8 +311,8 @@ export default function HomeScreen() { try { if (selectedSportId !== null) { setLoadingLeagues(true); - const list = await fetchLeagues(selectedSportId, ""); - setLeagues(list); + const res = await fetchLeagues({ sportId: selectedSportId }); + setLeagues(res.list); } } catch (e) { console.error(e); @@ -316,11 +324,16 @@ export default function HomeScreen() { const loadMatches = async (sportId: number) => { setLoading(true); try { - const list = await fetchTodayMatches( + const res = await fetchTodayMatches({ sportId, - selectedDate, - deviceTimeZone, - ); + date: selectedDate, + timezone: deviceTimeZone, + leagueKey: state.selectedLeagueKey || "", + page: 1, + pageSize: 50, + }); + setPage(1); + setTotal(res.total); const normalizeDate = (d: Date) => { const year = d.getFullYear(); @@ -333,7 +346,7 @@ export default function HomeScreen() { const selectedStr = normalizeDate(selectedDate); const shouldMergeLive = selectedStr === todayStr; - let merged: Match[] = list.map((m) => ({ + let merged: Match[] = res.list.map((m) => ({ ...m, date: m.date || selectedStr, sportId: m.sportId ?? sportId, @@ -402,11 +415,9 @@ export default function HomeScreen() { let listWithFavStatus = merged; if (token) { - // 直接传递 match.id 查询是否收藏,并更新列表状态 listWithFavStatus = await Promise.all( merged.map(async (m) => { try { - // 查询比赛是否已被收藏 const favRes = await checkFavorite("match", m.id); return { ...m, fav: favRes.isFavorite }; } catch (error) { @@ -434,6 +445,41 @@ export default function HomeScreen() { } }; + const loadMoreMatches = async () => { + if (loadingMore || loading) return; + if (!selectedSportId) return; + if (matches.length >= total) return; + + setLoadingMore(true); + try { + const nextPage = page + 1; + const res = await fetchTodayMatches({ + sportId: selectedSportId, + date: selectedDate, + timezone: deviceTimeZone, + leagueKey: state.selectedLeagueKey || "", + page: nextPage, + pageSize: 50, + }); + + setPage(nextPage); + setTotal(res.total); + + setMatches((prev) => { + const byId = new Map(); + prev.forEach((m) => byId.set(m.id, m)); + res.list.forEach((m) => { + if (!byId.has(m.id)) byId.set(m.id, m); + }); + return Array.from(byId.values()); + }); + } catch (e) { + console.error(e); + } finally { + setLoadingMore(false); + } + }; + const handleFavoriteToggle = (matchId: string, isFav: boolean) => { setMatches((prev) => { const updated = prev.map((m) => @@ -558,6 +604,15 @@ export default function HomeScreen() { renderItem={({ item }) => ( )} + onEndReached={loadMoreMatches} + onEndReachedThreshold={0.4} + ListFooterComponent={ + loadingMore ? ( + + + + ) : null + } contentContainerStyle={styles.listContent} ListEmptyComponent={ @@ -650,4 +705,9 @@ const styles = StyleSheet.create({ padding: 16, paddingTop: 8, }, + footer: { + paddingVertical: 16, + alignItems: "center", + justifyContent: "center", + }, }); diff --git a/lib/api.ts b/lib/api.ts index 5e2ae7a..0f630b4 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -115,22 +115,22 @@ export const fetchCountries = async (): Promise => { } }; -export const fetchLeagues = async ( - sportId: number, - countryKey: string -): Promise => { +export const fetchLeagues = async (params: { + sportId?: number; + countryKey?: string; + date?: string; + page?: number; + pageSize?: number; + sortBy?: "name" | "matchCount" | "createdAt"; + sortOrder?: "asc" | "desc"; +}): Promise> => { try { const response = await apiClient.get>>( API_ENDPOINTS.LEAGUES, - { - params: { - sportId, - countryKey, - }, - } + { params } ); if (response.data.code === 0) { - return response.data.data.list; + return response.data.data; } throw new Error(response.data.message); } catch (error) { @@ -139,48 +139,54 @@ export const fetchLeagues = async ( } }; -export const fetchTodayMatches = async ( - sportId: number, - date?: Date | string, - timezone?: string -): Promise => { +export const fetchTodayMatches = async (options: { + sportId: number; + date?: Date | string; + timezone?: string; + leagueKey?: string; + page?: number; + pageSize?: number; +}): Promise> => { try { - const params: { sportId: number; date?: string; timezone?: string } = { - sportId, + const params: { + sportId: number; + date?: string; + timezone?: string; + leagueKey?: string; + page?: number; + pageSize?: number; + } = { + sportId: options.sportId, + leagueKey: options.leagueKey, + page: options.page, + pageSize: options.pageSize, }; - // 如果提供了日期,格式化为 YYYY-MM-DD 格式 - if (date) { - let dateStr: string; - if (date instanceof Date) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - dateStr = `${year}-${month}-${day}`; + if (options.date) { + if (options.date instanceof Date) { + const year = options.date.getFullYear(); + const month = String(options.date.getMonth() + 1).padStart(2, "0"); + const day = String(options.date.getDate()).padStart(2, "0"); + params.date = `${year}-${month}-${day}`; } else { - dateStr = date; + params.date = options.date; } - params.date = dateStr; } - // 如果提供了时区,传给后端;不传则由后端使用本地时区 - if (timezone) { - params.timezone = timezone; + if (options.timezone) { + params.timezone = options.timezone; } const response = await apiClient.get>>( API_ENDPOINTS.MATCHES_TODAY, - { - params, - } + { params } ); if (response.data.code === 0) { - return response.data.data.list; + return response.data.data; } throw new Error(response.data.message); } catch (error) { console.error("Fetch matches error:", error); - // Let the caller handle errors; rethrow the original error throw error; } }; @@ -214,9 +220,9 @@ export const fetchLiveScore = async ( // }); try { const params: { sport_id: number; league_id?: number; timezone?: string } = - { - sport_id: sportId, - }; + { + sport_id: sportId, + }; if (leagueId) { params.league_id = leagueId; diff --git a/types/api.ts b/types/api.ts index 9631f43..a40a05f 100644 --- a/types/api.ts +++ b/types/api.ts @@ -152,6 +152,7 @@ export interface League { isActive: boolean; createdAt: string; updatedAt: string; + matchCount?: number; } export interface GoalEvent {