从0开始的中后台管理系统-6(添加用户以及绑定角色给用户动态添加权限,以及在layout父路由组件去进行路径跳转判断)

​编辑

权限设计,我们现在创建了用户列表以及角色列表,还有菜单列表,那么可以通过给用户绑定角色,然后角色拥有功能菜单的权限,权限可以被用户看到,实现权限设计功能,也就是说我们在创建用户的时候,设置一个角色,比如roleList,1对应的管理员,2对应的普通用户,然后我们登录的时候把roleList加密到token中,然后再我们调用权限路由的时候通过roleList(token解密拿到)然后去json里面筛选出对应管理员或者普通用户的权限包含的能访问的菜单id,然后拿到了菜单id之后去菜单列表里面递归筛选出所有对应id的菜单,也就是拿到了所有对应id的组件路径和菜单名称,这样就可以去跳转路径以及展示了。

然后我们可以在所有权限路由组件的父组件中loader进行拦截提前获取,然后把当前用户权限能访问到的菜单路径全部拿过来,然后就在layout组件里面进行逻辑判断,如果你访问的不是菜单权限数组里面包括的路径,以及不需要权限就可以访问的比如403404页面那么就跳转到403.前端也可以手动去设置比如一个属性,然后可以主动设置一些没有权限也可以访问的页面。

这里权限设计的思路,下面直接上代码。

1.首先是路由loader设置

javascript 复制代码
import {
  createBrowserRouter,
  Navigate,
  useRoutes,
  redirect
} from 'react-router-dom'
import Login from '@/views/login/Login'
import Welcome from '@/views/Welcome'
import Error404 from '@/views/Error404'
import Error403 from '@/views/Error403'
import Layout from '@/layout/index'
import DashBoard from '@/views/dashboard'
import UserList from '@/views/system/user'
import DeptList from '@/views/system/dept'
import MenuList from '@/views/system/menu'
import AuthLoader from './AuthLoader'
import RoleList from '@/views/system/role'
import OrderList from '@/views/system/order/OrderList'
export const router = [
  {
    id: 'layout',
    element: <Layout />,
    loader: AuthLoader,
    children: [
      {
        path: '/welcome',
        element: <Welcome />
      },
      {
        path: '/dashboard',
        element: <DashBoard />
      },
      {
        path: '/userlist',
        element: <UserList />
      },
      {
        path: '/deptlist',
        element: <DeptList />
      },
      {
        path: 'menulist',
        element: <MenuList />
      },
      {
        path: '/rolelist',
        element: <RoleList />
      },
      {
        path: '/orderlist',
        element: <OrderList />
      }
    ]
  },
  { path: '/', element: <Navigate to='/welcome' /> },
  { path: '/login', element: <Login /> },
  { path: '*', element: <Navigate to='/404' /> },
  { path: '/404', element: <Error404 /> },
  { path: '/403', element: <Error403 /> }
]

export default createBrowserRouter(router)
// export default function () {
//   return useRoutes(router)
// }

在所有权限组件外面设置父组件loader进行提前拦截以及布局。

typescript 复制代码
import api from "@/api"
import type { Menu } from "@/types/api"
import { getMenuPath } from "@/utils"
export interface IAuthLoader{
  buttonList:string[],
  menuList:Menu.MenuItem[],
  menuPathList:string[]
}
export default async function AuthLoader(){
  const data = await api.getPermissionList()
  const menuPathList = getMenuPath(data.menuList)
  console.log('menuPathList',menuPathList)
return {
  buttonList:data.buttonList,
  menuList:data.menuList,
  menuPathList:menuPathList
}
}

然后通过访问后端路由获取到当前权限的菜单列表以及将其中的path拿出来准备做一些判断,就是不能直接去访问路由,就算看不到也不能直接访问。然后菜单列表直接展示在导航菜单区域。

2.菜单提前拦截

typescript 复制代码
import React, { useEffect } from 'react'
import {
  UploadOutlined,
  UserOutlined,
  VideoCameraOutlined
} from '@ant-design/icons'
import { Layout, theme, Watermark } from 'antd'
import NavHeader from '@/components/NavHeader'
import NavFooter from '@/components/NavFooter'
import SideMenu from '@/components/Menu'
import { router } from '@/router'
import {
  Navigate,
  Outlet,
  useLocation,
  useRouteLoaderData
} from 'react-router-dom'
import styles from './index.module.less'
import api from '@/api'
import storage from '@/utils/storage'
import store, { useStore } from '@/store'
import type { IAuthLoader } from '@/router/AuthLoader'
import { searchRoute } from '@/utils'
const { Sider } = Layout
const App: React.FC = () => {
  const { pathname } = useLocation()
  const state = useStore()
  useEffect(() => {
    getUserInfo()
  }, [])
  const getUserInfo = async () => {
    const data = await api.getUserInfo()
    store.updataUserInfo(data)
    state.updateUserInfo(data)
    console.log('gogoog ', data.userName)
  }
  const route = searchRoute(pathname, router)
  if (route && route.meta?.auth === false) {
    //继续执行
  } else {
    //权限判断
    const data = useRouteLoaderData('layout') as IAuthLoader
    const staticPath = ['/welcome', '/403', '/404']
    if (
      !data.menuPathList.includes(pathname) &&
      !staticPath.includes(pathname)
    ) {
      return <Navigate to='/403' />
    }
  }
  return (
    <Watermark content={store.userInfo.userName}>
      <Layout>
        <Sider>
          <SideMenu />
        </Sider>
        <Layout>
          {/* <Header style={{ padding: 0, background: colorBgContainer }}>
            <NavHeader />
          </Header> /}
          <NavHeader />
          <div className={styles.content}>
            <div className={styles.wrapper}>
              <Outlet />
            </div>
            <NavFooter />
          </div>
          {/ <div className={styles.wrapper}>
            <Outlet />
          </div>
          <NavFooter /> */}
        </Layout>
      </Layout>
    </Watermark>
  )
}

export default App

在layout组件进行拦截,就是如果你访问的路径没有被你的权限路径数组包含或者不需要权限可以访问的路径,然后就会强制跳转到403,然后只能回到首页。

然后菜单列表会被Menu组件渲染到导航区。

typescript 复制代码
import React, { useEffect, useState } from 'react'
import { Menu as IMenu } from 'antd'
import type { Menu } from '@/types/api'
import {
  DesktopOutlined,
  SettingOutlined,
  TeamOutlined
} from '@ant-design/icons'
import styles from './index.module.less'
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
import type { MenuProps } from 'antd'
import * as Icons from '@ant-design/icons'
export default function SideMenu() {
  const [menuList, setMenuList] = useState<MenuItem[]>([])
  const data = useRouteLoaderData('layout')
  const [selectedKeys, setSelectKeys] = useState<string[]>([])
  console.log(' data', data)
  const { pathname } = useLocation()

  type MenuItem = Required<MenuProps>['items'][number]
  const navigite = useNavigate()
  // 生成每一个菜单项
  function getItem(
    label: React.ReactNode,
    key?: React.Key | null,
    icon?: React.ReactNode,
    children?: MenuItem[]
  ): MenuItem {
    return {
      label,
      key,
      icon,
      children
    } as MenuItem
  }
  function createIcon(name?: string) {
    if (!name) return <></>
    const customerIcons: { [key: string]: any } = Icons
    const icon = customerIcons[name]
    if (!icon) return <></>
    return React.createElement(icon)
  }
  //递归生成菜单
  const getTreeMenu = (
    menuList: Menu.MenuItem[],
    treeList: MenuItem[] = []
  ) => {
    menuList.forEach((item, index) => {
      if (item.menuType === 1 && item.menuState === 1) {
        if (item.button) {
          return treeList.push(
            getItem(item.menuName, item.path || index, createIcon(item.icon))
          )
        }
        treeList.push(
          getItem(
            item.menuName,
            item.path || index,
            createIcon(item.icon),
            getTreeMenu(item.children || [])
          )
        )
      }
    })
    return treeList
  }
  useEffect(() => {
    const treeMenuList = getTreeMenu(data.menuList)
    setMenuList(treeMenuList)
    setSelectKeys([pathname])
  }, [])
  const handleClickLogo = () => {
    navigite('/welcome')
  }
  //点击跳转路由
  const handleClickMenu = ({ key }: { key: string }) => {
    setSelectKeys([key])
    navigite(key)
  }
  return (
    <div>
      <div className={styles.logo} onClick={handleClickLogo}>
        <img className={styles.img} src='/images/logo.png' alt='' />
        <span>木木货运</span>
      </div>
      <IMenu
        mode='inline' //mode 模式设置的子导航打开方式 行内行外
        theme='dark'
        items={menuList}
        selectedKeys={selectedKeys}
        onClick={handleClickMenu}
      />
    </div>
  )
}

下面是效果图。

​编辑

​编辑

​编辑

相关推荐
逾明19 分钟前
Electron自定义菜单栏及Mac最大化无效的问题解决
前端·electron
辰九九23 分钟前
Uncaught URIError: URI malformed 报错如何解决?
前端·javascript·浏览器
月亮慢慢圆23 分钟前
Echarts的基本使用(待更新)
前端
芜青36 分钟前
实现文字在块元素中水平/垂直居中详解
前端·css·css3
useCallback40 分钟前
Elpis全栈项目总结
前端
小高0071 小时前
React useMemo 深度指南:原理、误区、实战与 2025 最佳实践
前端·javascript·react.js
LuckySusu1 小时前
【js篇】深入理解类数组对象及其转换为数组的多种方法
前端·javascript
LuckySusu1 小时前
【js篇】数组遍历的方法大全:前端开发中的高效迭代
前端·javascript
LuckySusu1 小时前
【js篇】for...in与 for...of 的区别:前端开发中的迭代器选择
前端·javascript
mon_star°1 小时前
有趣的 npm 库 · json-server
前端·npm·json