127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import React, { memo, useState } from 'react';
|
|
import { useNavigate, useLocation, NavigateFunction } from 'react-router-dom';
|
|
import { Menu, MenuValue } from 'tdesign-react';
|
|
import router, { IRouter } from 'router';
|
|
import { resolve } from 'utils/path';
|
|
import { useAppSelector } from 'modules/store';
|
|
import { selectGlobal } from 'modules/global';
|
|
import { selectUser } from 'modules/user';
|
|
import MenuLogo from './MenuLogo';
|
|
import Style from './Menu.module.less';
|
|
|
|
const { SubMenu, MenuItem, HeadMenu } = Menu;
|
|
|
|
interface IMenuProps {
|
|
showLogo?: boolean;
|
|
showOperation?: boolean;
|
|
}
|
|
|
|
// 纯函数:递归渲染菜单(不在递归/循环内调用 Hook)
|
|
const renderMenuItems = (menu: IRouter[], navigate: NavigateFunction, isAdmin: boolean, parentPath = '') => {
|
|
return menu.map((item) => {
|
|
const { children, meta, path } = item;
|
|
|
|
if (!meta || meta?.hidden === true) {
|
|
return null;
|
|
}
|
|
|
|
if (meta.admin && !isAdmin) {
|
|
// 平台管理菜单仅 admin 角色可见
|
|
return null;
|
|
}
|
|
|
|
const { Icon, title, single } = meta;
|
|
const routerPath = resolve(parentPath, path);
|
|
|
|
if (!children || children.length === 0) {
|
|
return (
|
|
<MenuItem
|
|
key={routerPath}
|
|
value={routerPath}
|
|
icon={Icon ? <Icon /> : undefined}
|
|
onClick={() => navigate(routerPath)}
|
|
>
|
|
{title}
|
|
</MenuItem>
|
|
);
|
|
}
|
|
|
|
if (single && children?.length > 0) {
|
|
const firstChild = children[0];
|
|
if (firstChild?.meta && !firstChild?.meta?.hidden) {
|
|
const { Icon, title } = meta;
|
|
const singlePath = resolve(resolve(parentPath, path), firstChild.path);
|
|
return (
|
|
<MenuItem
|
|
key={singlePath}
|
|
value={singlePath}
|
|
icon={Icon ? <Icon /> : undefined}
|
|
onClick={() => navigate(singlePath)}
|
|
>
|
|
{title}
|
|
</MenuItem>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<SubMenu key={routerPath} value={routerPath} title={title} icon={Icon ? <Icon /> : undefined}>
|
|
{renderMenuItems(children, navigate, isAdmin, routerPath)}
|
|
</SubMenu>
|
|
);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 顶部菜单
|
|
*/
|
|
export const HeaderMenu = memo(() => {
|
|
const globalState = useAppSelector(selectGlobal);
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const userState = useAppSelector(selectUser);
|
|
const isAdmin = userState.userInfo?.role === 'admin';
|
|
const [active, setActive] = useState<MenuValue>(location.pathname); // todo
|
|
|
|
return (
|
|
<HeadMenu
|
|
expandType='popup'
|
|
style={{ marginBottom: 20 }}
|
|
value={active}
|
|
theme={globalState.theme}
|
|
onChange={(v) => setActive(v)}
|
|
>
|
|
{renderMenuItems(router, navigate, isAdmin)}
|
|
</HeadMenu>
|
|
);
|
|
});
|
|
|
|
/**
|
|
* 左侧菜单
|
|
*/
|
|
export default memo((props: IMenuProps) => {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const globalState = useAppSelector(selectGlobal);
|
|
const userState = useAppSelector(selectUser);
|
|
const isAdmin = userState.userInfo?.role === 'admin';
|
|
|
|
const { version } = globalState;
|
|
const bottomText = globalState.collapsed ? version : 'EveryPublish ' + version;
|
|
|
|
return (
|
|
<Menu
|
|
width='232px'
|
|
style={{ flexShrink: 0, height: '100%' }}
|
|
className={Style.menuPanel2}
|
|
value={location.pathname}
|
|
theme={globalState.theme}
|
|
collapsed={globalState.collapsed}
|
|
operations={props.showOperation ? <div className={Style.menuTip}>{bottomText}</div> : undefined}
|
|
logo={props.showLogo ? <MenuLogo collapsed={globalState.collapsed} /> : undefined}
|
|
>
|
|
{renderMenuItems(router, navigate, isAdmin)}
|
|
</Menu>
|
|
);
|
|
});
|