如何在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能识别的格式:
- 父组件共享筛选状态:将
filterValues和更新方法放在父组件中,同时传给侧边栏和DataGrid。 - 转换筛选格式适配DataGrid:MUI DataGrid的
filterModel需要特定结构,比如文本类筛选转成{ columnField: '字段名', operator: 'contains', value: '筛选值' };多值复选框筛选转成{ columnField: '字段名', operator: 'in', value: ['值1', '值2'] }。 - 同步过滤参数到DataGrid:把转换后的
filterModel传给DataGrid的同名属性,表格就会自动过滤数据。 - 统一重置逻辑:
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
相关产品推荐
相关产品推荐

