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

如何构建用于文件分类的多级级联选择菜单?

多级分类级联选择实现建议

首先需要调整你的分类数据结构,从一维数组改成树形结构,才能支持多级层级:

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)
      }}
    />
  );
}

注意事项

  1. 最终提交时,根据后端需求选择提交全层级路径(如["a", "a2", "a21"])或仅叶子节点值
  2. 若需要支持无限层级,建议封装递归组件来渲染各级菜单
  3. 可通过CSS调整子菜单的缩进、展开图标样式,增强层级视觉区分

内容的提问来源于stack exchange,提问作者JCS

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 01:15:01