import React, { useState, useEffect, useMemo, Suspense } from "react";
|
import {
|
useTranslate,
|
DashboardMenuItem,
|
MenuItemLink,
|
Menu,
|
useSidebarState,
|
usePermissions,
|
} from "react-admin";
|
import { useLocation } from "react-router-dom";
|
import { Box } from "@mui/material";
|
import SubMenu from "./SubMenu";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
import DashboardIcon from "@mui/icons-material/Dashboard";
|
import HorizontalRuleIcon from "@mui/icons-material/HorizontalRule";
|
import PersonIcon from "@mui/icons-material/Person";
|
import * as Icons from "@mui/icons-material";
|
|
const getIconComponent = (iconStr) => {
|
return Icons[iconStr] || HorizontalRuleIcon;
|
};
|
|
export const MyMenu = ({ dense = false }) => {
|
const [state, setState] = useState({});
|
const translate = useTranslate();
|
const location = useLocation();
|
const [sidebarIsOpen] = useSidebarState();
|
const { isPending, permissions } = usePermissions();
|
|
useEffect(() => {
|
// default open sub menu
|
const defaultExpandMenu = [
|
"menu.system",
|
"menu.dispatcher",
|
"menu.equipment",
|
];
|
permissions?.forEach((item) => {
|
if (defaultExpandMenu.includes(item.name)) {
|
setState((state) => ({ ...state, [item.route]: true }));
|
}
|
});
|
}, [permissions]);
|
|
useEffect(() => {
|
// expand this parent menu
|
const currentPath = location.pathname;
|
const parentRoutes = findParentRoutes(currentPath, permissions);
|
for (const parentRoute of parentRoutes) {
|
setState((state) => ({ ...state, [parentRoute]: true }));
|
}
|
}, [location.pathname]);
|
|
const handleToggle = (menu) => {
|
setState((state) => ({ ...state, [menu]: !state[menu] }));
|
};
|
|
const getIcon = (iconStr) => {
|
const IconComponent = getIconComponent(iconStr);
|
if (IconComponent) {
|
return <IconComponent />;
|
} else {
|
return <KeyboardArrowDownIcon />;
|
}
|
};
|
|
// 检查菜单是否被选中
|
const isSelected = (component) => {
|
if (!component) return false;
|
const currentPath = location.pathname.replace("/", "");
|
return currentPath === component;
|
};
|
|
// 检查父级菜单是否有子菜单被选中
|
const hasSelectedChild = (node) => {
|
if (!node.children) return false;
|
return node.children.some(child => {
|
if (child.children) {
|
return hasSelectedChild(child);
|
}
|
return isSelected(child.component);
|
});
|
};
|
|
// 在 MyMenu 组件的 generateMenu 函数中,确保 MenuItemLink 也左对齐
|
const generateMenu = (permissions) => {
|
return permissions.map((node) => {
|
if (node.children) {
|
const selected = isSelected(node.component) || hasSelectedChild(node);
|
return (
|
<SubMenu
|
key={node.id}
|
handleToggle={() => handleToggle(node.route)}
|
isOpen={state[node.route]}
|
name={node.name}
|
dense={dense}
|
icon={getIcon(node.icon)}
|
isSelected={selected}
|
>
|
{generateMenu(node.children)}
|
</SubMenu>
|
);
|
} else {
|
if (node.component) {
|
const selected = isSelected(node.component);
|
// 在 generateMenu 函数中的 MenuItemLink 部分
|
return (
|
<MenuItemLink
|
key={node.id}
|
to={node.component}
|
state={{ _scrollToTop: true }}
|
primaryText={translate(node.name)}
|
leftIcon={getIcon(node.icon)}
|
dense={dense}
|
sx={{
|
backgroundColor: selected ? 'rgba(25, 118, 210, 0.08) !important' : 'transparent',
|
color: selected ? '#1976d2 !important' : 'text.secondary',
|
'&:hover': {
|
backgroundColor: selected ? 'rgba(25, 118, 210, 0.12) !important' : 'rgba(0, 0, 0, 0.04)',
|
},
|
borderLeft: 'none',
|
borderRadius: '4px',
|
margin: '2px 8px',
|
width: 'calc(100% - 16px)',
|
transition: 'all 0.2s ease-in-out',
|
|
// 缩小整体间距
|
padding: '6px 8px', // 减少内边距
|
minHeight: '36px', // 稍微减小高度
|
|
'& .RaMenuItemLink-icon': {
|
color: selected ? '#1976d2 !important' : 'text.secondary',
|
minWidth: '32px !important', // 缩小图标区域宽度
|
marginRight: '4px', // 缩小图标和文字间距
|
display: 'flex',
|
alignItems: 'center',
|
justifyContent: 'center', // 图标居中显示
|
},
|
|
fontWeight: selected ? 600 : 400,
|
|
// 确保文字内容左对齐
|
'& .MuiListItemText-root': {
|
margin: 0,
|
'& .MuiTypography-root': {
|
textAlign: 'left',
|
justifyContent: 'flex-start',
|
fontSize: '0.875rem', // 稍微减小字体大小
|
lineHeight: '1.3',
|
}
|
},
|
}}
|
/>
|
);
|
}
|
}
|
});
|
};
|
|
// 检查固定菜单是否选中
|
const isDashboardSelected = location.pathname === '/dashboard';
|
const isSettingsSelected = location.pathname === '/settings';
|
|
return isPending ? (
|
<div>Waiting for permissions...</div>
|
) : (
|
<Box
|
sx={{
|
width: sidebarIsOpen ? 200 : 50,
|
marginTop: 1,
|
marginBottom: 1,
|
transition: (theme) =>
|
theme.transitions.create("width", {
|
easing: theme.transitions.easing.sharp,
|
duration: theme.transitions.duration.leavingScreen,
|
}),
|
// 菜单容器样式
|
'& .MuiMenuItem-root': {
|
boxSizing: 'border-box',
|
}
|
}}
|
>
|
<Menu.Item
|
to="/dashboard"
|
primaryText="menu.dashboard"
|
leftIcon={<DashboardIcon />}
|
sx={{
|
backgroundColor: isDashboardSelected ? 'rgba(25, 118, 210, 0.08) !important' : 'transparent',
|
color: isDashboardSelected ? '#1976d2 !important' : 'text.secondary',
|
'&:hover': {
|
backgroundColor: isDashboardSelected ? 'rgba(25, 118, 210, 0.12) !important' : 'rgba(0, 0, 0, 0.04)',
|
},
|
borderLeft: isDashboardSelected ? '3px solid #1976d2' : '3px solid transparent',
|
borderRadius: '0 4px 4px 0',
|
margin: '1px 0',
|
width: '100%',
|
transition: 'all 0.2s ease-in-out',
|
'& .MuiListItemIcon-root': {
|
color: isDashboardSelected ? '#1976d2 !important' : 'text.secondary',
|
minWidth: 40,
|
}
|
}}
|
/>
|
{permissions && generateMenu(permissions)}
|
<Menu.Item
|
to="/settings"
|
primaryText="menu.settings"
|
leftIcon={<PersonIcon />}
|
sx={{
|
backgroundColor: isSettingsSelected ? 'rgba(25, 118, 210, 0.08) !important' : 'transparent',
|
color: isSettingsSelected ? '#1976d2 !important' : 'text.secondary',
|
'&:hover': {
|
backgroundColor: isSettingsSelected ? 'rgba(25, 118, 210, 0.12) !important' : 'rgba(0, 0, 0, 0.04)',
|
},
|
borderLeft: isSettingsSelected ? '3px solid #1976d2' : '3px solid transparent',
|
borderRadius: '0 4px 4px 0',
|
margin: '1px 0',
|
width: '100%',
|
transition: 'all 0.2s ease-in-out',
|
'& .MuiListItemIcon-root': {
|
color: isSettingsSelected ? '#1976d2 !important' : 'text.secondary',
|
minWidth: 40,
|
}
|
}}
|
/>
|
</Box>
|
);
|
};
|
|
const findParentRoutes = (pathname, permissions) => {
|
if (!pathname || !permissions) {
|
return [];
|
}
|
const findMenu = (currentPermissions, path) => {
|
for (const item of currentPermissions) {
|
if (item.component === path) {
|
return item;
|
}
|
if (item.children) {
|
const found = findMenu(item.children, path);
|
if (found) {
|
return found;
|
}
|
}
|
}
|
return null;
|
};
|
|
const findParentRoutesRecursive = (item, allPermissions) => {
|
const parentRoutes = [];
|
let current = item;
|
while (current && current.parentId) {
|
const parent = allPermissions.find(
|
(permission) => permission.id === current.parentId,
|
);
|
if (parent) {
|
parentRoutes.push(parent.route);
|
current = parent;
|
} else {
|
break;
|
}
|
}
|
|
return parentRoutes;
|
};
|
|
const currentMenu = findMenu(permissions, pathname.replace("/", ""));
|
if (currentMenu) {
|
return findParentRoutesRecursive(currentMenu, permissions);
|
}
|
|
return [];
|
};
|