如何制作带连接关系的概要表格(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> ); }
关键调整说明
- 布局重构:用Flex横向排列各轮次赛事,替代原Grid分栏,更贴合对阵 bracket 的横向延伸需求
- 连线实现:通过CSS伪元素绘制两类连线——列间的水平连线、分组卡片到下一轮的垂直连线,还原示意图的连接效果
- 数据处理:将赛事数据每两个分组,对应一对对阵,保证连线逻辑和布局的匹配
- 细节优化:过滤空数据、添加间距调整,提升布局的整洁度
如果后续需要支持更复杂的动态连线(比如卡片位置不固定),可以考虑用SVG或Canvas绘制连线,但对于赛事对阵这类结构固定的场景,CSS方案足够高效简洁。
内容的提问来源于stack exchange,提问作者MatiST00
相关产品推荐
相关产品推荐

