lbq
4 天以前 56ca28233a84c5aa3ca93cae266b2d008ea348e1
rsf-admin/src/layout/MyMenu.jsx
@@ -1,168 +1,272 @@
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';
  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;
  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();
  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(() => {
    // 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 }));
        }
  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]);
    }, [location.pathname]);
  const handleToggle = (menu) => {
    setState((state) => ({ ...state, [menu]: !state[menu] }));
  };
    const handleToggle = (menu) => {
        setState(state => ({ ...state, [menu]: !state[menu] }));
    };
  const getIcon = (iconStr) => {
    const IconComponent = getIconComponent(iconStr);
    if (IconComponent) {
      return <IconComponent />;
    } else {
      return <KeyboardArrowDownIcon />;
    }
  };
    const getIcon = (iconStr) => {
        const IconComponent = getIconComponent(iconStr);
        if (IconComponent) {
            return <IconComponent />;
        }
    };
  // 检查菜单是否被选中
  const isSelected = (component) => {
    if (!component) return false;
    const currentPath = location.pathname.replace("/", "");
    return currentPath === component;
  };
    const generateMenu = (permissions) => {
        return permissions.map((node) => {
            if (node.children) {
  // 检查父级菜单是否有子菜单被选中
  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 (
                    <SubMenu
                    <MenuItemLink
                        key={node.id}
                        handleToggle={() => handleToggle(node.route)}
                        isOpen={state[node.route]}
                        name={node.name}
                        to={node.component}
                        state={{ _scrollToTop: true }}
                        primaryText={translate(node.name)}
                        leftIcon={getIcon(node.icon)}
                        dense={dense}
                        icon={getIcon(node.icon)}
                    >
                        {generateMenu(node.children)}
                    </SubMenu>
                        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',
                                }
                            },
                        }}
                    />
                );
            } else {
                if (node.component) {
                    return (
                        <MenuItemLink
                            key={node.id}
                            to={node.component} // correspond to Resource.name
                            state={{ _scrollToTop: true }}
                            // primaryText={translate(`resources.orders.name`, {
                            //     smart_count: 2,
                            // })}
                            primaryText={node.name}
                            leftIcon={getIcon(node.icon)}
                            dense={dense}
                        />
                    );
                }
            }
        });
    };
        }
    });
  };
    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,
                        }),
                }}
            >
                <Menu.Item
                    to="/dashboard"
                    primaryText="menu.dashboard"
                    leftIcon={<DashboardIcon />}
                />
                {permissions && (generateMenu(permissions))}
                {/* <Menu.ResourceItems /> */}
                <Menu.Item
                    to="/settings"
                    primaryText="menu.settings"
                    leftIcon={<PersonIcon />}
                />
            </Box>
        )
}
  // 检查固定菜单是否选中
  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);
    }
  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 [];
};