后台管理系统一般分为三部分,左侧菜单栏、顶部导航栏和内容,这里使用了Ant Design layout组件: https://ant-design.antgroup.com/components/layout-cn,需要将layout进行划分三块。
在项目目录下面创建layouts文件夹

layout基础页面 index.tsx
可以分为左侧菜单组件LayOutMenu头部导航LayoutHeader 以及内容区,使用react-router-dom提供的 Outlet渲染对应的路由组件
javascript
import { Layout, theme } from 'antd';
import { Outlet } from "react-router-dom";
const { Sider, Content } = Layout;
import LayOutMenu from './components/Menu/index'
import LayoutHeader from './components/Header/index'
import LayoutTabs from './components/Tabs/index'
import './index.scss'
import { useMenuStore } from '@/store/useMenuStore';
const LayoutIndex = () => {
const { isCollapsed } = useMenuStore()
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout className='container'>
<Sider trigger={null} collapsible collapsed={isCollapsed}>
<LayOutMenu />
</Sider>
<Layout>
<LayoutHeader />
<LayoutTabs />
<Content
style={{
margin: '15px 15px',
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<Outlet />
</Content>
</Layout>
</Layout>
)
}
export default LayoutIndex
顶部导航 Header组件
头部导航包括折叠图标CollapseIcon和面包屑组件以及右侧角色信息
javascript
import { Layout, theme } from 'antd'
const { Header } = Layout
import CollapseIcon from './components/CollapseIcon'
import Fullscreen from './components/Fullscreen'
import AvatarIcon from './components/AvatarIcon'
import Breadcrumb from './components/Breadcrumb'
import { getAccessUser } from '@/utils/auth'
import './index.scss'
const LayoutHeader = () => {
const { token: { colorBgContainer } } = theme.useToken();
const { realName, loginName } = getAccessUser()
return (
<Header style={{ padding: '0 40px 0 20px', background: colorBgContainer }}>
<div className='header-lf'>
{/* 折叠图标 */}
<CollapseIcon />
{/* 面包屑 */}
<Breadcrumb />
</div>
<div className='header-rt'>
<span className="username">{ realName } [ { loginName } ]</span>
<AvatarIcon />
</div>
</Header>
)
}
export default LayoutHeader
折叠图标CollapseIcon组件
javascript
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { Button } from 'antd'
import { useMenuStore } from '@/store/useMenuStore';
const CollapseIcon = () => {
const { isCollapsed, toggleCollapsed } = useMenuStore()
return (
<Button
type="text"
icon={isCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => toggleCollapsed(!isCollapsed)}
style={{
fontSize: '16px',
width: 64,
height: 50,
}}
/>
)
}
export default CollapseIcon
左侧菜单组件Menu
javascript
import React, { useEffect, useState } from 'react'
import { useNavigate } from "react-router-dom";
import { searchRoute } from "@/utils/index";
import { Menu } from 'antd'
import Logo from './components/Logo'
import type { MenuProps } from "antd"
import * as Icons from "@ant-design/icons"
import { routerArray } from '@/router/routes'
import './index.scss'
const LayoutMenu = () => {
// 定义 menu 类型
type MenuItem = Required<MenuProps>["items"][number];
const getItem = (
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
type?: "group"
): MenuItem => {
return {
key,
icon,
children,
label,
type
} as MenuItem;
};
// 动态渲染 Icon 图标
const customIcons: { [key: string]: any } = Icons;
const addIcon = (name: string) => {
if (!name || !customIcons[name]) return null;
return React.createElement(customIcons[name]);
};
const [ menuList, setMenuList ] = useState<MenuProps['items']>([])
// 处理
const deepLoopFloat = (menuList: Menu.MenuOptions[], newArr: MenuItem[] = []) => {
menuList.forEach((item: Menu.MenuOptions) => {
if(item?.meta?.hidden) return
if (!item?.children?.length) return newArr.push(getItem(item.meta.title, item.path, addIcon(item.icon!)));
// 没有meta 则不渲染
if (!item?.meta?.title) return deepLoopFloat(item.children, newArr);
newArr.push(getItem(item.meta.title, item.path, addIcon(item.icon!), deepLoopFloat(item.children)));
});
return newArr;
};
useEffect(() => {
// console.log((deepLoopFloat(routes)))
setMenuList(deepLoopFloat(routerArray))
}, [])
// 点击当前菜单跳转页面
const navigate = useNavigate();
const clickMenu: MenuProps["onClick"] = ({ key }: { key: string }) => {
const route = searchRoute(key, routerArray);
if (route.isLink) window.open(route.isLink, "_blank");
navigate(key);
};
return (
<div className='menu'>
<Logo />
<Menu
theme="dark"
mode="inline"
triggerSubMenuAction="click"
items={menuList}
onClick={clickMenu}
/>
</div>
)
}
export default LayoutMenu
目前菜单使用的是静态路由也就是前端在页面写好的路由,菜单权限控制是在每个对应的菜单下面添加对应的权限码标识,在用户登录成功后从后台拿到对应的角色菜单权限码,然后跟自己定义好的菜单标识递归进行对比,如果存在左侧菜单就显示否则左侧菜单就不显示。