如何构建用于文件分类的多级级联选择菜单?
多级分类级联选择实现建议
首先需要调整你的分类数据结构,从一维数组改成树形结构,才能支持多级层级:
const categories = [ { value: "a", label: "分类A", children: [ { value: "a1", label: "子分类A1" }, { value: "a2", label: "子分类A2", children: [{ value: "a21", label: "孙分类A21" }] } ] }, { value: "b", label: "分类B", children: [{ value: "b1", label: "子分类B1" }] }, { value: "c", label: "分类C" }, { value: "d", label: "分类D" } ];
下面提供两种基于Material-UI的实现方案:
方案一:多Select联动(直观易实现)
通过多个Select组件,根据上一级的选择动态渲染下一级选项,适合层级不多的场景。
实现代码
import { useState } from 'react'; import { InputLabel, Select, MenuItem } from '@mui/material'; export default function MultiLevelCategorySelector() { // 存储每一级选中的值,比如 ["a", "a2", "a21"] const [selectedLevels, setSelectedLevels] = useState([]); // 当前待选的下一级选项 const [nextLevelOptions, setNextLevelOptions] = useState(categories); // 查找对应value的分类项 const findCategoryItem = (options, value) => { return options.find(item => item.value === value); }; // 处理某一级的选择变化 const handleLevelChange = (index, value) => { const updatedLevels = [...selectedLevels]; updatedLevels[index] = value; // 截断当前层级之后的选中值(比如重新选一级时,清空二级、三级) updatedLevels.splice(index + 1); setSelectedLevels(updatedLevels); // 获取选中项的子分类,作为下一级的可选选项 const parentOptions = index === 0 ? categories : findCategoryItem(categories, selectedLevels[0]).children; const selectedItem = findCategoryItem(parentOptions, value); setNextLevelOptions(selectedItem?.children || []); }; return ( <div style={{ margin: '16px 0' }}> <InputLabel id="multi-category-label">文件多级分类</InputLabel> {/* 渲染已选中的每一级Select */} {selectedLevels.map((levelValue, index) => ( <Select key={index} value={levelValue} onChange={(e) => handleLevelChange(index, e.target.value)} fullWidth style={{ margin: '8px 0' }} labelId="multi-category-label" > {index === 0 ? categories.map(item => ( <MenuItem key={item.value} value={item.value}> {item.label} </MenuItem> )) : findCategoryItem(categories, selectedLevels[0]).children.map(item => ( <MenuItem key={item.value} value={item.value}> {item.label} </MenuItem> )) } </Select> ))} {/* 如果有下一级选项,渲染新的Select */} {nextLevelOptions.length > 0 && ( <Select value="" onChange={(e) => handleLevelChange(selectedLevels.length, e.target.value)} fullWidth style={{ margin: '8px 0' }} placeholder="选择子分类" > <MenuItem value="" disabled>选择子分类</MenuItem> {nextLevelOptions.map(item => ( <MenuItem key={item.value} value={item.value}> {item.label} </MenuItem> ))} </Select> )} </div> ); }
方案二:嵌套下拉菜单(更紧凑的交互)
利用Material-UI的Autocomplete组件,在下拉列表中渲染嵌套的子菜单,点击父选项可展开/收起子选项,适合层级较多的场景。
实现代码
import { useState } from 'react'; import { Autocomplete, TextField, IconButton, ListItemText } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; export default function NestedCategorySelector() { const [selectedCategory, setSelectedCategory] = useState(null); // 复制原始数据,避免修改源数据 const [expandedCategories, setExpandedCategories] = useState([...categories]); // 切换分类的展开状态 const toggleExpand = (categoryValue) => { const updatedCategories = expandedCategories.map(item => { if (item.value === categoryValue) { return { ...item, expanded: !item.expanded }; } // 递归处理子分类 if (item.children) { return { ...item, children: toggleExpandChildren(item.children, categoryValue) }; } return item; }); setExpandedCategories(updatedCategories); }; const toggleExpandChildren = (children, targetValue) => { return children.map(child => { if (child.value === targetValue) { return { ...child, expanded: !child.expanded }; } if (child.children) { return { ...child, children: toggleExpandChildren(child.children, targetValue) }; } return child; }); }; // 递归渲染嵌套选项 const renderNestedOptions = (options) => { return options.map(option => ( <li key={option.value} style={{ listStyle: 'none' }}> <Autocomplete.Option value={option} onClick={() => { if (option.children) { toggleExpand(option.value); } else { setSelectedCategory(option); } }} > <ListItemText> {option.label} {option.children && ( <IconButton size="small" style={{ marginLeft: '8px' }}> {option.expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />} </IconButton> )} </ListItemText> {/* 渲染展开的子选项 */} {option.expanded && option.children && ( <ul style={{ marginLeft: '24px', padding: 0 }}> {renderNestedOptions(option.children)} </ul> )} </Autocomplete.Option> </li> )); }; return ( <Autocomplete value={selectedCategory} onChange={(e, newValue) => setSelectedCategory(newValue)} options={expandedCategories} getOptionLabel={(option) => option.label} renderOption={() => null} // 自定义渲染选项,覆盖默认 renderInput={(params) => ( <TextField {...params} label="文件多级分类" variant="outlined" fullWidth /> )} ListboxProps={{ children: renderNestedOptions(expandedCategories) }} /> ); }
注意事项
- 最终提交时,根据后端需求选择提交全层级路径(如
["a", "a2", "a21"])或仅叶子节点值 - 若需要支持无限层级,建议封装递归组件来渲染各级菜单
- 可通过CSS调整子菜单的缩进、展开图标样式,增强层级视觉区分
内容的提问来源于stack exchange,提问作者JCS
相关产品推荐
相关产品推荐

