474 lines
17 KiB
JavaScript
474 lines
17 KiB
JavaScript
import React, { useState, useEffect, useMemo, useRef } from "react";
|
||
import { connect } from "dva";
|
||
import { withRouter, matchPath } from "dva/router";
|
||
import { Scrollbars } from "react-custom-scrollbars";
|
||
import { Icon } from "antd";
|
||
import EnergyIcon from "../utils/energyIcon";
|
||
import { $consts } from "../plugins";
|
||
import "./sider1.less";
|
||
|
||
const Sider = (props) => {
|
||
const [activeKeepMenu, setActiveKeepMenu] = useState(null);
|
||
const [shortMenuShow, setShortMenuShow] = useState(true);
|
||
const [expandMenuShow, setExpandMenuShow] = useState(true);
|
||
|
||
/** 菜单页展开与收起 */
|
||
const timer = useRef(-1);
|
||
const handleExpandMenuShow = () => {
|
||
clearTimeout(timer.current);
|
||
setExpandMenuShow(true);
|
||
};
|
||
const handleExpandMenuHide = () => {
|
||
clearTimeout(timer.current);
|
||
timer.current = setTimeout(() => {
|
||
setExpandMenuShow(false);
|
||
}, 200);
|
||
};
|
||
|
||
/** 控制三级菜单收缩和展开 */
|
||
const [menuShrink, setMenuShrink] = useState({});
|
||
const handleMenuShrink = (menuId) => {
|
||
const data = Object.assign({}, menuShrink);
|
||
data[menuId] ? (data[menuId] = "") : (data[menuId] = menuId);
|
||
setMenuShrink(data);
|
||
};
|
||
|
||
/** 回到首页 */
|
||
const navToHome = () => {
|
||
setActiveMenu(null);
|
||
props.history.push({ pathname: "/home" });
|
||
};
|
||
|
||
/** 子菜单路由 */
|
||
const navToMenu = (menu) => {
|
||
props.history.push({ pathname: `/main/${menu.ID}` });
|
||
};
|
||
|
||
/** 后台设置:四级菜单 */
|
||
const navToBackend = (menu) => {
|
||
props.history.push({ pathname: `/backend/${menu.ID}` });
|
||
};
|
||
|
||
/** 一级菜单显示菜单 */
|
||
const [currMenu, setCurrMenu] = useState(null);
|
||
useEffect(() => {
|
||
const menus = props.login.loginInfo?.Menus || [];
|
||
menus.length && !currMenu && setCurrMenu(menus[0].Node);
|
||
}, [props.login.loginInfo?.Menus]);
|
||
|
||
/** 已激活菜单 */
|
||
const [activeMenu, setActiveMenu] = useState(null);
|
||
const handleActiveMenu = (menu) => {
|
||
setActiveMenu(menu);
|
||
navToMenu(menu);
|
||
};
|
||
|
||
/** 页面刷新时候,更新菜单的选中状态 */
|
||
useEffect(() => {
|
||
const { pathname } = props.location;
|
||
const mathHome = matchPath(pathname, {
|
||
path: $consts["ROUTE/HOME"],
|
||
exact: true,
|
||
strict: true,
|
||
});
|
||
if (mathHome) {
|
||
activeMenu && setActiveMenu(null);
|
||
}
|
||
const mathMain = matchPath(pathname, {
|
||
path: $consts["ROUTE/MAIN"],
|
||
exact: true,
|
||
strict: true,
|
||
});
|
||
if (mathMain) {
|
||
const { flatMenus } = props.login;
|
||
const { menuId } = mathMain.params;
|
||
const find = flatMenus.find((item) => item.ID === menuId);
|
||
if (find) {
|
||
props.dispatch({
|
||
type: "app/updateActivatedMenu",
|
||
payload: {
|
||
currActivatedTab: find.ID,
|
||
currActivatedMenu: find,
|
||
},
|
||
});
|
||
!activeMenu && setActiveMenu(find);
|
||
}
|
||
}
|
||
}, [props.location.pathname, props.login.flatMenus, activeMenu]);
|
||
|
||
/** 常用菜单 */
|
||
const favorMenus = useMemo(() => {
|
||
const { flatMenus } = props.login;
|
||
return flatMenus.filter((menu) => menu.IS_RESIDENT);
|
||
}, [props.login.flatMenus]);
|
||
|
||
/** 一级菜单 */
|
||
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 leafMenuSections =
|
||
menus.length < 3 ? 1 : menus.length === 3 ? 2 : menus.length - 2;
|
||
return {
|
||
menus,
|
||
width: menuWidth < 120 ? 120 : menuWidth,
|
||
leafMenuSections,
|
||
};
|
||
}, [props.login.loginInfo?.Menus]);
|
||
|
||
/** 二三级子菜单 */
|
||
const subMenus = useMemo(() => {
|
||
const { menus } = topMenus;
|
||
const find = menus.find((menu) => menu.Node.ID === currMenu?.ID);
|
||
return find?.Children || [];
|
||
}, [currMenu, topMenus]);
|
||
|
||
/** 三级子菜单默认收缩与展开 */
|
||
const { IS_MENU_SHRINK = false } = props.login.baseConfig;
|
||
useEffect(() => {
|
||
// IS_MENU_SHRINK为true时, 收缩所有三级菜单
|
||
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)
|
||
)
|
||
);
|
||
setMenuShrink(data);
|
||
}
|
||
}, [IS_MENU_SHRINK]);
|
||
|
||
/** 登录页隐藏 */
|
||
if (props.matchLogin) return null;
|
||
|
||
return (
|
||
<div
|
||
// onMouseEnter={() => setShortMenuShow(true)}
|
||
// onMouseLeave={() => setShortMenuShow(false)}
|
||
className={`sider ${shortMenuShow ? "shortMenuShow" : ""}`}
|
||
>
|
||
{/** 所有菜单 */}
|
||
<div
|
||
// onMouseEnter={handleExpandMenuShow}
|
||
// onMouseLeave={handleExpandMenuHide}
|
||
onClick={navToHome}
|
||
className="sider__menuAll"
|
||
>
|
||
<img
|
||
src={require("../assets/layout/menu-all.png")}
|
||
alt=""
|
||
className="sider__menuAll-icon"
|
||
/>
|
||
<div className="sider__menu-font">
|
||
<span className="sider__menu-font--text">首页</span>
|
||
<Icon type="right" className="sider__menu-font--icon" />
|
||
</div>
|
||
</div>
|
||
{/** 常用菜单 */}
|
||
<Scrollbars
|
||
autoHide
|
||
autoHideTimeout={1000}
|
||
autoHideDuration={200}
|
||
className="sider__scrollbars"
|
||
>
|
||
<div className="sider__menuExpand-header">
|
||
{topMenus.menus.map((menu, index) => {
|
||
const active = menu.Node.ID === activeMenu?.ID;
|
||
const activeKeep = menu.Node.ID === activeKeepMenu?.ID;
|
||
const wrapClass = `sider__menuExpand-menu ${
|
||
index === topMenus.menus.length - 1 ? "lastChild" : ""
|
||
}`;
|
||
const iconClass = `sider__menuExpand-menu--itemIcon ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
const fontClass = `sider__menuExpand-menu--itemText text-ellipsis ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
const indicatorClass = `sider__menuExpand-menu--indicator ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
return (
|
||
<div
|
||
key={`${menu.Node.ID}_${index}`}
|
||
// onMouseEnter={() => {
|
||
// setCurrMenu(menu.Node);
|
||
// setActiveKeepMenu(menu.Node);
|
||
// }}
|
||
onClick={() =>{handleActiveMenu(menu.Node);setCurrMenu(menu.Node);
|
||
setActiveKeepMenu(menu.Node);} }
|
||
className={wrapClass}
|
||
>
|
||
<div className="sider__menuExpand-menu--item">
|
||
<Icon
|
||
type={menu.Node.ICON ? menu.ICON : "file-text"}
|
||
className={iconClass}
|
||
/>
|
||
<div className={fontClass}>{menu.Node.NAME}</div>
|
||
</div>
|
||
<div className={indicatorClass} />
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
{/** 二三级菜单 */}
|
||
<div className="sider__menuExpand-body">
|
||
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200}>
|
||
<div className="sider__menuExpand-body--scroll">
|
||
{subMenus.map((menu, index) => {
|
||
const isLevel4Menu = menu.Children?.find(
|
||
(mc) => mc.Children?.length
|
||
);
|
||
const childMenus = isLevel4Menu
|
||
? [menu]
|
||
: menu.Children || [];
|
||
return (
|
||
<div
|
||
key={`${menu.Node.ID}_${index}`}
|
||
className="sider__menuExpand-subMenu"
|
||
style={{
|
||
flex: `0 0 ${(1 / topMenus.leafMenuSections) * 100}%`,
|
||
}}
|
||
>
|
||
{/** 二级菜单 */}
|
||
<div
|
||
onClick={() => handleMenuShrink(menu.Node.ID)}
|
||
className="sider__menuExpand-subMenu--title"
|
||
>
|
||
{menu.Node.NAME}
|
||
<Icon
|
||
type={!menuShrink[menu.Node.ID] ? "down" : "up"}
|
||
className="sider__menuExpand-subMenu--title--icon"
|
||
/>
|
||
</div>
|
||
{/** 三级菜单 */}
|
||
{!menuShrink[menu.Node.ID]
|
||
? childMenus.map((child, childIndex) => {
|
||
const active = child.Node.ID === activeMenu?.ID;
|
||
const iconClass = `sider__menuExpand-menu--itemIcon ${
|
||
active ? "active" : ""
|
||
}`;
|
||
const fontClass = `sider__menuExpand-menu--itemText text-ellipsis ${
|
||
active ? "active" : ""
|
||
}`;
|
||
return (
|
||
<div
|
||
key={`${child.Node.ID}_${index}_${childIndex}`}
|
||
onClick={() =>
|
||
isLevel4Menu
|
||
? navToBackend(child.Node)
|
||
: handleActiveMenu(child.Node)
|
||
}
|
||
className="sider__menuExpand-subMenu--menu"
|
||
>
|
||
<Icon
|
||
type={
|
||
child.Node.ICON
|
||
? child.Node.ICON
|
||
: "file-text"
|
||
}
|
||
className={iconClass}
|
||
/>
|
||
<div className={fontClass}>
|
||
{child.Node.NAME}
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
: null}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</Scrollbars>
|
||
</div>
|
||
{favorMenus.map((menu, index) => {
|
||
const active = menu.ID === activeMenu?.ID;
|
||
const wrapClass = `sider__menu ${
|
||
index === favorMenus.length - 1 ? "lastChild" : ""
|
||
}`;
|
||
const iconClass = `sider__menu-icon ${active ? "active" : ""}`;
|
||
const fontClass = `sider__menu-font--text ${active ? "active" : ""}`;
|
||
return (
|
||
<div
|
||
key={`${menu.ID}_${index}`}
|
||
onClick={() => handleActiveMenu(menu)}
|
||
className={wrapClass}
|
||
>
|
||
<Icon
|
||
type={menu.ICON ? menu.ICON : "file-text"}
|
||
className={iconClass}
|
||
/>
|
||
<div className="sider__menu-font">
|
||
<span className={fontClass}>{menu.NAME}</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
|
||
|
||
</Scrollbars>
|
||
{/** 展开菜单 */}
|
||
<div
|
||
// onMouseEnter={expandMenuShow ? handleExpandMenuShow : handleExpandMenuHide}
|
||
// onMouseLeave={handleExpandMenuHide}
|
||
className={`sider__menuExpand ${
|
||
expandMenuShow ? "expandMenuShow" : ""
|
||
}`}
|
||
style={{
|
||
width: expandMenuShow
|
||
? topMenus.width + 48 < 300
|
||
? 300
|
||
: topMenus.width + 48
|
||
: 0,
|
||
overflow: expandMenuShow ? "visible" : "hidden",
|
||
}} // 加 padding: 24
|
||
>
|
||
{/* 留白, 扩大上方onMouseEnter的范围 */}
|
||
<div
|
||
className={`${
|
||
expandMenuShow
|
||
? "sider__menuExpand-divempty"
|
||
: "sider__menuExpand-divempty-hide"
|
||
}`}
|
||
></div>
|
||
{/** inner 加 padding,否则加在 sider__menuExpand 上的话,width 为 0,也会有 padding 的宽度 */}
|
||
<div
|
||
className="sider__menuExpand-inner"
|
||
style={{
|
||
width: topMenus.width + 48 < 300 ? 300 : topMenus.width + 48,
|
||
}}
|
||
>
|
||
{/** 一级菜单 */}
|
||
<div className="sider__menuExpand-header">
|
||
{topMenus.menus.map((menu, index) => {
|
||
const active = menu.Node.ID === activeMenu?.ID;
|
||
const activeKeep = menu.Node.ID === activeKeepMenu?.ID;
|
||
const wrapClass = `sider__menuExpand-menu ${
|
||
index === topMenus.menus.length - 1 ? "lastChild" : ""
|
||
}`;
|
||
const iconClass = `sider__menuExpand-menu--itemIcon ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
const fontClass = `sider__menuExpand-menu--itemText text-ellipsis ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
const indicatorClass = `sider__menuExpand-menu--indicator ${
|
||
active ? "active" : ""
|
||
} ${activeKeep ? "active" : ""}`;
|
||
return (
|
||
<div
|
||
key={`${menu.Node.ID}_${index}`}
|
||
onMouseEnter={() => {
|
||
setCurrMenu(menu.Node);
|
||
setActiveKeepMenu(menu.Node);
|
||
}}
|
||
onClick={() => handleActiveMenu(menu.Node)}
|
||
className={wrapClass}
|
||
>
|
||
<div className="sider__menuExpand-menu--item">
|
||
<Icon
|
||
type={menu.Node.ICON ? menu.ICON : "file-text"}
|
||
className={iconClass}
|
||
/>
|
||
<div className={fontClass}>{menu.Node.NAME}</div>
|
||
</div>
|
||
<div className={indicatorClass} />
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
{/** 二三级菜单 */}
|
||
<div className="sider__menuExpand-body">
|
||
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200}>
|
||
<div className="sider__menuExpand-body--scroll">
|
||
{subMenus.map((menu, index) => {
|
||
const isLevel4Menu = menu.Children?.find(
|
||
(mc) => mc.Children?.length
|
||
);
|
||
const childMenus = isLevel4Menu
|
||
? [menu]
|
||
: menu.Children || [];
|
||
return (
|
||
<div
|
||
key={`${menu.Node.ID}_${index}`}
|
||
className="sider__menuExpand-subMenu"
|
||
style={{
|
||
flex: `0 0 ${(1 / topMenus.leafMenuSections) * 100}%`,
|
||
}}
|
||
>
|
||
{/** 二级菜单 */}
|
||
<div
|
||
onClick={() => handleMenuShrink(menu.Node.ID)}
|
||
className="sider__menuExpand-subMenu--title"
|
||
>
|
||
{menu.Node.NAME}
|
||
<Icon
|
||
type={!menuShrink[menu.Node.ID] ? "down" : "up"}
|
||
className="sider__menuExpand-subMenu--title--icon"
|
||
/>
|
||
</div>
|
||
{/** 三级菜单 */}
|
||
{!menuShrink[menu.Node.ID]
|
||
? childMenus.map((child, childIndex) => {
|
||
const active = child.Node.ID === activeMenu?.ID;
|
||
const iconClass = `sider__menuExpand-menu--itemIcon ${
|
||
active ? "active" : ""
|
||
}`;
|
||
const fontClass = `sider__menuExpand-menu--itemText text-ellipsis ${
|
||
active ? "active" : ""
|
||
}`;
|
||
return (
|
||
<div
|
||
key={`${child.Node.ID}_${index}_${childIndex}`}
|
||
onClick={() =>
|
||
isLevel4Menu
|
||
? navToBackend(child.Node)
|
||
: handleActiveMenu(child.Node)
|
||
}
|
||
className="sider__menuExpand-subMenu--menu"
|
||
>
|
||
<Icon
|
||
type={
|
||
child.Node.ICON
|
||
? child.Node.ICON
|
||
: "file-text"
|
||
}
|
||
className={iconClass}
|
||
/>
|
||
<div className={fontClass}>
|
||
{child.Node.NAME}
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
: null}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</Scrollbars>
|
||
</div>
|
||
</div>
|
||
{/** 左边的 border */}
|
||
<div className="sider__menuExpand-divider" />
|
||
{/* 留白, 扩大右侧onMouseEnter的范围 */}
|
||
<div
|
||
className={`${
|
||
expandMenuShow
|
||
? "sider__menuExpand-divempty-right"
|
||
: "sider__menuExpand-divempty-hide"
|
||
}`}
|
||
></div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default withRouter(connect(({ login }) => ({ login }))(Sider));
|