接口添加时间与时区&赔率

This commit is contained in:
xianyi
2026-01-14 15:14:09 +08:00
parent 1bd10694bf
commit 8dc87c9b29
7 changed files with 149 additions and 13 deletions

View File

@@ -34,6 +34,15 @@ export default function HomeScreen() {
const [loadingLeagues, setLoadingLeagues] = useState(false); const [loadingLeagues, setLoadingLeagues] = useState(false);
const [now, setNow] = useState(() => new Date()); const [now, setNow] = useState(() => new Date());
const deviceTimeZone = useMemo(() => {
try {
console.log("deviceTimeZone", Intl.DateTimeFormat().resolvedOptions().timeZone);
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
} catch {
return "UTC";
}
}, []);
// Selection States // Selection States
// 默认足球 // 默认足球
const [selectedSportId, setSelectedSportId] = useState<number | null>(1); const [selectedSportId, setSelectedSportId] = useState<number | null>(1);
@@ -158,7 +167,7 @@ export default function HomeScreen() {
const loadMatches = async (sportId: number) => { const loadMatches = async (sportId: number) => {
setLoading(true); setLoading(true);
try { try {
const list = await fetchTodayMatches(sportId, selectedDate); const list = await fetchTodayMatches(sportId, selectedDate, deviceTimeZone);
setMatches(list); setMatches(list);
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -10,7 +10,7 @@ import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
import { fetchLeagues, fetchSports, fetchTodayMatches } from "@/lib/api"; import { fetchLeagues, fetchSports, fetchTodayMatches } from "@/lib/api";
import { League, Match, Sport } from "@/types/api"; import { League, Match, Sport } from "@/types/api";
import React, { useEffect, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
ActivityIndicator, ActivityIndicator,
@@ -32,6 +32,14 @@ export default function HomeScreen() {
const [matches, setMatches] = useState<Match[]>([]); const [matches, setMatches] = useState<Match[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const deviceTimeZone = useMemo(() => {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
} catch {
return "UTC";
}
}, []);
// Selection States // Selection States
// 默认足球 // 默认足球
const [selectedSportId, setSelectedSportId] = useState<number | null>(1); const [selectedSportId, setSelectedSportId] = useState<number | null>(1);
@@ -131,7 +139,7 @@ export default function HomeScreen() {
setLoading(true); setLoading(true);
try { try {
// Pass selectedDate if API supported it // Pass selectedDate if API supported it
const list = await fetchTodayMatches(sportId); const list = await fetchTodayMatches(sportId, undefined, deviceTimeZone);
setMatches(list); setMatches(list);
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -7,6 +7,7 @@ import { SubstitutesCard } from "@/components/match-detail/football/substitutes-
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 { OddsView } from "@/components/match-detail/odds/odds-view";
import { ScoreHeader } from "@/components/match-detail/score-header"; import { ScoreHeader } from "@/components/match-detail/score-header";
import { ThemedText } from "@/components/themed-text"; import { ThemedText } from "@/components/themed-text";
import { ThemedView } from "@/components/themed-view"; import { ThemedView } from "@/components/themed-view";
@@ -160,13 +161,7 @@ export default function MatchDetailScreen() {
); );
} }
case "odds": case "odds":
return ( return <OddsView sportId={sportId} matchId={data.match.ID} />;
<View style={styles.emptyContent}>
<ThemedText style={styles.emptyText}>
{t("detail.empty_odds")}
</ThemedText>
</View>
);
case "h2h": case "h2h":
return ( return (
<View style={styles.emptyContent}> <View style={styles.emptyContent}>

View File

@@ -0,0 +1,84 @@
import { ThemedText } from "@/components/themed-text";
import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext";
import { fetchOdds } from "@/lib/api";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, StyleSheet, View } from "react-native";
interface OddsViewProps {
sportId: number;
matchId: number;
}
export function OddsView({ sportId, matchId }: OddsViewProps) {
const { theme } = useTheme();
const { t } = useTranslation();
const isDark = theme === "dark";
const bgColor = isDark ? "#1C1C1E" : "#FFF";
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const run = async () => {
try {
setLoading(true);
setError(null);
const data = await fetchOdds(sportId, matchId);
if (cancelled) return;
console.log("odds", { sportId, matchId, data });
} catch (e: any) {
if (cancelled) return;
setError(e?.message || "fetchOdds failed");
} finally {
if (!cancelled) setLoading(false);
}
};
run();
return () => {
cancelled = true;
};
}, [sportId, matchId]);
return (
<View style={[styles.container, { backgroundColor: bgColor }]}>
{loading ? (
<View style={styles.center}>
<ActivityIndicator size="small" color={Colors[theme].tint} />
<ThemedText style={styles.text}>{t("detail.loading")}</ThemedText>
</View>
) : error ? (
<ThemedText style={styles.text}>{error}</ThemedText>
) : (
<ThemedText style={styles.text}>{t("detail.empty_odds")}</ThemedText>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
margin: 16,
marginTop: 0,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
center: {
flexDirection: "row",
alignItems: "center",
gap: 10,
},
text: {
opacity: 0.7,
},
});

View File

@@ -10,4 +10,5 @@ export const API_ENDPOINTS = {
MATCHES_TODAY: "/v1/api/matches/today", MATCHES_TODAY: "/v1/api/matches/today",
UPCOMING_MATCHES: "/v1/api/matches/upcoming", UPCOMING_MATCHES: "/v1/api/matches/upcoming",
MATCH_DETAIL: (id: string) => `/v1/api/matches/${id}`, MATCH_DETAIL: (id: string) => `/v1/api/matches/${id}`,
ODDS: "/v1/api/odds",
}; };

View File

@@ -6,6 +6,7 @@ import {
League, League,
Match, Match,
MatchDetailData, MatchDetailData,
OddsData,
Sport, Sport,
UpcomingMatch, UpcomingMatch,
} from "@/types/api"; } from "@/types/api";
@@ -76,11 +77,12 @@ export const fetchLeagues = async (
export const fetchTodayMatches = async ( export const fetchTodayMatches = async (
sportId: number, sportId: number,
date?: Date | string date?: Date | string,
timezone?: string
): Promise<Match[]> => { ): Promise<Match[]> => {
try { try {
const params: { sport_id: number; date?: string } = { const params: { sportId: number; date?: string; timezone?: string } = {
sport_id: sportId, sportId,
}; };
// 如果提供了日期,格式化为 YYYY-MM-DD 格式 // 如果提供了日期,格式化为 YYYY-MM-DD 格式
@@ -97,6 +99,11 @@ export const fetchTodayMatches = async (
params.date = dateStr; params.date = dateStr;
} }
// 如果提供了时区,传给后端;不传则由后端使用本地时区
if (timezone) {
params.timezone = timezone;
}
const response = await apiClient.get<ApiResponse<ApiListResponse<Match>>>( const response = await apiClient.get<ApiResponse<ApiListResponse<Match>>>(
API_ENDPOINTS.MATCHES_TODAY, API_ENDPOINTS.MATCHES_TODAY,
{ {
@@ -159,3 +166,30 @@ export const fetchUpcomingMatches = async (
throw error; throw error;
} }
}; };
// 获取实时赔率(足球/网球使用 LiveOdds篮球/板球使用 Odds
export const fetchOdds = async (
sportId: number,
matchId: number
): Promise<OddsData> => {
try {
const response = await apiClient.get<ApiResponse<OddsData>>(
API_ENDPOINTS.ODDS,
{
params: {
sport_id: sportId,
match_id: matchId,
},
}
);
if (response.data.code === 0) {
return response.data.data;
}
throw new Error(response.data.message);
} catch (error) {
console.error("Fetch odds error:", error);
throw error;
}
};

View File

@@ -195,3 +195,8 @@ export interface MatchDetailData {
}; };
}; };
} }
// 实时赔率(根据 sportId 可能是 LiveOdds 或 Odds 结构,暂时使用宽泛类型)
export interface OddsData {
[key: string]: any;
}