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

如何制作带连接关系的概要表格(React+Material-UI)

实现带连接关系的卡片布局方案

核心思路

要实现卡片间的连接关系,针对赛事对阵这类结构固定的场景,用CSS伪元素+Flex布局是最简洁的方案:通过伪元素绘制水平/垂直连线,配合分组的卡片结构,还原对阵 bracket 的视觉效果。

修改后的完整代码

import React from "react";
import { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import MundialButtons from "../../componentes/mundial-buttons";

const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
    padding: theme.spacing(3),
  },
  bracketContainer: {
    display: "flex",
    justifyContent: "space-around",
    alignItems: "flex-start",
    gap: theme.spacing(4),
  },
  roundColumn: {
    display: "flex",
    flexDirection: "column",
    gap: theme.spacing(6),
    position: "relative",
  },
  // 列间水平连线
  roundColumn:not(:last-child):after {
    content: "";
    position: absolute;
    top: 22%;
    right: -20px;
    width: 20px;
    height: 2px;
    background-color: #ccc;
  },
  roundColumn:not(:last-child):before {
    content: "";
    position: absolute;
    bottom: 22%;
    right: -20px;
    width: 20px;
    height: 2px;
    background-color: #ccc;
  },
  cardPair: {
    position: "relative",
  },
  // 分组卡片到下一轮的垂直连线
  cardPair:after: {
    content: "";
    position: absolute;
    left: calc(100% + 20px);
    top: 50%;
    transform: translateY(-50%);
    height: 100%;
    width: 2px;
    background-color: #ccc;
  },
  card: {
    minWidth: 200,
    maxWidth: 250,
    marginBottom: theme.spacing(2),
  },
  paper: {
    padding: theme.spacing(2),
    textAlign: "center",
    color: theme.palette.text.primary,
  },
}));

export default function Two() {
  const url = "https://adad1EUmIOwosuGTI7L2DD6S02RjOG7vbxU3FjVVD1u-iYiw/a!A1:Z1000";
  const [todos, setTodos] = useState([]);
  
  const fetchApi = async () => {
    try {
      const response = await fetch(url);
      const responseJSON = await response.json();
      // 过滤空对阵数据
      setTodos(responseJSON.filter(todo => todo.Local && todo.Visitante));
    } catch (err) {
      console.error("获取赛事数据失败:", err);
    }
  };

  useEffect(() => {
    fetchApi();
  }, []);

  const classes = useStyles();

  // 将赛事每两个分组,对应一对对阵
  const groupedMatches = [];
  for (let i = 0; i < todos.length; i += 2) {
    groupedMatches.push(todos.slice(i, i + 2));
  }

  return (
    <div className={classes.root}>
      <MundialButtons />

      <Grid container spacing={3} mb={3}>
        <Grid item xs={12}>
          <Paper className={classes.paper}>
            <Typography>赛事对阵 Bracket</Typography>
          </Paper>
        </Grid>
      </Grid>

      {todos.length === 0 ? (
        <Typography>Cargando...</Typography>
      ) : (
        <div className={classes.bracketContainer}>
          {/* 八分之一决赛列 */}
          <div className={classes.roundColumn}>
            {groupedMatches.map((pair, pairIndex) => (
              <div className={classes.cardPair} key={pairIndex}>
                {pair.map((match, matchIndex) => (
                  <Card key={matchIndex} className={classes.card} variant="outlined">
                    <CardContent>
                      <Typography color="textSecondary" gutterBottom>
                        Octavos:
                      </Typography>
                      <Typography variant="h6" component="h2">
                        {match.Local}
                      </Typography>
                      <Typography variant="h6" component="h2">
                        {match.Visitante}
                      </Typography>
                    </CardContent>
                  </Card>
                ))}
              </div>
            ))}
          </div>
          {/* 四分之一决赛列(示例) */}
          <div className={classes.roundColumn}>
            {groupedMatches.map((_, index) => (
              <Card key={index} className={classes.card} variant="outlined">
                <CardContent>
                  <Typography color="textSecondary" gutterBottom>
                    Cuartos:
                  </Typography>
                  <Typography variant="h6" component="h2">
                    胜者 {index + 1}
                  </Typography>
                </CardContent>
              </Card>
            ))}
          </div>
          {/* 可继续添加半决赛、决赛列 */}
        </div>
      )}
    </div>
  );
}

关键调整说明

  1. 布局重构:用Flex横向排列各轮次赛事,替代原Grid分栏,更贴合对阵 bracket 的横向延伸需求
  2. 连线实现:通过CSS伪元素绘制两类连线——列间的水平连线、分组卡片到下一轮的垂直连线,还原示意图的连接效果
  3. 数据处理:将赛事数据每两个分组,对应一对对阵,保证连线逻辑和布局的匹配
  4. 细节优化:过滤空数据、添加间距调整,提升布局的整洁度

如果后续需要支持更复杂的动态连线(比如卡片位置不固定),可以考虑用SVG或Canvas绘制连线,但对于赛事对阵这类结构固定的场景,CSS方案足够高效简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:31:03