You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

核心功能说明

  1. 数据排序:已结束赛程按结束时间倒序排列,最新结束的赛事紧邻未开赛区域;未开赛赛程按开赛时间正序排列,最早开赛的赛事在最上方。
  2. 自动滚动:组件加载完成后,自动滚动到未开赛赛程区域,确保用户默认看到即将开始的赛事;向上滚动可查看已结束赛事,向下滚动查看更多未开赛赛事。
  3. 响应式布局:赛程卡片采用弹性布局,适配不同屏幕尺寸。

可选优化方向

  • 无限滚动:针对大量赛程数据,实现滚动到底部自动加载更多未开赛赛事。
  • 加载状态:添加加载动画提升用户体验。
  • 筛选功能:支持按日期范围、球队筛选赛程。
  • 赛事详情:点击赛程卡片跳转至赛事详情页。

内容的提问来源于stack exchange,提问作者Rayyan Alam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 21:59:57