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

下面是效果图。

​编辑

​编辑

​编辑

相关推荐
仰望星空的小猴子11 分钟前
React18和React19新特性
前端
小码哥_常13 分钟前
Android新航标:Navigation 3为何成为变革先锋?
前端
SuperEugene13 分钟前
Vue状态管理扫盲篇:状态管理中的常见坑 | 循环依赖、状态污染与调试技巧
前端·vue.js·面试
骑着小黑马14 分钟前
从 Electron 到 Tauri 2:我用 3.5MB 做了个音乐播放器
前端·vue.js·typescript
aykon15 分钟前
DataSource详解以及优势
前端
Mintopia15 分钟前
戴了 30 天智能手环后,我才发现自己一直低估了“睡眠”
前端
leolee1815 分钟前
react redux 简单使用
前端·react.js·redux
仰望星空的小猴子16 分钟前
常用的Hooks
前端
天才熊猫君17 分钟前
Vue Fragment 锚点机制
前端
米丘17 分钟前
Git 常用操作命令
前端