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

如何在React+Material UI项目中实现与DataGrid联动的左侧垂直可折叠侧边栏筛选组件

如何在React+Material UI项目中实现与DataGrid联动的左侧垂直可折叠侧边栏筛选组件

嗨,看起来你已经搭建了一个很不错的草稿版筛选侧边栏!针对你想要的这种可折叠、和MUI DataGrid联动的垂直筛选组件,我来分享一些实用的思路和优化方案:

一、MUI生态内的现成参考方向

其实MUI X(MUI专门处理数据表格的扩展组件库)本身就支持灵活的筛选扩展——虽然默认是列内筛选,但你可以基于它的筛选逻辑,封装出侧边栏形式的组件。社区里的同类实现核心思路也和你的草稿一致:用可折叠面板组织筛选项,再把筛选状态同步到DataGrid的过滤参数中。

二、优化你现有组件的细节建议

你的草稿已经覆盖了核心功能,这里可以做一些贴合MUI最佳实践的优化:

  • 用Accordion替代自定义折叠逻辑:MUI的Accordion自带展开/收起的动画和交互,不需要自己维护openFilters状态,代码会更简洁。每个筛选项对应一个Accordion,用AccordionSummary放标题和图标,AccordionDetails放筛选控件即可。
  • 统一筛选状态的管理:把filterValues状态提升到包含侧边栏和DataGrid的父组件(或者用Zustand/Redux这类状态管理工具),这样两边能共享同一个筛选状态,联动更顺畅。
  • 增强必填项的视觉提示:除了给图标加红色,还可以在筛选标题旁加*标记,更符合用户的常规认知。
  • 优化控件样式一致性:调整复选框组的内边距、输入框的边框样式,让整体视觉更协调。

三、和MUI DataGrid联动的核心步骤

要实现筛选组件和DataGrid的联动,关键是把筛选状态转换成DataGrid能识别的格式:

  1. 父组件共享筛选状态:将filterValues和更新方法放在父组件中,同时传给侧边栏和DataGrid。
  2. 转换筛选格式适配DataGrid:MUI DataGrid的filterModel需要特定结构,比如文本类筛选转成{ columnField: '字段名', operator: 'contains', value: '筛选值' };多值复选框筛选转成{ columnField: '字段名', operator: 'in', value: ['值1', '值2'] }。
  3. 同步过滤参数到DataGrid:把转换后的filterModel传给DataGrid的同名属性,表格就会自动过滤数据。
  4. 统一重置逻辑:Reset All按钮要同时清空filterValues和DataGrid的filterModel。

四、优化后的示例代码片段

基于你的现有代码,用Accordion重构核心部分,同时适配DataGrid联动的结构:

import React from "react";
import {
  Autocomplete,
  Box,
  Button,
  Checkbox,
  Accordion,
  AccordionSummary,
  AccordionDetails,
  Divider,
  List,
  ListItem,
  ListItemText,
  TextField
} from "@mui/material";
import { ExpandMore, ExpandLess } from "@mui/icons-material";

// 给每个筛选项绑定对应DataGrid的列字段
const filters = [
  {
    label: "Laptop Name",
    type: "autocomplete",
    columnField: "laptopName",
    options: ["Lorem ipsum", "Lorem ipsum"]
  },
  {
    label: "Manufacturer",
    type: "autocomplete",
    columnField: "manufacturer",
    options: ["Asus", "Lenovo", "Dell", "Apple", "Samsung"]
  },
  {
    label: "Screen size",
    type: "checkbox",
    columnField: "screenSize",
    options: ["13in", "14in", "15.6in", "Over 15.6in"]
  },
  {
    label: "Price",
    type: "checkbox",
    columnField: "priceRange",
    options: ["< $1000", "$1000-$1400", "$1400-$2000", "> $2000"],
    required: true
  },
  {
    label: "Frame refresh rate",
    type: "checkbox",
    columnField: "refreshRate",
    options: ["61-99 Hz", "100-120 Hz", "121-144 Hz", "145-240 Hz"]
  },
  {
    label: "RAM",
    type: "checkbox",
    columnField: "ram",
    options: ["< 2GB", "2-4 GB", "4-8 GB", "8-16 GB"]
  }
];

export const LeftSidebarFilter = ({ filterValues, setFilterValues, onResetAll }) => {
  const handleAutocompleteChange = (event, value, columnField) => {
    setFilterValues(prev => ({
      ...prev,
      [columnField]: value
    }));
  };

  const handleCheckboxChange = (event, columnField) => {
    const { value, checked } = event.target;
    setFilterValues(prev => ({
      ...prev,
      [columnField]: checked
        ? [...(prev[columnField] || []), value]
        : prev[columnField]?.filter(val => val !== value) || []
    }));
  };

  return (
    <Box sx={{ bgcolor: "#F5F5F5", width: "212px", p: "24px" }}>
      <Box
        display="flex"
        justifyContent="space-between"
        alignItems="center"
        mb={2}
      >
        <Box fontSize="h6.fontSize" fontWeight="bold">
          Filters
        </Box>
        <Button variant="text" color="primary" onClick={onResetAll}>
          Reset All
        </Button>
      </Box>
      {filters.map((filter) => (
        <React.Fragment key={filter.columnField}>
          <Accordion sx={{ boxShadow: "none", bgcolor: "transparent" }}>
            <AccordionSummary
              expandIcon={filter.required ? <ExpandLess sx={{ color: "red" }} /> : <ExpandMore />}
              aria-controls={`${filter.columnField}-content`}
              id={`${filter.columnField}-header`}
            >
              <ListItemText 
                primary={`${filter.label}${filter.required ? ' *' : ''}`} 
                sx={{ fontWeight: 500 }}
              />
            </AccordionSummary>
            <AccordionDetails sx={{ pl: 2 }}>
              {filter.type === "autocomplete" && (
                <Autocomplete
                  options={filter.options}
                  renderInput={(params) => (
                    <TextField {...params} label={filter.label} fullWidth size="small" />
                  )}
                  onChange={(event, value) =>
                    handleAutocompleteChange(event, value, filter.columnField)
                  }
                  value={filterValues[filter.columnField] || null}
                />
              )}
              {filter.type === "checkbox" && (
                <List component="div" disablePadding>
                  {filter.options.map((option) => (
                    <ListItem key={option} disablePadding sx={{ py: 0.5 }}>
                      <Checkbox
                        checked={filterValues[filter.columnField]?.includes(option) || false}
                        onChange={(e) => handleCheckboxChange(e, filter.columnField)}
                        value={option}
                        size="small"
                        sx={{ "&.Mui-checked": { color: "red" } }}
                      />
                      <ListItemText primary={option} />
                    </ListItem>
                  ))}
                </List>
              )}
            </AccordionDetails>
          </Accordion>
          <Divider sx={{ my: 1 }} />
        </React.Fragment>
      ))}
    </Box>
  );
};

父组件联动DataGrid的示例

import { DataGrid } from '@mui/x-data-grid';
import { LeftSidebarFilter } from './LeftSidebarFilter';

const ParentComponent = () => {
  // 模拟表格数据
  const yourDataRows = [
    { id: 1, laptopName: "Lorem ipsum", manufacturer: "Asus", screenSize: "14in", priceRange: "$1000-$1400", refreshRate: "61-99 Hz", ram: "8-16 GB" },
    // 更多数据...
  ];

  // 表格列配置
  const yourDataColumns = [
    { field: 'laptopName', headerName: 'Laptop Name', width: 180 },
    { field: 'manufacturer', headerName: 'Manufacturer', width: 150 },
    { field: 'screenSize', headerName: 'Screen size', width: 130 },
    { field: 'priceRange', headerName: 'Price', width: 150 },
    { field: 'refreshRate', headerName: 'Frame refresh rate', width: 180 },
    { field: 'ram', headerName: 'RAM', width: 120 },
  ];

  const [filterValues, setFilterValues] = React.useState({});
  const [filterModel, setFilterModel] = React.useState({ items: [] });

  // 实时转换筛选值为DataGrid的filterModel格式
  React.useEffect(() => {
    const newFilterItems = Object.entries(filterValues).map(([columnField, value]) => {
      if (Array.isArray(value) && value.length > 0) {
        return { columnField, operator: 'in', value };
      } else if (typeof value === 'string' && value) {
        return { columnField, operator: 'contains', value };
      }
      return null;
    }).filter(Boolean);

    setFilterModel({ items: newFilterItems });
  }, [filterValues]);

  const handleResetAll = () => {
    setFilterValues({});
    setFilterModel({ items: [] });
  };

  return (
    <Box display="flex" height="100vh">
      <LeftSidebarFilter 
        filterValues={filterValues} 
        setFilterValues={setFilterValues} 
        onResetAll={handleResetAll} 
      />
      <Box sx={{ flexGrow: 1, p: 2 }}>
        <DataGrid
          rows={yourDataRows}
          columns={yourDataColumns}
          filterModel={filterModel}
          autoHeight
          sx={{ border: 0 }}
        />
      </Box>
    </Box>
  );
};

备注:内容来源于stack exchange,提问作者A_M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 13:37:58