菜单优化

This commit is contained in:
yunkexin 2025-11-25 13:52:08 +08:00
parent b96f742a52
commit e184b009f9

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import React, { useState, useEffect, useMemo, useRef } from 'react';
import {
AppstoreOutlined,
CalendarOutlined,
@ -7,26 +7,20 @@ import {
SettingOutlined,
MenuUnfoldOutlined,
MenuFoldOutlined,
} from "@ant-design/icons";
import { Divider, Menu, Switch, Icon, Button, Modal } from "antd";
import { connect } from "dva";
import { withRouter, matchPath } from "dva/router";
import { Scrollbars } from "react-custom-scrollbars";
import EnergyIcon from "../utils/energyIcon";
import { $consts } from "../plugins";
import "./sider.less";
import MenuItem from "antd/lib/menu/MenuItem";
import SubMenu from "antd/lib/menu/SubMenu";
import FullScreenPage from "./FullScreen";
import {
requestFullScreenMethod,
} from "../utils/common";
} from '@ant-design/icons';
import { Divider, Menu, Switch, Icon, Button, Modal } from 'antd';
import { connect } from 'dva';
import { withRouter, matchPath } from 'dva/router';
import { Scrollbars } from 'react-custom-scrollbars';
import EnergyIcon from '../utils/energyIcon';
import { $consts } from '../plugins';
import './sider.less';
import MenuItem from 'antd/lib/menu/MenuItem';
import SubMenu from 'antd/lib/menu/SubMenu';
import FullScreenPage from './FullScreen';
import { requestFullScreenMethod } from '../utils/common';
const Sider = (props) => {
/** 菜单页展开与收起 */
const timer = useRef(-1);
const handleExpandMenuShow = () => {
@ -44,11 +38,11 @@ const Sider = (props) => {
const [menuShrink, setMenuShrink] = useState({});
const handleMenuShrink = (menuId) => {
const data = Object.assign({}, menuShrink);
data[menuId] ? (data[menuId] = "") : (data[menuId] = menuId);
data[menuId] ? (data[menuId] = '') : (data[menuId] = menuId);
setMenuShrink(data);
};
const [showModal, setshowModal] = useState(false);
const escFunction = () => {
const [showModal, setshowModal] = useState(false);
const escFunction = () => {
let isFull = !!(
document.fullscreen ||
document.mozFullScreen ||
@ -61,39 +55,30 @@ const Sider = (props) => {
} else {
// requestFullScreenMethod(document.body);
setshowModal(false);
setActiveMenu(null);
setActiveMenu(null);
}
};
useEffect(() => {
// 监听退出全屏事件 --- chrome 用 esc 退出全屏并不会触发 keyup 事件
document.addEventListener(
"webkitfullscreenchange",
escFunction
); /* Chrome, Safari and Opera */
document.addEventListener("mozfullscreenchange", escFunction); /* Firefox */
document.addEventListener(
"fullscreenchange",
escFunction
); /* Standard syntax */
document.addEventListener(
"msfullscreenchange",
escFunction
); /* IE / Edge */
document.addEventListener('webkitfullscreenchange', escFunction); /* Chrome, Safari and Opera */
document.addEventListener('mozfullscreenchange', escFunction); /* Firefox */
document.addEventListener('fullscreenchange', escFunction); /* Standard syntax */
document.addEventListener('msfullscreenchange', escFunction); /* IE / Edge */
return () => {
//销毁时清除监听
document.removeEventListener("webkitfullscreenchange", escFunction);
document.removeEventListener("mozfullscreenchange", escFunction);
document.removeEventListener("fullscreenchange", escFunction);
document.removeEventListener("MSFullscreenChange", escFunction);
document.removeEventListener('webkitfullscreenchange', escFunction);
document.removeEventListener('mozfullscreenchange', escFunction);
document.removeEventListener('fullscreenchange', escFunction);
document.removeEventListener('MSFullscreenChange', escFunction);
};
}, []);
/** 回到首页 */
const navToHome = () => {
setActiveMenu(null);
if (localStorage.getItem("webOrgId") == '00300000-0000-0000-0000-000000000000' ) {
props.history.replace('/grouphome')
}else{
props.history.replace('/home')
if (localStorage.getItem('webOrgId') == '00300000-0000-0000-0000-000000000000') {
props.history.replace('/grouphome');
} else {
props.history.replace('/home');
}
// props.history.push({ pathname: "/home" });
};
@ -101,12 +86,72 @@ const Sider = (props) => {
setActiveMenu('largeScreen');
setshowModal(true);
requestFullScreenMethod(document.body);
}
};
const clickMenu = () => {
collapsed === true ? setCollapsed(false) : setCollapsed(false)
collapsed === true ? setCollapsed(false) : setCollapsed(false);
};
//ykx新增
const [openKeys, setOpenKeys] = useState([]);
// 手风琴模式:只展开一个菜单
const onOpenChange = (keys) => {
if (!keys || keys.length === 0) {
setOpenKeys([]);
return;
}
}
// 包含常用菜单 sub1 和所有一级菜单 key
const rootSubMenuKeys = ['sub1', ...topMenus.menus.map((menu, index) => `${menu.Node.ID}_${index}`)];
// 查找最新展开的 key相对于当前 openKeys
const latestOpenKey = keys.find((key) => !openKeys.includes(key));
const isRoot = (key) => rootSubMenuKeys.includes(key);
// 根据二级 key 去找到它的一级父 key通过匹配 topMenus 中的 Children.Node.ID
const getParentKeyBySecondKey = (secondKey) => {
const secondId = String(secondKey).split('_')[0];
for (let i = 0; i < topMenus.menus.length; i++) {
const top = topMenus.menus[i];
if (top.Children && top.Children.some((c) => String(c.Node.ID) === secondId)) {
return `${top.Node.ID}_${i}`;
}
}
return null;
};
// 如果最新展开的是一级菜单,直接只保留该一级菜单(手风琴)
if (latestOpenKey && isRoot(latestOpenKey)) {
setOpenKeys([latestOpenKey]);
return;
}
// 如果最新展开的是二级菜单,保留它的父级和它自己
if (latestOpenKey) {
const parentKey = getParentKeyBySecondKey(latestOpenKey) || keys.find((k) => isRoot(k));
if (parentKey) {
setOpenKeys([parentKey, latestOpenKey]);
return;
}
}
// 兜底:保证每个父级只保留一个二级(取 keys 中最后一个属于该父级的二级)
const parentsInKeys = keys.filter((k) => isRoot(k));
if (parentsInKeys.length) {
const parent = parentsInKeys[0];
const secondsForParent = keys.filter((k) => !isRoot(k) && getParentKeyBySecondKey(k) === parent);
if (secondsForParent.length) {
setOpenKeys([parent, secondsForParent[secondsForParent.length - 1]]);
return;
} else {
setOpenKeys([parent]);
return;
}
}
// 其他情况直接使用传入的 keys
setOpenKeys(keys);
};
/** 子菜单路由 */
const navToMenu = (menu) => {
props.history.push({ pathname: `/main/${menu.ID}` });
@ -135,7 +180,7 @@ const Sider = (props) => {
useEffect(() => {
const { pathname } = props.location;
const mathHome = matchPath(pathname, {
path: $consts["ROUTE/HOME"],
path: $consts['ROUTE/HOME'],
exact: true,
strict: true,
});
@ -143,7 +188,7 @@ const Sider = (props) => {
activeMenu && setActiveMenu(null);
}
const mathMain = matchPath(pathname, {
path: $consts["ROUTE/MAIN"],
path: $consts['ROUTE/MAIN'],
exact: true,
strict: true,
});
@ -153,7 +198,7 @@ const Sider = (props) => {
const find = flatMenus.find((item) => item.ID === menuId);
if (find) {
props.dispatch({
type: "app/updateActivatedMenu",
type: 'app/updateActivatedMenu',
payload: {
currActivatedTab: find.ID,
currActivatedMenu: find,
@ -175,12 +220,9 @@ const Sider = (props) => {
const topMenus = useMemo(() => {
const menus = props.login.loginInfo?.Menus || [];
// 菜单宽度 82 + margin 40
const menuWidth = menus.length
? menus.length * 82 + (menus.length - 1) * 40
: 0;
const menuWidth = menus.length ? menus.length * 82 + (menus.length - 1) * 40 : 0;
// 叶子菜单需要换行展示个数
const leafMenuSections =
menus.length < 3 ? 1 : menus.length === 3 ? 2 : menus.length - 2;
const leafMenuSections = menus.length < 3 ? 1 : menus.length === 3 ? 2 : menus.length - 2;
const result = {
menus,
width: menuWidth < 120 ? 120 : menuWidth,
@ -203,13 +245,7 @@ const Sider = (props) => {
if (IS_MENU_SHRINK) {
const { menus } = topMenus;
const data = {};
menus.forEach(
(menu) =>
menu.Children &&
menu.Children.forEach(
(child) => (data[child.Node.ID] = child.Node.ID)
)
);
menus.forEach((menu) => menu.Children && menu.Children.forEach((child) => (data[child.Node.ID] = child.Node.ID)));
setMenuShrink(data);
}
}, [IS_MENU_SHRINK]);
@ -221,14 +257,14 @@ const Sider = (props) => {
if (collapsed == false) {
setCollapsed(true);
}
}, [props.repost])
}, [props.repost]);
// console.log(props.repost,'21312313213213123')
/** 登录页隐藏 */
if (props.matchLogin) return null;
return (
<div className={`sider ${"shortMenuShow"}`}>
<div className={`sider ${'shortMenuShow'}`}>
<Modal
title="功能导航"
visible={showModal}
@ -244,16 +280,18 @@ const Sider = (props) => {
>
<FullScreenPage />
</Modal>
<div className={`sider ${"shortMenuShow_overflow"}`}>
<div className={`sider ${'shortMenuShow_overflow'}`}>
<Menu
mode="inline"
inlineCollapsed={collapsed}
style={{
// marginTop: 24,
// paddingRight: 10,
paddingTop: '21px'
paddingTop: '21px',
}}
// defaultOpenKeys={['sub1']}
openKeys={openKeys}
onOpenChange={onOpenChange}
inlineIndent="16"
onClick={clickMenu}
>
@ -266,16 +304,17 @@ const Sider = (props) => {
<Icon type="home"></Icon>
<span>首页</span>
</MenuItem>
{localStorage.getItem("webOrgId") == '00300000-0000-0000-0000-000000000000'?
<MenuItem key="largeScreen" onClick={navToLarge} title={'可视化大屏'} >
{/* <img
{localStorage.getItem('webOrgId') == '00300000-0000-0000-0000-000000000000' ? (
<MenuItem key="largeScreen" onClick={navToLarge} title={'可视化大屏'}>
{/* <img
src={require("../assets/layout/menu-all.png")}
alt=""
className="sider__menuAll-icon"
/> */}
<Icon type="alert"></Icon>
<span>可视化大屏</span>
</MenuItem>:null}
<Icon type="alert"></Icon>
<span>可视化大屏</span>
</MenuItem>
) : null}
<SubMenu
key="sub1"
title={
@ -283,18 +322,13 @@ const Sider = (props) => {
<Icon type="profile" title="常用菜单"></Icon>
<span>常用菜单</span>
</span>
}
onTitleClick={clickMenu}
>
{favorMenus.map((menu2, index2) => {
return (
<MenuItem
key={`${menu2.ID}_${index2}`}
onClick={() => handleActiveMenu(menu2)}
title={menu2.NAME}
>
<Icon type={menu2.ICON ? menu2.ICON : "file-text"} />
<MenuItem key={`${menu2.ID}_${index2}`} onClick={() => handleActiveMenu(menu2)} title={menu2.NAME}>
<Icon type={menu2.ICON ? menu2.ICON : 'file-text'} />
<span>{menu2.NAME}</span>
</MenuItem>
@ -308,47 +342,38 @@ const Sider = (props) => {
key={`${menu.Node.ID}_${index}`}
title={
<span>
<Icon type={menu.Node.ICON ? menu.Node.ICON : "reconciliation"} title={menu.Node.NAME}></Icon>
<Icon type={menu.Node.ICON ? menu.Node.ICON : 'reconciliation'} title={menu.Node.NAME}></Icon>
<span>{menu.Node.NAME}</span>
</span>
}
onTitleClick={() => {
setCurrMenu(menu.Node);
clickMenu()
clickMenu();
}}
>
{/* 二级 */}
{menu.Children.map((menu1, index1) => {
const isLevel4Menu = menu1.Children?.find(
(mc) => mc.Children?.length
);
const childMenus = isLevel4Menu
? [menu]
: menu.Children || [];
const isLevel4Menu = menu1.Children?.find((mc) => mc.Children?.length);
const childMenus = isLevel4Menu ? [menu] : menu.Children || [];
return (
<SubMenu
key={`${menu1.Node.ID}_${index1}`}
title={
<span>
<Icon type={menu1.Node.ICON ? menu1.Node.ICON : "reconciliation"}></Icon>
<Icon type={menu1.Node.ICON ? menu1.Node.ICON : 'reconciliation'}></Icon>
<span>{menu1.Node.NAME}</span>
</span>
}
>
{/* 三级 */}
{menu1.Children.map((menu3, index3) => {
return (
<MenuItem key={`${menu3.Node.ID}_${index3}`} onClick={() =>
isLevel4Menu
? navToBackend(menu3.Node)
: handleActiveMenu(menu3.Node)
}
<MenuItem
key={`${menu3.Node.ID}_${index3}`}
onClick={() => (isLevel4Menu ? navToBackend(menu3.Node) : handleActiveMenu(menu3.Node))}
title={menu3.Node.NAME}
>
<Icon type={menu3.Node.ICON ? menu3.Node.ICON : "file-text"} />
<Icon type={menu3.Node.ICON ? menu3.Node.ICON : 'file-text'} />
<span>{menu3.Node.NAME}</span>
</MenuItem>
@ -361,9 +386,9 @@ const Sider = (props) => {
);
})}
</Menu>
</div>
<div onClick={toggleCollapsed}
<div
onClick={toggleCollapsed}
style={{
marginBottom: 16,
display: 'flex',
@ -375,17 +400,18 @@ const Sider = (props) => {
backgroundColor: '#DEE0E8',
borderRadius: '6px',
margin: '0px 0px 0px 0px',
}}>
<Icon type={collapsed ? 'right' : 'left'} style={{
fontSize: '12px',
display: 'flex',
// alignItems:'right',
// justifyContent:'flex-end'
}} />
}}
>
<Icon
type={collapsed ? 'right' : 'left'}
style={{
fontSize: '12px',
display: 'flex',
// alignItems:'right',
// justifyContent:'flex-end'
}}
/>
</div>
</div>
);
};