深度復(fù)盤:一文讀懂前端B端權(quán)限控制
關(guān)注并將「趣談前端」設(shè)為星標(biāo)
每天定時(shí)分享技術(shù)干貨/優(yōu)秀開源/技術(shù)思維
后臺(tái)管理平臺(tái)內(nèi)部權(quán)限大部分涉及到到兩種方式:資源權(quán)限 & 數(shù)據(jù)權(quán)限
demo分支:https://github.com/rodchen-king/ant-design-pro-v2/tree/permission-branch
1. 基本介紹
資源權(quán)限:菜單導(dǎo)航欄 & 頁(yè)面 & 按鈕 資源可見權(quán)限。 數(shù)據(jù)權(quán)限:對(duì)于頁(yè)面上的數(shù)據(jù)操作,同一個(gè)人同一個(gè)頁(yè)面相同的數(shù)據(jù)可能存在不同的數(shù)據(jù)操作權(quán)限。
權(quán)限緯度
角色維度:大部分的情況為:用戶 => 角色 => 權(quán)限 用戶維度:用戶 => 權(quán)限
表現(xiàn)形式
基礎(chǔ)表現(xiàn)形式還是樹結(jié)構(gòu)的展現(xiàn)形式,因?yàn)閷?duì)應(yīng)的菜單-頁(yè)面-按鈕是一個(gè)樹的從主干到節(jié)點(diǎn)的數(shù)據(jù)流向。
2. 權(quán)限數(shù)據(jù)錄入與展示
采用樹結(jié)構(gòu)進(jìn)行處理。唯一需要處理的是父子節(jié)點(diǎn)的聯(lián)動(dòng)關(guān)系處理。這里因?yàn)椴煌墓净蛘呦到y(tǒng)可能對(duì)于這部分的數(shù)據(jù)錄入方式不同,所以就不貼圖了。
3. 權(quán)限數(shù)據(jù)控制
3.1 用戶資源權(quán)限流程圖

3.2 前端權(quán)限控制
前端控制權(quán)限也是分為兩部分,菜單頁(yè)面 與 按鈕。因?yàn)榍岸藱?quán)限控制的實(shí)現(xiàn),會(huì)因?yàn)楹笈_(tái)接口形式有所影響,但是大體方向是相同。還是會(huì)分為這兩塊內(nèi)容。這里對(duì)于權(quán)限是使用多接口查詢權(quán)限,初始登錄查詢頁(yè)面權(quán)限,點(diǎn)擊業(yè)務(wù)頁(yè)面,查詢對(duì)應(yīng)業(yè)務(wù)頁(yè)面的資源code。
3.2.1 菜單權(quán)限
菜單權(quán)限控制需要了解兩個(gè)概念:
一個(gè)是可見的菜單頁(yè)面 :左側(cè)dom節(jié)點(diǎn) 一個(gè)是可訪問的菜單頁(yè)面:系統(tǒng)當(dāng)中路由這一塊
這里說的意思是:我們所說的菜單權(quán)限控制,大多只是停留在菜單是否可見,但是系統(tǒng)路由的頁(yè)面可見和頁(yè)面上的菜單是否可見是兩回事情。假設(shè)系統(tǒng)路由/path1可見,盡管頁(yè)面上的沒有/path1對(duì)應(yīng)的菜單顯示。我們直接在瀏覽器輸入對(duì)應(yīng)的path1,還是可以訪問到對(duì)應(yīng)的頁(yè)面。這是因?yàn)橄到y(tǒng)路由那一塊其實(shí)我們是沒有去處理的。
了解了這個(gè)之后,我們需要做菜單頁(yè)面權(quán)限的時(shí)候就需要去考慮兩塊,并且是對(duì)應(yīng)的。
3.2.1.1 路由權(quán)限
代碼地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/0e7895c56e4962d75ab8ccf4637cefca3f5f71b6#diff-a7acc04e8fb20252554c588f7b7a8564
這里是有兩種做法:第一種,控制路由的配置,當(dāng)然不是路由配置文件里去配置。而是生效的路由配置里去做。第二種,完全不做這里的路由控制,而是在路由跳轉(zhuǎn)到?jīng)]有權(quán)限的頁(yè)面,寫邏輯校驗(yàn)是否有當(dāng)前的權(quán)限,然后手動(dòng)跳轉(zhuǎn)到403頁(yè)面。
這里還是先用第一種做法來做:因?yàn)檫@里用第一種做了之后,菜單可見權(quán)限自動(dòng)適配好了。會(huì)省去我們很多事情。
a. 路由文件,定義菜單頁(yè)面權(quán)限。并且將exception以及404的路由添加notInAut標(biāo)志,這個(gè)標(biāo)志說明:這兩個(gè)路由不走權(quán)限校驗(yàn)。同理的還有 /user。
export default [
// user
{
path: '/user',
component: '../layouts/UserLayout',
routes: [
{ path: '/user', redirect: '/user/login' },
{ path: '/user/login', component: './User/Login' },
{ path: '/user/register', component: './User/Register' },
{ path: '/user/register-result', component: './User/RegisterResult' },
],
},
// app
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
authority: ['admin', 'user'],
routes: [
// dashboard
{ path: '/', redirect: '/list/table-list' },
// forms
{
path: '/form',
icon: 'form',
name: 'form',
code: 'form_menu',
routes: [
{
path: '/form/basic-form',
code: 'form_basicForm_page',
name: 'basicform',
component: './Forms/BasicForm',
},
],
},
// list
{
path: '/list',
icon: 'table',
name: 'list',
code: 'list_menu',
routes: [
{
path: '/list/table-list',
name: 'searchtable',
code: 'list_tableList_page',
component: './List/TableList',
},
],
},
{
path: '/profile',
name: 'profile',
icon: 'profile',
code: 'profile_menu',
routes: [
// profile
{
path: '/profile/basic',
name: 'basic',
code: 'profile_basic_page',
component: './Profile/BasicProfile',
},
{
path: '/profile/advanced',
name: 'advanced',
code: 'profile_advanced_page',
authority: ['admin'],
component: './Profile/AdvancedProfile',
},
],
},
{
name: 'exception',
icon: 'warning',
notInAut: true,
hideInMenu: true,
path: '/exception',
routes: [
// exception
{
path: '/exception/403',
name: 'not-permission',
component: './Exception/403',
},
{
path: '/exception/404',
name: 'not-find',
component: './Exception/404',
},
{
path: '/exception/500',
name: 'server-error',
component: './Exception/500',
},
{
path: '/exception/trigger',
name: 'trigger',
hideInMenu: true,
component: './Exception/TriggerException',
},
],
},
{
notInAut: true,
component: '404',
},
],
},
];
b. 修改app.js 文件,加載路由
export const dva = {
config: {
onError(err) {
err.preventDefault();
},
},
};
let authRoutes = null;
function ergodicRoutes(routes, authKey, authority) {
routes.forEach(element => {
if (element.path === authKey) {
Object.assign(element.authority, authority || []);
} else if (element.routes) {
ergodicRoutes(element.routes, authKey, authority);
}
return element;
});
}
function customerErgodicRoutes(routes) {
const menuAutArray = (localStorage.getItem('routerAutArray') || '').split(',');
routes.forEach(element => {
// 沒有path的情況下不需要走邏輯檢查
// path 為 /user 不需要走邏輯檢查
if (element.path === '/user' || !element.path) {
return element;
}
// notInAut 為true的情況下不需要走邏輯檢查
if (!element.notInAut) {
if (menuAutArray.indexOf(element.code) >= 0 || element.path === '/') {
if (element.routes) {
// eslint-disable-next-line no-param-reassign
element.routes = customerErgodicRoutes(element.routes);
// eslint-disable-next-line no-param-reassign
element.routes = element.routes.filter(item => !item.isNeedDelete);
}
} else {
// eslint-disable-next-line no-param-reassign
element.isNeedDelete = true;
}
}
/**
* 后臺(tái)接口返回子節(jié)點(diǎn)的情況,父節(jié)點(diǎn)需要溯源處理
*/
// notInAut 為true的情況下不需要走邏輯檢查
// if (!element.notInAut) {
// if (element.routes) {
// // eslint-disable-next-line no-param-reassign
// element.routes = customerErgodicRoutes(element.routes);
// // eslint-disable-next-line no-param-reassign
// if (element.routes.filter(item => item.isNeedSave && !item.hideInMenu).length) {
// // eslint-disable-next-line no-param-reassign
// element.routes = element.routes.filter(item => item.isNeedSave);
// if (element.routes.length) {
// // eslint-disable-next-line no-param-reassign
// element.isNeedSave = true;
// }
// }
// } else if (menuAutArray.indexOf(element.code) >= 0) {
// // eslint-disable-next-line no-param-reassign
// element.isNeedSave = true;
// }
// } else {
// // eslint-disable-next-line no-param-reassign
// element.isNeedSave = true;
// }
return element;
});
return routes;
}
export function patchRoutes(routes) {
Object.keys(authRoutes).map(authKey =>
ergodicRoutes(routes, authKey, authRoutes[authKey].authority),
);
customerErgodicRoutes(routes);
/**
* 后臺(tái)接口返回子節(jié)點(diǎn)的情況,父節(jié)點(diǎn)需要溯源處理
*/
window.g_routes = routes.filter(item => !item.isNeedDelete);
/**
* 后臺(tái)接口返回子節(jié)點(diǎn)的情況,父節(jié)點(diǎn)需要溯源處理
*/
// window.g_routes = routes.filter(item => item.isNeedSave);
}
export function render(oldRender) {
authRoutes = '';
oldRender();
}
c. 修改login.js,獲取路由當(dāng)中的code便利獲取到,進(jìn)行查詢權(quán)限
import { routerRedux } from 'dva/router';
import { stringify } from 'qs';
import { fakeAccountLogin, getFakeCaptcha } from '@/services/api';
import { getAuthorityMenu } from '@/services/authority';
import { setAuthority } from '@/utils/authority';
import { getPageQuery } from '@/utils/utils';
import { reloadAuthorized } from '@/utils/Authorized';
import routes from '../../config/router.config';
export default {
namespace: 'login',
state: {
status: undefined,
},
effects: {
*login({ payload }, { call, put }) {
const response = yield call(fakeAccountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
});
// Login successfully
if (response.status === 'ok') {
// 這里的數(shù)據(jù)通過接口返回菜單頁(yè)面的權(quán)限是什么
const codeArray = [];
// eslint-disable-next-line no-inner-declarations
function ergodicRoutes(routesParam) {
routesParam.forEach(element => {
if (element.code) {
codeArray.push(element.code);
}
if (element.routes) {
ergodicRoutes(element.routes);
}
});
}
ergodicRoutes(routes);
const authMenuArray = yield call(getAuthorityMenu, codeArray.join(','));
localStorage.setItem('routerAutArray', authMenuArray.join(','));
reloadAuthorized();
const urlParams = new URL(window.location.href);
const params = getPageQuery();
let { redirect } = params;
if (redirect) {
const redirectUrlParams = new URL(redirect);
if (redirectUrlParams.origin === urlParams.origin) {
redirect = redirect.substr(urlParams.origin.length);
if (redirect.match(/^\/.*#/)) {
redirect = redirect.substr(redirect.indexOf('#') + 1);
}
} else {
window.location.href = redirect;
return;
}
}
// yield put(routerRedux.replace(redirect || '/'));
// 這里之所以用頁(yè)面跳轉(zhuǎn),因?yàn)槁酚傻闹匦略O(shè)置需要頁(yè)面重新刷新才可以生效
window.location.href = redirect || '/';
}
},
*getCaptcha({ payload }, { call }) {
yield call(getFakeCaptcha, payload);
},
*logout(_, { put }) {
yield put({
type: 'changeLoginStatus',
payload: {
status: false,
currentAuthority: 'guest',
},
});
reloadAuthorized();
yield put(
routerRedux.push({
pathname: '/user/login',
search: stringify({
redirect: window.location.href,
}),
}),
);
},
},
reducers: {
changeLoginStatus(state, { payload }) {
setAuthority(payload.currentAuthority);
return {
...state,
status: payload.status,
type: payload.type,
};
},
},
};
d. 添加service
import request from '@/utils/request';
// 查詢菜單權(quán)限
export async function getAuthorityMenu(codes) {
return request(`/api/authority/menu?resCodes=${codes}`);
}
// 查詢頁(yè)面按鈕權(quán)限
export async function getAuthority(params) {
return request(`/api/authority?codes=${params}`);
}
3.2.1.2 菜單可見權(quán)限
參照上面的方式,這里的菜單可見權(quán)限不用做其他的操作。
3.2.2 按鈕權(quán)限
代碼地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/0e7895c56e4962d75ab8ccf4637cefca3f5f71b6#diff-a7acc04e8fb20252554c588f7b7a8564
按鈕權(quán)限上就涉及到兩塊,資源權(quán)限和數(shù)據(jù)權(quán)限。數(shù)據(jù)獲取的方式不同,代碼邏輯上會(huì)稍微有點(diǎn)不同。核心是業(yè)務(wù)組件內(nèi)部的code,在加載的時(shí)候就自行累加,然后在頁(yè)面加載完成的時(shí)候,發(fā)送請(qǐng)求。拿到數(shù)據(jù)之后,自行進(jìn)行權(quán)限校驗(yàn)。盡量減少業(yè)務(wù)頁(yè)面代碼的復(fù)雜度。
資源權(quán)限邏輯介紹:
PageHeaderWrapper包含的業(yè)務(wù)頁(yè)面存在按鈕權(quán)限 按鈕權(quán)限通過AuthorizedButton包含處理,需要添加code。但是業(yè)務(wù)頁(yè)面因?yàn)槭菃为?dú)頁(yè)面發(fā)送當(dāng)前頁(yè)面code集合去查詢權(quán)限code,然后在AuthorizedButton進(jìn)行權(quán)限邏輯判斷。 所以AuthorizedButton的componentWillMount生命周期進(jìn)行當(dāng)前業(yè)務(wù)頁(yè)面的code累加。累加完成之后,通過PageHeaderWrapper的componentDidMount生命周期函數(shù)發(fā)送權(quán)限請(qǐng)求,拿到權(quán)限code,通過公有g(shù)lobalAuthority model讀取數(shù)據(jù)進(jìn)行權(quán)限邏輯判斷。 對(duì)于業(yè)務(wù)頁(yè)面的調(diào)用參考readme進(jìn)行使用。因?yàn)閷?duì)于彈出框內(nèi)部的code,在業(yè)務(wù)列表頁(yè)面渲染的時(shí)候,組件還未加載,所以通過extencode提前將code累加起來進(jìn)行查詢權(quán)限。
數(shù)據(jù)權(quán)限介紹:
涉及數(shù)據(jù)權(quán)限,則直接將對(duì)應(yīng)的數(shù)據(jù)規(guī)則放進(jìn)AuthorizedButton內(nèi)部進(jìn)行判斷,需要傳入的數(shù)據(jù)則直接通過props傳入即可。因?yàn)閿?shù)據(jù)權(quán)限的規(guī)則不同,這里就沒有舉例子。 需要注意的邏輯是資源權(quán)限和數(shù)據(jù)權(quán)限是串行的,先判斷資源權(quán)限,然后判斷數(shù)據(jù)權(quán)限。
a. 添加公用authority model
/* eslint-disable no-unused-vars */
/* eslint-disable no-prototype-builtins */
import { getAuthority } from '@/services/authority';
export default {
namespace: 'globalAuthority',
state: {
hasAuthorityCodeArray: [], // 獲取當(dāng)前具有權(quán)限的資源code
pageCodeArray: [], // 用來存儲(chǔ)當(dāng)前頁(yè)面存在的資源code
},
effects: {
/**
* 獲取當(dāng)前頁(yè)面的權(quán)限控制
*/
*getAuthorityForPage({ payload }, { put, call, select }) {
// 這里的資源code都是自己加載的
const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
const response = yield call(getAuthority, pageCodeArray);
if (pageCodeArray.length) {
yield put({
type: 'save',
payload: {
hasAuthorityCodeArray: response,
},
});
}
},
*plusCode({ payload }, { put, select }) {
// 組件累加當(dāng)前頁(yè)面的code,用來發(fā)送請(qǐng)求返回對(duì)應(yīng)的權(quán)限code
const { codeArray = [] } = payload;
const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
yield put({
type: 'save',
payload: {
pageCodeArray: pageCodeArray.concat(codeArray),
},
});
},
// eslint-disable-next-line no-unused-vars
*resetAuthorityForPage({ payload }, { put, call }) {
yield put({
type: 'save',
payload: {
hasAuthorityCodeArray: [],
pageCodeArray: [],
},
});
},
},
reducers: {
save(state, { payload }) {
return {
...state,
...payload,
};
},
},
};
b. 修改PageHeaderWrapper文件【因?yàn)樗械臉I(yè)務(wù)頁(yè)面都是這個(gè)組件的子節(jié)點(diǎn)】
import React, { PureComponent } from 'react';
import { FormattedMessage } from 'umi/locale';
import Link from 'umi/link';
import PageHeader from '@/components/PageHeader';
import { connect } from 'dva';
import MenuContext from '@/layouts/MenuContext';
import { Spin } from 'antd';
import GridContent from './GridContent';
import styles from './index.less';
class PageHeaderWrapper extends PureComponent {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'globalAuthority/getAuthorityForPage', // 發(fā)送請(qǐng)求獲取當(dāng)前頁(yè)面的權(quán)限code
});
}
componentWillUnmount() {
const { dispatch } = this.props;
dispatch({
type: 'globalAuthority/resetAuthorityForPage',
});
}
render() {
const { children, contentWidth, wrapperClassName, top, loading, ...restProps } = this.props;
return (
<Spin spinning={loading}>
<div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
{top}
<MenuContext.Consumer>
{value => (
<PageHeader
wide={contentWidth === 'Fixed'}
home={<FormattedMessage id="menu.home" defaultMessage="Home" />}
{...value}
key="pageheader"
{...restProps}
linkElement={Link}
itemRender={item => {
if (item.locale) {
return <FormattedMessage id={item.locale} defaultMessage={item.title} />;
}
return item.title;
}}
/>
)}
</MenuContext.Consumer>
{children ? (
<div className={styles.content}>
<GridContent>{children}</GridContent>
</div>
) : null}
</div>
</Spin>
);
}
}
export default connect(({ setting, globalAuthority, loading }) => ({
contentWidth: setting.contentWidth,
globalAuthority,
loading: loading.models.globalAuthority,
}))(PageHeaderWrapper);
c. 添加AuthorizedButton公共組件
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'dva';
@connect(({ globalAuthority }) => ({
globalAuthority,
}))
class AuthorizedButton extends Component {
static contextTypes = {
isMobile: PropTypes.bool,
};
componentWillMount() {
// extendcode 擴(kuò)展表格中的code還沒有出現(xiàn)的情況
const {
dispatch,
code,
extendCode = [],
globalAuthority: { pageCodeArray },
} = this.props;
let codeArray = [];
if (code) {
codeArray.push(code);
}
if (extendCode && extendCode.length) {
codeArray = codeArray.concat(extendCode);
}
// code已經(jīng)存在,證明是頁(yè)面數(shù)據(jù)渲染之后或者彈出框的按鈕資源,不需要走dva了
if (pageCodeArray.indexOf(code) >= 0) {
return;
}
dispatch({
type: 'globalAuthority/plusCode',
payload: {
codeArray,
},
});
}
checkAuthority = code => {
const {
globalAuthority: { hasAuthorityCodeArray },
} = this.props;
return hasAuthorityCodeArray.indexOf(code) >= 0; // 資源權(quán)限
};
render() {
const { children, code } = this.props;
return (
<span style={{ display: this.checkAuthority(code) ? 'inline' : 'none' }}>{children}</span>
);
}
}
export default AuthorizedButton;
d. 添加AuthorizedButton readme文件
https://github.com/rodchen-king/ant-design-pro-v2/blob/permission-branch/src/components/AuthorizedButton/index.md
3.2.3 按鈕權(quán)限擴(kuò)展-鏈接權(quán)限控制
代碼地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/02914330f17f11f3d6e8b7d5c1239702c6832337
背景:頁(yè)面上有需要控制跳轉(zhuǎn)鏈接的權(quán)限,有權(quán)限則可以跳轉(zhuǎn),沒有權(quán)限則不能跳轉(zhuǎn)。

a.公共model添加新的state:codeAuthorityObject

通過redux-devtool,查看到codeAuthorityObject的狀態(tài)值為:key:code值,value的值為true/false。true代表,有權(quán)限,false代表無權(quán)限。主要用于開發(fā)人員自己做相關(guān)處理。

b.需要控制的按鈕code,通過其他方式擴(kuò)展進(jìn)行code計(jì)算,發(fā)送請(qǐng)求獲取權(quán)限

c.獲取數(shù)據(jù)進(jìn)行數(shù)據(jù)控制

3.2.4 按鈕數(shù)據(jù)權(quán)限
代碼地址:https://github.com/rodchen-king/ant-design-pro-v2/commit/463514b0964c4c0187a503d315aa9f088e963f71
背景
數(shù)據(jù)權(quán)限是對(duì)于業(yè)務(wù)組件內(nèi)部表格組件的數(shù)據(jù)進(jìn)行的數(shù)據(jù)操作權(quán)限。列表數(shù)據(jù)可能歸屬于不同的數(shù)據(jù)類型,所以具有不同的數(shù)據(jù)操作權(quán)限。對(duì)于批量操作則需要判斷選擇的數(shù)據(jù)是否都具有操作權(quán)限,然后顯示是否可以批量操作,如果有一個(gè)沒有操作權(quán)限,都不能進(jìn)行操作。

總體思路
場(chǎng)景:比如在商品列表中,每條商品記錄后面的“操作”一欄下用三個(gè)按鈕:【編輯】、【上架/下架】、【刪除】,而對(duì)于某一個(gè)用戶,他可以查看所有的商品,但對(duì)于某些品牌他可以【上架/下架】但不能【編輯】,則前端需要控制到每一個(gè)商品后面的按鈕的可用狀態(tài),比如用戶A對(duì)于某一條業(yè)務(wù)數(shù)據(jù)(id=1999)有編輯權(quán)限,則這條記錄上的【編輯】按鈕對(duì)他來說是可見的(前提是他首先要有【編輯】這個(gè)按鈕的資源權(quán)限),但對(duì)于另一條記錄(id=1899)是沒有【編輯】權(quán)限,則這條記錄上的【編輯】按鈕對(duì)他來說是不可見的。
按鈕【actType】屬性定義
每個(gè)數(shù)據(jù)操作的按鈕上加一個(gè)屬性 “actType”代表這個(gè)按鈕的動(dòng)作類型(如:編輯、刪除、審核等),這個(gè)屬性是資源權(quán)限的接口返回的,前端在調(diào)這個(gè)接口時(shí)將這個(gè)屬性記錄下來,或者保存到對(duì)應(yīng)的控件中。所以前端可以不用關(guān)于這個(gè)屬性的每個(gè)枚舉值代表的是什么含義,只需根據(jù)接口的返回值賦值就好。用興趣的同學(xué)也可以參考一下actType取值如下:1 可讀,2 編輯,3 可讀+可寫, 4 可收貨,8 可發(fā)貨,16 可配貨, 32 可審核,64 可完結(jié)
業(yè)務(wù)接口返回權(quán)限類型字段【permissionType】
對(duì)于有權(quán)限控制的業(yè)務(wù)數(shù)據(jù),列表接口或者詳情接口都會(huì)返回一個(gè)“permissionType”的字段,這個(gè)字段代表當(dāng)前用戶對(duì)于這條業(yè)務(wù)數(shù)據(jù)的權(quán)限類型,如當(dāng) permissionType=2 代表這個(gè)用戶對(duì)于這條數(shù)據(jù)有【編輯權(quán)限】,permisionType=4 代表這個(gè)用戶對(duì)于這條業(yè)務(wù)數(shù)據(jù)有收貨的權(quán)限,permisionType=6表示這個(gè)用戶對(duì)于這條記錄用編輯和發(fā)貨的權(quán)限(6=2+4)
怎么控制按鈕的可用狀態(tài)?
現(xiàn)在列表上有三個(gè)按鈕,【編輯】、【收貨】、【完結(jié)】,它們對(duì)應(yīng)的“actType”分別為2、4、64,某一條數(shù)據(jù)的permissionType=3,這時(shí)這三個(gè)按鈕的狀態(tài)怎么判斷呢,permissionType=3 我們可以分解為 1+2,表示這個(gè)用戶對(duì)于這條記錄有“可讀”+“編輯”權(quán)限,則這三個(gè)按鈕中,只有【編輯】按鈕是可用的。那么判斷的公式為:
((data[i].permissionType & obj.actType)==obj.actType)
前端的js數(shù)據(jù)進(jìn)行&判斷
需要進(jìn)行數(shù)據(jù)轉(zhuǎn)換 data.toString(2): 將數(shù)據(jù)進(jìn)行2進(jìn)制轉(zhuǎn)換成二進(jìn)制字符串。parseInt(permissionType,2) : 二進(jìn)制字符串轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)。
代碼修改
接口mock返回?cái)?shù)據(jù)
response = [{
"type": 3,
"name": "創(chuàng)建活動(dòng)-10001",
"actType": 0,
"code": "10001"
}, {
"type": 3,
"name": "編輯-10002",
"actType": 2,
"code": "10002"
}, {
"type": 3,
"name": "配置-10005",
"actType": 4,
"code": "10005"
}, {
"type": 3,
"name": "訂閱警報(bào)-10006",
"actType": 8,
"code": "10006"
}, {
"type": 3,
"name": "查詢?cè)斍?20001",
"actType": 16,
"code": "20001"
}, {
"type": 3,
"name": "批量操作-10007",
"actType": 32,
"code": "10007"
}, {
"type": 3,
"name": "更多操作-10008",
"actType": 64,
"code": "10008"
}]
每一個(gè)返回的接口權(quán)限會(huì)將對(duì)應(yīng)的actType一起返回。
getAuthorityForPage代碼修改簡(jiǎn)單修改一下,因?yàn)橹胺祷氐氖莄ode數(shù)組,現(xiàn)在返回的是對(duì)象
/**
* 獲取當(dāng)前頁(yè)面的權(quán)限控制
*/
*getAuthorityForPage({ payload }, { put, call, select }) {
// 這里的資源code都是自己加載的
const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
const response = yield call(getAuthority, pageCodeArray);
const hasAuthorityCodeArray = response || [];
const codeAuthorityObject = {};
pageCodeArray.forEach((value, index, array) => {
codeAuthorityObject[value] = hasAuthorityCodeArray.map(item => item.code).indexOf(value) >= 0;
});
// debugger
yield put({
type: 'save',
payload: {
hasAuthorityCodeArray,
codeAuthorityObject,
},
});
},

修改AuthorizedButton代碼
增加數(shù)據(jù)權(quán)限判斷
/* eslint-disable eqeqeq */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'dva';
@connect(({ globalAuthority }) => ({
globalAuthority,
}))
class AuthorizedButton extends Component {
static contextTypes = {
isMobile: PropTypes.bool,
};
componentWillMount() {
// extendcode 擴(kuò)展表格中的code還沒有出現(xiàn)的情況
const {
dispatch,
code,
extendCode = [],
globalAuthority: { pageCodeArray },
} = this.props;
let codeArray = [];
if (code) {
codeArray.push(code);
}
if (extendCode && extendCode.length) {
codeArray = codeArray.concat(extendCode);
}
// code已經(jīng)存在,證明是頁(yè)面數(shù)據(jù)渲染之后或者彈出框的按鈕資源,不需要走dva了
if (pageCodeArray.indexOf(code) >= 0) {
return;
}
dispatch({
type: 'globalAuthority/plusCode',
payload: {
codeArray,
},
});
}
checkAuthority = code => {
const {
globalAuthority: { hasAuthorityCodeArray },
} = this.props;
return hasAuthorityCodeArray.map(item => item.code).indexOf(code) >= 0 && this.checkDataAuthority(); // 資源權(quán)限
};
/**
* 檢測(cè)數(shù)據(jù)權(quán)限
*/
checkDataAuthority = () => {
const {
globalAuthority: { hasAuthorityCodeArray },
code, // 當(dāng)前按鈕的code
actType, // 當(dāng)前按鈕的actType的值通過傳遞傳入
recordPermissionType, // 單條數(shù)據(jù)的數(shù)據(jù)操作權(quán)限總和
actTypeArray
} = this.props;
if (recordPermissionType || actTypeArray) { // 單條數(shù)據(jù)權(quán)限校驗(yàn)
const tempCode = hasAuthorityCodeArray.filter(item => item.code === code)
let tempActType = ''
if (actType) {
tempActType = actType
} else if (tempCode.length) {
tempActType = tempCode[0].actType
} else {
return true; // 默認(rèn)返回true
}
if (actTypeArray) { // 批量操作
return !actTypeArray.some(item => !this.checkPermissionType(item.toString(2), tempActType.toString(2)))
}
// 單條數(shù)據(jù)操作
return this.checkPermissionType(recordPermissionType.toString(2), tempActType.toString(2))
}
return true; // 如果字段沒有值的情況下,證明不需要進(jìn)行數(shù)據(jù)權(quán)限
}
/**
* 二進(jìn)制檢查當(dāng)前當(dāng)前數(shù)據(jù)是否具有當(dāng)前權(quán)限
* @param {*} permissionType
* @param {*} actType
*/
checkPermissionType = (permissionType, actType) =>
// eslint-disable-next-line no-bitwise
(parseInt(permissionType,2) & parseInt(actType,2)).toString(2) == actType
render() {
const { children, code } = this.props;
return (
<span style={{ display: this.checkAuthority(code) ? 'inline' : 'none' }}>{children}</span>
);
}
}
export default AuthorizedButton;
調(diào)用方式
單條數(shù)據(jù)操作
<AuthoriedButton code="10005" recordPermissionType={record.permissionType}>
<a onClick={() => this.handleUpdateModalVisible(true, record)}>配置</a>
</AuthoriedButton>
批量操作
<AuthoriedButton code="10007" actTypeArray={getNotDuplicateArrayById(selectedRows, 'permissionType')}>
<Button>批量操作</Button>
</AuthoriedButton>
從零搭建全??梢暬笃林谱髌脚_(tái)V6.Dooring
創(chuàng)作不易,加個(gè)點(diǎn)贊、在看 支持一下哦!
