从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>
  )
}

下面是效果图。

​编辑

​编辑

​编辑

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax