作者:aSuncat
原文:https://www.yuque.com/gm9oz0/htn2dd/rkv8p1
前提
从服务器请求菜单:https://blog.csdn.net/aSuncat/article/details/109557489
需求
权限控制分3个方面
- 点击左边菜单:如果没有权限则右边内容显示403页面
- 浏览器输入:如果输入的是没有权限的url,则右边内容显示403页面
- 内容中按钮的权限:如果右边内容中的按钮没有权限,则该按钮不显示
权限
左边菜单点击
src/layouts/BasicLayout.tsx
menuDataRender={() => menuData}
改成menuDataRender={() => menuDataRender(menuData)}
右边内容部分权限(如按钮权限)
服务器返回的菜单数据有authority
如服务器返回的数据如下
const data = [
{
path: '/',
redirect: '/welcome',
},
{
path: '/welcome',
name: '哈哈哈',
authority: ['user'],
},
{
path: '/admin',
name: '管理员',
authority: ['admin', 'user'],
children: [
{
path: '/admin/sub-page',
name: '二级管理员',
authority: ['admin'],
children: [
{
name: '前台',
authKey: 'item-category-front',
}
]
},
],
},
]
则页面改动如下:
src/layouts/BasicLayout.tsx
// get children authority
const authorized = useMemo(
() => {
if (!menuDataRef.current[0]) {
const currentAuthority = menuData[0] && formatMenuInfo(menuData, 'menu', location.pathname || '', 'authority')
return {
authority: currentAuthority
} || {
authority: undefined
}
}
return getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
authority: undefined,
}
},
[location.pathname, loading],
);
新增utils/menuData.ts
import {
MenuDataItem,
} from '@ant-design/pro-layout';
interface flattenMenuDataType {
path: string;
name: string;
authority: string;
}
interface flattenBtnData {
key: string,
name: string,
}
const updateFlatten = (list:MenuDataItem[], flattenMenuData:flattenMenuDataType[], flattenBtnData:flattenBtnData[]) => {
list.forEach(l => {
if (l.path) {
const obj:flattenMenuDataType = {
path: l.path,
name: l.name,
authority: l.authority
}
flattenMenuData.push(obj)
}
if (l.key) {
const keyObj:flattenBtnData = {
key: l.key,
name: l.name,
}
flattenBtnData.push(keyObj)
}
if (l.children) {
updateFlatten(l.children, flattenMenuData, flattenBtnData)
}
})
}
export const flattenMenu = (menuData:MenuDataItem[]) => {
let flattenMenuData:flattenMenuDataType[] = []
let flattenBtnData:flattenBtnData[] = []
updateFlatten(menuData, flattenMenuData, flattenBtnData)
return { flattenMenuData, flattenBtnData }
}
/**
* @param menuData 服务器返回的菜单信息
* @param type 处理类型:type:menu,菜单,type:content, 按钮
* @param equalType 比较的对照值,如path为/user,则传入/user
* @param key 最终返回的属性值,如path一样,返回authority
*/
export const formatMenuInfo = (menuData:MenuDataItem[], type:string, equalType:string, key:string) => {
const { flattenMenuData, flattenBtnData } = flattenMenu(menuData)
let retVal
if (type==='menu') {
flattenMenuData.forEach(l => {
if(l.path === equalType) {
retVal = l[key]
}
})
} else {
flattenBtnData.forEach(l => {
if(l.key === equalType) {
retVal = l[key]
}
})
}
return retVal
}
服务器返回的数据没有authority(推荐,我在项目中用的是这个)
如服务器返回数据如下
const data = [
{
path: '/',
redirect: '/welcome',
},
{
path: '/welcome',
name: '欢迎页',
children: [
{
name: '查看',
authKey: 'key-btn1',
},
{
name: '添加',
authKey: 'key-btn-add',
},
],
},
{
path: '/admin',
name: '管理员',
type: 'menu',
children: [
{
path: '/admin/sub-page',
name: '二级管理员',
children: [
{
name: '前台',
authKey: 'item-category-front',
}
]
},
],
},
]
则页面改动如下:
src/pages/layouts/BasicLayout.tsx
// get children authority
const authorized = useMemo(
() => {
if (!menuDataRef.current[0]) {
const currentAuthority = menuData[0] && pathAuthority(menuData, 'menu', location.pathname || '', 'path')
return {
authority: currentAuthority
} || {
authority: undefined
}
}
return getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
authority: undefined,
}
},
[location.pathname, loading],
);
src/utils/menuData.ts
import {
MenuDataItem,
} from '@ant-design/pro-layout';
interface flattenMenuDataType {
path: string;
name: string;
authority: string;
}
interface flattenBtnData {
key: string,
name: string,
}
const updateFlatten = (list:MenuDataItem[], flattenMenuData:flattenMenuDataType[], flattenBtnData:flattenBtnData[]) => {
list.forEach(l => {
if (l.path) {
const obj:flattenMenuDataType = {
path: l.path,
name: l.name,
authority: l.authority
}
flattenMenuData.push(obj)
}
if (l.key) {
const keyObj:flattenBtnData = {
key: l.key,
name: l.name,
}
flattenBtnData.push(keyObj)
}
if (l.children) {
updateFlatten(l.children, flattenMenuData, flattenBtnData)
}
})
}
export const flattenMenu = (menuData:MenuDataItem[]) => {
let flattenMenuData:flattenMenuDataType[] = []
let flattenBtnData:flattenBtnData[] = []
updateFlatten(menuData, flattenMenuData, flattenBtnData)
return { flattenMenuData, flattenBtnData }
}
/**
* @param menuData 服务器返回的菜单信息
* @param type 处理类型:type:menu,菜单,type:content, 按钮
* @param equalType 比较的对照值,如path为/user,则传入/user
* @param key 最终返回的属性值,如path一样,返回authority
*/
export const formatMenuInfo = (menuData:MenuDataItem[], type:string, equalType:string, key:string) => {
const { flattenMenuData, flattenBtnData } = flattenMenu(menuData)
let retVal
if (type==='menu') {
flattenMenuData.forEach(l => {
if(l.path === equalType) {
retVal = l[key]
}
})
} else {
flattenBtnData.forEach(l => {
if(l.key === equalType) {
retVal = l[key]
}
})
}
return retVal
}
export const formatArrToUniq = (menuData:MenuDataItem[], type:string, key:string) => {
const { flattenMenuData, flattenBtnData } = flattenMenu(menuData)
let arr:string[] = []
const data:any[] = type === 'menu' ? flattenMenuData : flattenBtnData
data.forEach(l => {
if (l[key]) {
arr.push(l[key])
}
})
return arr
}
export const pathAuthority = (menuData:MenuDataItem[], type:string, compareKey:string, key:string) => {
let retVal
const arr = formatArrToUniq(menuData, type, key)
if (arr.indexOf(compareKey) > -1) {
retVal = undefined
} else {
retVal = 'other-uniq-authority'
}
return retVal
}
按钮权限
按钮权限utils
新增src/utils/content-auth/index.ts
export { AuthProvider, AuthConsumer } from './auth'
// export { AuthProvider, AuthConsumer, AuthWrapper } from './auth'
export hasAuth from './utils/validate'
export flattenAuth from './utils/flatten'
新增src/utils/content-auth/auth.ts
import React, { Context, createContext, useContext } from 'react'
import flattenAuth from './utils/flatten'
import hasAuth from './utils/validate'
interface propsType {
name: string;
children: React.ReactNode
}
const AuthContext:Context<any> = createContext({
allAuth: [],
})
export const AuthConsumer = (props:propsType) => {
const context = useContext(AuthContext)
// const allAuth = flattenAuth([{context.allAuth}]).operations
// const hasAuthResult = hasAuth(context.allAuth, props.name)
// const hasAuthResult = hasAuth(allAuth, props.name)
const { allAuth } = context
const all = flattenAuth([{resources: allAuth}]).operations
const hasAuthResult = hasAuth(all, props.name)
return hasAuthResult ? props.children : null
}
export const AuthProvider = AuthContext.Provider
新增src/utils/content-auth/utils/vlidate.ts
export default function validate (list:string[] = [], name:string) {
return list.indexOf(name) > -1
}
新增src/utils/content-auth/utils/flatten.ts
// const apiReg = /^\/api/
interface listType {
authKey?: string,
name: string,
type: string,
children: listType[]
}
function separate (list:listType[], operations:string[]) {
list.forEach(l => {
l.authKey && operations.push(l.authKey)
if (l.children) {
separate(l.children, operations)
}
})
}
export default function flatten(list:listType[]) {
let operations:string[] = []
list.forEach(l => {
let { resources }: {resources: listType[]} = l
separate(resources, operations)
})
return { operations }
}
页面中按钮权限写法
import { AuthConsumer } from '@/utils/content-auth';
<AuthConsumer name="key-btn1">
<button>这是个小按钮,其实我后面还藏了一个小按钮(因为它的权限不够)</button>
</AuthConsumer>
<AuthConsumer name="key-btn2">
<button>这是个小按钮2</button>
</AuthConsumer>