react+andDesign+vite+ts从零搭建后台管理系统(三)-Layout布局


后台管理系统一般分为三部分,左侧菜单栏、顶部导航栏和内容,这里使用了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

目前菜单使用的是静态路由也就是前端在页面写好的路由,菜单权限控制是在每个对应的菜单下面添加对应的权限码标识,在用户登录成功后从后台拿到对应的角色菜单权限码,然后跟自己定义好的菜单标识递归进行对比,如果存在左侧菜单就显示否则左侧菜单就不显示。

相关推荐
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
铅笔侠_小龙虾6 小时前
Flutter 实战: 计算器
开发语言·javascript·flutter
大模型玩家七七6 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
2501_944711436 小时前
JS 对象遍历全解析
开发语言·前端·javascript
发现一只大呆瓜7 小时前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
阔皮大师7 小时前
INote轻量文本编辑器
java·javascript·python·c#
lbb 小魔仙7 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js
_codemonster7 小时前
Vue的三种使用方式对比
前端·javascript·vue.js
全栈前端老曹8 小时前
【MongoDB】Node.js 集成 —— Mongoose ORM、Schema 设计、Model 操作
前端·javascript·数据库·mongodb·node.js·nosql·全栈