如何在React中基于赛事属性实现赛程页面的滚动展示功能?
React 联赛赛程页面实现方案
一、数据处理逻辑
首先需要将接口返回的赛程数据拆分为已结束和未开赛两组,并按时间排序,确保展示顺序符合需求:
const processFixtures = (fixtures) => { const currentTimestamp = Math.floor(Date.now() / 1000); // 转换为秒级时间戳,匹配API返回格式 // 筛选并排序已结束赛程(最新结束的排在最前) const finishedFixtures = fixtures .filter(fixture => { const { status } = fixture.fixture; // 判断赛程是否结束:状态为完赛、加时赛、点球大战,或比赛时长超过90分钟 return ['FT', 'AET', 'PEN'].includes(status.short) || (status.elapsed && status.elapsed >= 90); }) .sort((a, b) => b.fixture.timestamp - a.fixture.timestamp); // 筛选并排序未开赛赛程(最早开赛的排在最前) const upcomingFixtures = fixtures .filter(fixture => { const isFinished = finishedFixtures.some(f => f.fixture.id === fixture.fixture.id); return !isFinished && fixture.fixture.timestamp > currentTimestamp; }) .sort((a, b) => a.fixture.timestamp - b.fixture.timestamp); return { finishedFixtures, upcomingFixtures }; };
二、React 组件实现
创建组件完成数据请求、处理,并实现自动滚动到未开赛赛程的功能:
import { useState, useEffect, useRef } from 'react'; const LeagueFixtures = ({ leagueId }) => { const [fixtures, setFixtures] = useState([]); const [processedData, setProcessedData] = useState({ finishedFixtures: [], upcomingFixtures: [] }); const upcomingSectionRef = useRef(null); // 从API获取赛程数据 useEffect(() => { const fetchFixtures = async () => { try { const response = await fetch(`你的API-Football接口地址?league=${leagueId}&season=2024`, { headers: { 'x-apisports-key': '你的API密钥' // 替换为实际密钥 } }); const data = await response.json(); setFixtures(data.response); } catch (error) { console.error('获取赛程数据失败:', error); } }; fetchFixtures(); }, [leagueId]); // 数据加载完成后处理赛程分组 useEffect(() => { if (fixtures.length > 0) { const processed = processFixtures(fixtures); setProcessedData(processed); } }, [fixtures]); // 数据处理完成后自动滚动到未开赛赛程区域 useEffect(() => { if (upcomingSectionRef.current && processedData.upcomingFixtures.length > 0) { upcomingSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, [processedData.upcomingFixtures]); // 单个赛程卡片组件 const FixtureCard = ({ fixtureData }) => { const { fixture, teams, goals, score } = fixtureData; const isFinished = ['FT', 'AET', 'PEN'].includes(fixture.status.short); return ( <div className="fixture-card"> <div className="fixture-date"> {new Date(fixture.date).toLocaleDateString('zh-CN', { weekday: 'short', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} </div> <div className="fixture-teams"> <div className="team home"> <img src={teams.home.logo} alt={teams.home.name} className="team-logo" /> <span className="team-name">{teams.home.name}</span> {isFinished && <span className="team-score">{goals.home}</span>} </div> <div className="fixture-status"> {isFinished ? `${score.fulltime.home} - ${score.fulltime.away}` : fixture.status.long} </div> <div className="team away"> <img src={teams.away.logo} alt={teams.away.name} className="team-logo" /> <span className="team-name">{teams.away.name}</span> {isFinished && <span className="team-score">{goals.away}</span>} </div> </div> </div> ); }; return ( <div className="fixtures-container"> {/* 已结束赛程(向上滚动查看) */} <div className="finished-fixtures"> <h2>已结束赛事</h2> {processedData.finishedFixtures.map(fixture => ( <FixtureCard key={fixture.fixture.id} fixtureData={fixture} /> ))} </div> {/* 未开赛赛程(默认显示在视口,向下滚动查看更多) */} <div ref={upcomingSectionRef} className="upcoming-fixtures"> <h2>即将开赛</h2> {processedData.upcomingFixtures.map(fixture => ( <FixtureCard key={fixture.fixture.id} fixtureData={fixture} /> ))} </div> </div> ); }; export default LeagueFixtures;
三、CSS 样式配置
添加样式确保布局符合需求,已结束赛程在视口上方,未开赛赛程默认显示在视口内:
.fixtures-container { min-height: 100vh; padding: 20px; } .finished-fixtures { margin-bottom: 40px; } .upcoming-fixtures { /* 确保未开赛区域初始时对齐视口顶部(如果有页面头部可调整scroll-margin-top) */ scroll-margin-top: 20px; } .fixture-card { display: flex; flex-direction: column; gap: 10px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 10px; background: white; } .fixture-date { font-size: 0.9rem; color: #666; } .fixture-teams { display: flex; align-items: center; justify-content: space-between; gap: 20px; } .team { display: flex; align-items: center; gap: 10px; flex: 1; } .team.home { justify-content: flex-end; } .team.away { justify-content: flex-start; } .team-logo { width: 40px; height: 40px; object-fit: contain; } .team-name { font-size: 1rem; } .team-score { font-size: 1.2rem; font-weight: bold; margin-left: 5px; } .fixture-status { font-size: 1.1rem; font-weight: bold; min-width: 60px; text-align: center; }
核心功能说明
- 数据排序:已结束赛程按结束时间倒序排列,最新结束的赛事紧邻未开赛区域;未开赛赛程按开赛时间正序排列,最早开赛的赛事在最上方。
- 自动滚动:组件加载完成后,自动滚动到未开赛赛程区域,确保用户默认看到即将开始的赛事;向上滚动可查看已结束赛事,向下滚动查看更多未开赛赛事。
- 响应式布局:赛程卡片采用弹性布局,适配不同屏幕尺寸。
可选优化方向
- 无限滚动:针对大量赛程数据,实现滚动到底部自动加载更多未开赛赛事。
- 加载状态:添加加载动画提升用户体验。
- 筛选功能:支持按日期范围、球队筛选赛程。
- 赛事详情:点击赛程卡片跳转至赛事详情页。
内容的提问来源于stack exchange,提问作者Rayyan Alam
相关产品推荐
相关产品推荐

