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

React MUI卡片展开异常:点击整列同步展开,需求单张独立展开

问题描述

我通过循环从数据库获取数据生成标签页卡片,点击「查看更多」按钮时,整列可见卡片会同步展开;只有行内仅一张卡片时,才能正常展开对应单张卡片。我需要实现每次仅展开一张卡片的效果。

解决方案

要解决这个问题,需要将卡片的展开状态从子组件提升到父组件统一管理,通过控制唯一的展开卡片ID来实现单张展开的效果:

  • 父组件维护当前展开的卡片ID,子组件根据该ID判断自身是否需要展开
  • 点击按钮时,更新父组件的展开ID,确保同一时间只有一个卡片处于展开状态

修改后的完整代码

import React, { useState } from "react";
import {
  Box,
  Card,
  CardActions,
  CardContent,
  Collapse,
  Button,
  Typography,
  Rating,
  useTheme,
  useMediaQuery,
} from "@mui/material";
import Header from "../../components/Header";
import { useGetProductsQuery } from "../../state/api";
import LinearProgress from "@mui/material/LinearProgress";

const Product = ({
  _id,
  name,
  description,
  price,
  rating,
  category,
  supply,
  stat,
  isExpanded,
  handleExpand
}) => {
  const theme = useTheme();

  return (
    <Card
      sx={{
        backgroundImage: "none",
        backgroundColor: theme.palette.background.alt,
        borderRadius: "0.55rem",
      }}
    >
      <CardContent>
        <Typography
          sx={{ fontSize: 14 }}
          color={theme.palette.secondary[700]}
          gutterBottom
        >
          {category}
        </Typography>
        <Typography variant="h5" component="div">
          {name}
        </Typography>
        <Typography sx={{ mb: "1.5rem" }} color={theme.palette.secondary[400]}>
          ${Number(price).toFixed(2)}
        </Typography>
        <Rating value={rating} readOnly />

        <Typography variant="body2">{description}</Typography>
      </CardContent>
      <CardActions>
        <Button
          variant="primary"
          size="small"
          onClick={() => handleExpand(_id)}
          sx={{ backgroundColor: theme.palette.secondary[600] }}
        >
          {isExpanded ? "收起" : "查看更多"}
        </Button>
      </CardActions>
      <Collapse
        in={isExpanded}
        timeout="auto"
        unmountOnExit
        sx={{
          color: theme.palette.neutral[300],
        }}
      >
        <CardContent>
          <Typography>id: {_id}</Typography>
          <Typography>剩余库存: {supply}</Typography>
          <Typography>
            今年年度总销售额: {stat[0].yearlySalesTotal}
          </Typography>
          <Typography>
            今年年度总销量: {stat[0].yearlyTotalSoldUnits}
          </Typography>
        </CardContent>
      </Collapse>
    </Card>
  );
};

const Products = () => {
  const { data, isLoading } = useGetProductsQuery();
  const isNonMobile = useMediaQuery("(min-width: 1000px)");
  const [expandedCardId, setExpandedCardId] = useState(null);

  const handleExpand = (id) => {
    // 如果当前卡片已展开则收起,否则展开该卡片并关闭其他卡片
    setExpandedCardId(expandedCardId === id ? null : id);
  };

  return (
    <Box m="1.5rem 2.5rem">
      <Header title="产品列表" subtitle="查看你的全部产品" />
      {data && !isLoading ? (
        <Box
          mt="20px"
          display="grid"
          gridTemplateColumns="repeat(4, minmax(0, 1fr))"
          justifyContent="space-between"
          rowGap="20px"
          columnGap="1.33%"
          sx={{
            "& > div": { gridColumn: isNonMobile ? undefined : "span 4" },
          }}
        >
          {data.map(
            ({
              _id,
              name,
              description,
              price,
              rating,
              category,
              supply,
              stat,
            }) => (
              <Product
                key={_id}
                _id={_id}
                name={name}
                description={description}
                price={price}
                rating={rating}
                category={category}
                supply={supply}
                stat={stat}
                isExpanded={expandedCardId === _id}
                handleExpand={handleExpand}
              />
            )
          )}
        </Box>
      ) : (
        <LinearProgress
          color="primary"
          fourcolor="false"
          variant="indeterminate"
        />
      )}
    </Box>
  );
};

export default Products;

关键修改点说明

  1. 状态提升:将原本Product组件内部的isExpanded状态移到父组件Products中,改为expandedCardId存储当前展开的卡片ID
  2. 统一控制:点击「查看更多」时,通过父组件的handleExpand方法更新expandedCardId,确保同一时间只有一个卡片处于展开状态
  3. 状态传递:父组件向每个Product传递isExpanded(判断当前卡片是否匹配展开ID)和handleExpand方法
  4. 交互优化:按钮文本根据展开状态切换为「查看更多」或「收起」,提升用户体验

内容的提问来源于stack exchange,提问作者Abdullah Web-dev

相关产品推荐
方舟 Agent Plan

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

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