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

基于Material-UI Next实现左侧多级嵌套菜单的技术咨询

Got it, let's walk through building that multi-level nested sidebar menu that matches Material-UI Next's style closely. Since you already have a way to convert your flat JSON data into nested objects, here's a practical, step-by-step guide to implement this:

1. Core Component Setup

First, we'll use Material-UI's core components that align with the official site's sidebar structure: Drawer for the sidebar container, List/ListItemButton for menu items, Collapse for expandable submenus, and icons like ExpandLess/ExpandMore for toggle indicators.

2. Recursive Menu Item Component

Since your data is now nested, a recursive component is the cleanest way to render multi-level menus. Here's a reusable implementation:

import { List, ListItemButton, ListItemText, Collapse, Typography } from '@mui/material';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { useState, memo } from 'react';
import { useLocation } from 'react-router-dom'; // If using React Router for routing

// Memoize to avoid unnecessary re-renders for large menus
const NestedMenuItem = memo(({ item, depth = 0 }) => {
  const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
  const location = useLocation();
  const isActive = location.pathname === item.path; // Mark active menu item

  const toggleSubmenu = () => {
    setIsSubmenuOpen(!isSubmenuOpen);
  };

  // Handle indentation for nested items (matches official site's spacing)
  const indentStyles = {
    paddingLeft: `${depth * 32}px`
  };

  return (
    <>
      <ListItemButton
        onClick={item.children ? toggleSubmenu : undefined}
        style={indentStyles}
        selected={isActive}
        sx={{
          '&.Mui-selected': {
            backgroundColor: '#e0e0e0', // Match official site's active item background
            '&:hover': { backgroundColor: '#bdbdbd' }
          }
        }}
      >
        <ListItemText
          primary={
            <Typography 
              variant="body1" 
              fontWeight={item.children ? 'normal' : 'medium'} // Bold parent items
            >
              {item.label}
            </Typography>
          }
        />
        {/* Show toggle icon only if item has children */}
        {item.children && (isSubmenuOpen ? <ExpandLess /> : <ExpandMore />)}
      </ListItemButton>

      {/* Render submenu with collapse animation */}
      {item.children && (
        <Collapse in={isSubmenuOpen} timeout="auto" unmountOnExit>
          <List component="div" disablePadding>
            {item.children.map(child => (
              <NestedMenuItem key={child.id} item={child} depth={depth + 1} />
            ))}
          </List>
        </Collapse>
      )}
    </>
  );
});
3. Assemble the Full Sidebar

Now wrap the recursive component in a Drawer to match the official site's layout:

import { Drawer, List, Box } from '@mui/material';

const SidebarMenu = ({ nestedMenuData }) => {
  return (
    <Drawer
      variant="permanent"
      sx={{
        width: 240,
        flexShrink: 0,
        '& .MuiDrawer-paper': {
          width: 240,
          boxSizing: 'border-box',
          backgroundColor: '#f5f5f5', // Match official site's sidebar background
          paddingTop: '64px' // Offset for top AppBar (adjust based on your layout)
        }
      }}
    >
      <Box sx={{ overflow: 'auto' }}>
        <List>
          {nestedMenuData.map(item => (
            <NestedMenuItem key={item.id} item={item} />
          ))}
        </List>
      </Box>
    </Drawer>
  );
};
4. Add Extra Polish (Match Official Site Details)
  • Icon Support: If your JSON includes icon references, add a ListItemIcon component inside ListItemButton:
    // Example: Assume item.icon is an imported icon component like Home
    import { Home, Settings } from '@mui/icons-material';
    
    // Inside ListItemButton
    <ListItemIcon>
      {item.icon === 'Home' ? <Home /> : <Settings />}
    </ListItemIcon>
    
  • Persistent Expansion: If you want submenus to stay open on page refresh, save isSubmenuOpen state to localStorage instead of using useState.
5. Quick Flat-to-Nested Data Conversion (Just in Case)

If you ever need to adjust your data conversion function, here's a reliable flat-to-nested utility:

const convertFlatToNested = (flatData) => {
  const itemMap = {};
  const nestedData = [];

  // First map all items by their ID
  flatData.forEach(item => {
    itemMap[item.id] = { ...item, children: [] };
  });

  // Build nested structure by linking children to parents
  flatData.forEach(item => {
    if (item.parentId) {
      itemMap[item.parentId].children.push(itemMap[item.id]);
    } else {
      nestedData.push(itemMap[item.id]);
    }
  });

  return nestedData;
};

内容的提问来源于stack exchange,提问作者Gregor Sotošek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:36:58