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

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

相关推荐
郝开3 小时前
7. React组件基础样式控制:行内样式,class类名控制
react.js
DoraBigHead3 小时前
🧭 React 理念:让时间屈服于 UI —— 从同步到可中断的演化之路
前端·javascript·面试
千码君20164 小时前
React Native:发现默认参数children【特殊的prop】
javascript·react native·ecmascript·react·组件树
Never_Satisfied5 小时前
在JavaScript / HTML中,line-height是一个CSS属性
javascript·css·html
用户916357440956 小时前
LeetCode热题100——15.三数之和
javascript·算法
skykun6 小时前
都2026年了还在说闭包吗?
javascript
饮水机战神6 小时前
小程序被下架后,我连夜加了个 "安全接口"
前端·javascript
柯南二号6 小时前
【大前端】 TypeScript vs JavaScript:全面对比与实践指南
前端·javascript·typescript
Larry_Yanan6 小时前
QML学习笔记(三十一)QML的Flow定位器
java·前端·javascript·笔记·qt·学习·ui