编辑
权限设计,我们现在创建了用户列表以及角色列表,还有菜单列表,那么可以通过给用户绑定角色,然后角色拥有功能菜单的权限,权限可以被用户看到,实现权限设计功能,也就是说我们在创建用户的时候,设置一个角色,比如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>
)
}
下面是效果图。
编辑
编辑
编辑