当前位置:   article > 正文

深度复盘:一文读懂前端B端权限控制

eslint-disable-next-line no-param-reassign

关注并将「趣谈前端」设为星标

每天定时分享技术干货/优秀开源/技术思维

后台管理平台内部权限大部分涉及到到两种方式:资源权限  & 数据权限

demo分支:https://github.com/rodchen-king/ant-design-pro-v2/tree/permission-branch

1. 基本介绍

  • 资源权限:菜单导航栏 & 页面 & 按钮 资源可见权限。

  • 数据权限:对于页面上的数据操作,同一个人同一个页面相同的数据可能存在不同的数据操作权限。


权限纬度

  • 角色维度:大部分的情况为:用户 => 角色 => 权限

  • 用户维度:用户 => 权限


表现形式

  • 基础表现形式还是树结构的展现形式,因为对应的菜单-页面-按钮是一个树的从主干到节点的数据流向。


2. 权限数据录入与展示

采用树结构进行处理。唯一需要处理的是父子节点的联动关系处理。这里因为不同的公司或者系统可能对于这部分的数据录入方式不同,所以就不贴图了。

3. 权限数据控制

3.1 用户资源权限流程图

3.2 前端权限控制

前端控制权限也是分为两部分,菜单页面 与 按钮。因为前端权限控制的实现,会因为后台接口形式有所影响,但是大体方向是相同。还是会分为这两块内容。这里对于权限是使用多接口查询权限,初始登录查询页面权限,点击业务页面,查询对应业务页面的资源code。


3.2.1 菜单权限

菜单权限控制需要了解两个概念:

  • 一个是可见的菜单页面 :左侧dom节点

  • 一个是可访问的菜单页面:系统当中路由这一块

这里说的意思是:我们所说的菜单权限控制,大多只是停留在菜单是否可见,但是系统路由的页面可见和页面上的菜单是否可见是两回事情。假设系统路由/path1可见,尽管页面上的没有/path1对应的菜单显示。我们直接在浏览器输入对应的path1,还是可以访问到对应的页面。这是因为系统路由那一块其实我们是没有去处理的。

了解了这个之后,我们需要做菜单页面权限的时候就需要去考虑两块,并且是对应的。


3.2.1.1 路由权限

代码地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/0e7895c56e4962d75ab8ccf4637cefca3f5f71b6#diff-a7acc04e8fb20252554c588f7b7a8564

这里是有两种做法:第一种,控制路由的配置,当然不是路由配置文件里去配置。而是生效的路由配置里去做。第二种,完全不做这里的路由控制,而是在路由跳转到没有权限的页面,写逻辑校验是否有当前的权限,然后手动跳转到403页面。

这里还是先用第一种做法来做:因为这里用第一种做了之后,菜单可见权限自动适配好了。会省去我们很多事情。


a. 路由文件,定义菜单页面权限。并且将exception以及404的路由添加notInAut标志,这个标志说明:这两个路由不走权限校验。同理的还有 /user。

  1. export default [
  2.   // user
  3.   {
  4.     path: '/user',
  5.     component: '../layouts/UserLayout',
  6.     routes: [
  7.       { path: '/user', redirect: '/user/login' },
  8.       { path: '/user/login', component: './User/Login' },
  9.       { path: '/user/register', component: './User/Register' },
  10.       { path: '/user/register-result', component: './User/RegisterResult' },
  11.     ],
  12.   },
  13.   // app
  14.   {
  15.     path: '/',
  16.     component: '../layouts/BasicLayout',
  17.     Routes: ['src/pages/Authorized'],
  18.     authority: ['admin''user'],
  19.     routes: [
  20.       // dashboard
  21.       { path: '/', redirect: '/list/table-list' },
  22.       // forms
  23.       {
  24.         path: '/form',
  25.         icon: 'form',
  26.         name: 'form',
  27.         code: 'form_menu',
  28.         routes: [
  29.           {
  30.             path: '/form/basic-form',
  31.             code: 'form_basicForm_page',
  32.             name: 'basicform',
  33.             component: './Forms/BasicForm',
  34.           },
  35.         ],
  36.       },
  37.       // list
  38.       {
  39.         path: '/list',
  40.         icon: 'table',
  41.         name: 'list',
  42.         code: 'list_menu',
  43.         routes: [
  44.           {
  45.             path: '/list/table-list',
  46.             name: 'searchtable',
  47.             code: 'list_tableList_page',
  48.             component: './List/TableList',
  49.           },
  50.         ],
  51.       },
  52.       {
  53.         path: '/profile',
  54.         name: 'profile',
  55.         icon: 'profile',
  56.         code: 'profile_menu',
  57.         routes: [
  58.           // profile
  59.           {
  60.             path: '/profile/basic',
  61.             name: 'basic',
  62.             code: 'profile_basic_page',
  63.             component: './Profile/BasicProfile',
  64.           },
  65.           {
  66.             path: '/profile/advanced',
  67.             name: 'advanced',
  68.             code: 'profile_advanced_page',
  69.             authority: ['admin'],
  70.             component: './Profile/AdvancedProfile',
  71.           },
  72.         ],
  73.       },
  74.       {
  75.         name: 'exception',
  76.         icon: 'warning',
  77.         notInAut: true,
  78.         hideInMenu: true,
  79.         path: '/exception',
  80.         routes: [
  81.           // exception
  82.           {
  83.             path: '/exception/403',
  84.             name: 'not-permission',
  85.             component: './Exception/403',
  86.           },
  87.           {
  88.             path: '/exception/404',
  89.             name: 'not-find',
  90.             component: './Exception/404',
  91.           },
  92.           {
  93.             path: '/exception/500',
  94.             name: 'server-error',
  95.             component: './Exception/500',
  96.           },
  97.           {
  98.             path: '/exception/trigger',
  99.             name: 'trigger',
  100.             hideInMenu: true,
  101.             component: './Exception/TriggerException',
  102.           },
  103.         ],
  104.       },
  105.       {
  106.         notInAut: true,
  107.         component: '404',
  108.       },
  109.     ],
  110.   },
  111. ];

b. 修改app.js 文件,加载路由

  1. export const dva = {
  2.   config: {
  3.     onError(err) {
  4.       err.preventDefault();
  5.     },
  6.   },
  7. };
  8. let authRoutes = null;
  9. function ergodicRoutes(routes, authKey, authority) {
  10.   routes.forEach(element => {
  11.     if (element.path === authKey) {
  12.       Object.assign(element.authority, authority || []);
  13.     } else if (element.routes) {
  14.       ergodicRoutes(element.routes, authKey, authority);
  15.     }
  16.     return element;
  17.   });
  18. }
  19. function customerErgodicRoutes(routes) {
  20.   const menuAutArray = (localStorage.getItem('routerAutArray') || '').split(',');
  21.   routes.forEach(element => {
  22.     // 没有path的情况下不需要走逻辑检查
  23.     // path 为 /user 不需要走逻辑检查
  24.     if (element.path === '/user' || !element.path) {
  25.       return element;
  26.     }
  27.     // notInAut 为true的情况下不需要走逻辑检查
  28.     if (!element.notInAut) {
  29.       if (menuAutArray.indexOf(element.code) >= 0 || element.path === '/') {
  30.         if (element.routes) {
  31.           // eslint-disable-next-line no-param-reassign
  32.           element.routes = customerErgodicRoutes(element.routes);
  33.           // eslint-disable-next-line no-param-reassign
  34.           element.routes = element.routes.filter(item => !item.isNeedDelete);
  35.         }
  36.       } else {
  37.         // eslint-disable-next-line no-param-reassign
  38.         element.isNeedDelete = true;
  39.       }
  40.     }
  41.     /**
  42.      * 后台接口返回子节点的情况,父节点需要溯源处理
  43.      */
  44.     // notInAut 为true的情况下不需要走逻辑检查
  45.     // if (!element.notInAut) {
  46.     //   if (element.routes) {
  47.     //     // eslint-disable-next-line no-param-reassign
  48.     //     element.routes = customerErgodicRoutes(element.routes);
  49.     //     // eslint-disable-next-line no-param-reassign
  50.     //     if (element.routes.filter(item => item.isNeedSave && !item.hideInMenu).length) {
  51.     //       // eslint-disable-next-line no-param-reassign
  52.     //       element.routes = element.routes.filter(item => item.isNeedSave);
  53.     //       if (element.routes.length) {
  54.     //         // eslint-disable-next-line no-param-reassign
  55.     //         element.isNeedSave = true;
  56.     //       }
  57.     //     }
  58.     //   } else if (menuAutArray.indexOf(element.code) >= 0) {
  59.     //     // eslint-disable-next-line no-param-reassign
  60.     //     element.isNeedSave = true;
  61.     //   }
  62.     // } else {
  63.     //   // eslint-disable-next-line no-param-reassign
  64.     //   element.isNeedSave = true;
  65.     // }
  66.     return element;
  67.   });
  68.   return routes;
  69. }
  70. export function patchRoutes(routes) {
  71.   Object.keys(authRoutes).map(authKey =>
  72.     ergodicRoutes(routes, authKey, authRoutes[authKey].authority),
  73.   );
  74.   customerErgodicRoutes(routes);
  75.   /**
  76.    * 后台接口返回子节点的情况,父节点需要溯源处理
  77.    */
  78.   window.g_routes = routes.filter(item => !item.isNeedDelete);
  79.   /**
  80.    * 后台接口返回子节点的情况,父节点需要溯源处理
  81.    */
  82.   // window.g_routes = routes.filter(item => item.isNeedSave);
  83. }
  84. export function render(oldRender) {
  85.   authRoutes = '';
  86.   oldRender();
  87. }

c. 修改login.js,获取路由当中的code便利获取到,进行查询权限

  1. import { routerRedux } from 'dva/router';
  2. import { stringify } from 'qs';
  3. import { fakeAccountLogin, getFakeCaptcha } from '@/services/api';
  4. import { getAuthorityMenu } from '@/services/authority';
  5. import { setAuthority } from '@/utils/authority';
  6. import { getPageQuery } from '@/utils/utils';
  7. import { reloadAuthorized } from '@/utils/Authorized';
  8. import routes from '../../config/router.config';
  9. export default {
  10.   namespace: 'login',
  11.   state: {
  12.     status: undefined,
  13.   },
  14.   effects: {
  15.     *login({ payload }, { call, put }) {
  16.       const response = yield call(fakeAccountLogin, payload);
  17.       yield put({
  18.         type'changeLoginStatus',
  19.         payload: response,
  20.       });
  21.       // Login successfully
  22.       if (response.status === 'ok') {
  23.         // 这里的数据通过接口返回菜单页面的权限是什么
  24.         const codeArray = [];
  25.         // eslint-disable-next-line no-inner-declarations
  26.         function ergodicRoutes(routesParam) {
  27.           routesParam.forEach(element => {
  28.             if (element.code) {
  29.               codeArray.push(element.code);
  30.             }
  31.             if (element.routes) {
  32.               ergodicRoutes(element.routes);
  33.             }
  34.           });
  35.         }
  36.         ergodicRoutes(routes);
  37.         const authMenuArray = yield call(getAuthorityMenu, codeArray.join(','));
  38.         localStorage.setItem('routerAutArray', authMenuArray.join(','));
  39.         reloadAuthorized();
  40.         const urlParams = new URL(window.location.href);
  41.         const params = getPageQuery();
  42.         let { redirect } = params;
  43.         if (redirect) {
  44.           const redirectUrlParams = new URL(redirect);
  45.           if (redirectUrlParams.origin === urlParams.origin) {
  46.             redirect = redirect.substr(urlParams.origin.length);
  47.             if (redirect.match(/^\/.*#/)) {
  48.               redirect = redirect.substr(redirect.indexOf('#') + 1);
  49.             }
  50.           } else {
  51.             window.location.href = redirect;
  52.             return;
  53.           }
  54.         }
  55.         // yield put(routerRedux.replace(redirect || '/'));
  56.         // 这里之所以用页面跳转,因为路由的重新设置需要页面重新刷新才可以生效
  57.         window.location.href = redirect || '/';
  58.       }
  59.     },
  60.     *getCaptcha({ payload }, { call }) {
  61.       yield call(getFakeCaptcha, payload);
  62.     },
  63.     *logout(_, { put }) {
  64.       yield put({
  65.         type'changeLoginStatus',
  66.         payload: {
  67.           status: false,
  68.           currentAuthority: 'guest',
  69.         },
  70.       });
  71.       reloadAuthorized();
  72.       yield put(
  73.         routerRedux.push({
  74.           pathname: '/user/login',
  75.           search: stringify({
  76.             redirect: window.location.href,
  77.           }),
  78.         }),
  79.       );
  80.     },
  81.   },
  82.   reducers: {
  83.     changeLoginStatus(state, { payload }) {
  84.       setAuthority(payload.currentAuthority);
  85.       return {
  86.         ...state,
  87.         status: payload.status,
  88.         type: payload.type,
  89.       };
  90.     },
  91.   },
  92. };

d. 添加service

  1. import request from '@/utils/request';
  2. // 查询菜单权限
  3. export async function getAuthorityMenu(codes) {
  4.   return request(`/api/authority/menu?resCodes=${codes}`);
  5. }
  6. // 查询页面按钮权限
  7. export async function getAuthority(params) {
  8.   return request(`/api/authority?codes=${params}`);
  9. }
3.2.1.2 菜单可见权限

参照上面的方式,这里的菜单可见权限不用做其他的操作。

3.2.2 按钮权限

代码地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/0e7895c56e4962d75ab8ccf4637cefca3f5f71b6#diff-a7acc04e8fb20252554c588f7b7a8564

按钮权限上就涉及到两块,资源权限数据权限。数据获取的方式不同,代码逻辑上会稍微有点不同。核心是业务组件内部的code,在加载的时候就自行累加,然后在页面加载完成的时候,发送请求。拿到数据之后,自行进行权限校验。尽量减少业务页面代码的复杂度。


资源权限逻辑介绍:

  1. PageHeaderWrapper包含的业务页面存在按钮权限

  2. 按钮权限通过AuthorizedButton包含处理,需要添加code。但是业务页面因为是单独页面发送当前页面code集合去查询权限code,然后在AuthorizedButton进行权限逻辑判断。

  3. 所以AuthorizedButtoncomponentWillMount生命周期进行当前业务页面的code累加。累加完成之后,通过PageHeaderWrappercomponentDidMount生命周期函数发送权限请求,拿到权限code,通过公有globalAuthority model读取数据进行权限逻辑判断。

  4. 对于业务页面的调用参考readme进行使用。因为对于弹出框内部的code,在业务列表页面渲染的时候,组件还未加载,所以通过extencode提前将code累加起来进行查询权限。


数据权限介绍:

  1. 涉及数据权限,则直接将对应的数据规则放进AuthorizedButton内部进行判断,需要传入的数据则直接通过props传入即可。因为数据权限的规则不同,这里就没有举例子。

  2. 需要注意的逻辑是资源权限和数据权限是串行的,先判断资源权限,然后判断数据权限。


a. 添加公用authority model

  1. /* eslint-disable no-unused-vars */
  2. /* eslint-disable no-prototype-builtins */
  3. import { getAuthority } from '@/services/authority';
  4. export default {
  5.   namespace: 'globalAuthority',
  6.   state: {
  7.     hasAuthorityCodeArray: [], // 获取当前具有权限的资源code
  8.     pageCodeArray: [], // 用来存储当前页面存在的资源code
  9.   },
  10.   effects: {
  11.     /**
  12.      * 获取当前页面的权限控制
  13.      */
  14.     *getAuthorityForPage({ payload }, { put, call, select }) {
  15.       // 这里的资源code都是自己加载的
  16.       const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
  17.       const response = yield call(getAuthority, pageCodeArray);
  18.       if (pageCodeArray.length) {
  19.         yield put({
  20.           type'save',
  21.           payload: {
  22.             hasAuthorityCodeArray: response,
  23.           },
  24.         });
  25.       }
  26.     },
  27.     *plusCode({ payload }, { put, select }) {
  28.       // 组件累加当前页面的code,用来发送请求返回对应的权限code
  29.       const { codeArray = [] } = payload;
  30.       const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
  31.       yield put({
  32.         type'save',
  33.         payload: {
  34.           pageCodeArray: pageCodeArray.concat(codeArray),
  35.         },
  36.       });
  37.     },
  38.     // eslint-disable-next-line no-unused-vars
  39.     *resetAuthorityForPage({ payload }, { put, call }) {
  40.       yield put({
  41.         type'save',
  42.         payload: {
  43.           hasAuthorityCodeArray: [],
  44.           pageCodeArray: [],
  45.         },
  46.       });
  47.     },
  48.   },
  49.   reducers: {
  50.     save(state, { payload }) {
  51.       return {
  52.         ...state,
  53.         ...payload,
  54.       };
  55.     },
  56.   },
  57. };

b. 修改PageHeaderWrapper文件【因为所有的业务页面都是这个组件的子节点】

  1. import React, { PureComponent } from 'react';
  2. import { FormattedMessage } from 'umi/locale';
  3. import Link from 'umi/link';
  4. import PageHeader from '@/components/PageHeader';
  5. import { connect } from 'dva';
  6. import MenuContext from '@/layouts/MenuContext';
  7. import { Spin } from 'antd';
  8. import GridContent from './GridContent';
  9. import styles from './index.less';
  10. class PageHeaderWrapper extends PureComponent {
  11.   componentDidMount() {
  12.     const { dispatch } = this.props;
  13.     dispatch({
  14.       type'globalAuthority/getAuthorityForPage'// 发送请求获取当前页面的权限code
  15.     });
  16.   }
  17.   componentWillUnmount() {
  18.     const { dispatch } = this.props;
  19.     dispatch({
  20.       type'globalAuthority/resetAuthorityForPage',
  21.     });
  22.   }
  23.   render() {
  24.     const { children, contentWidth, wrapperClassName, top, loading, ...restProps } = this.props;
  25.     return (
  26.       <Spin spinning={loading}>
  27.         <div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
  28.           {top}
  29.           <MenuContext.Consumer>
  30.             {value => (
  31.               <PageHeader
  32.                 wide={contentWidth === 'Fixed'}
  33.                 home={<FormattedMessage id="menu.home" defaultMessage="Home" />}
  34.                 {...value}
  35.                 key="pageheader"
  36.                 {...restProps}
  37.                 linkElement={Link}
  38.                 itemRender={item => {
  39.                   if (item.locale) {
  40.                     return <FormattedMessage id={item.locale} defaultMessage={item.title} />;
  41.                   }
  42.                   return item.title;
  43.                 }}
  44.               />
  45.             )}
  46.           </MenuContext.Consumer>
  47.           {children ? (
  48.             <div className={styles.content}>
  49.               <GridContent>{children}</GridContent>
  50.             </div>
  51.           ) : null}
  52.         </div>
  53.       </Spin>
  54.     );
  55.   }
  56. }
  57. export default connect(({ setting, globalAuthority, loading }) => ({
  58.   contentWidth: setting.contentWidth,
  59.   globalAuthority,
  60.   loading: loading.models.globalAuthority,
  61. }))(PageHeaderWrapper);

c. 添加AuthorizedButton公共组件

  1. import React, { Component } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { connect } from 'dva';
  4. @connect(({ globalAuthority }) => ({
  5.   globalAuthority,
  6. }))
  7. class AuthorizedButton extends Component {
  8.   static contextTypes = {
  9.     isMobile: PropTypes.bool,
  10.   };
  11.   componentWillMount() {
  12.     // extendcode 扩展表格中的code还没有出现的情况
  13.     const {
  14.       dispatch,
  15.       code,
  16.       extendCode = [],
  17.       globalAuthority: { pageCodeArray },
  18.     } = this.props;
  19.     let codeArray = [];
  20.     if (code) {
  21.       codeArray.push(code);
  22.     }
  23.     if (extendCode && extendCode.length) {
  24.       codeArray = codeArray.concat(extendCode);
  25.     }
  26.     // code已经存在,证明是页面数据渲染之后或者弹出框的按钮资源,不需要走dva了
  27.     if (pageCodeArray.indexOf(code) >= 0) {
  28.       return;
  29.     }
  30.     dispatch({
  31.       type'globalAuthority/plusCode',
  32.       payload: {
  33.         codeArray,
  34.       },
  35.     });
  36.   }
  37.   checkAuthority = code => {
  38.     const {
  39.       globalAuthority: { hasAuthorityCodeArray },
  40.     } = this.props;
  41.     return hasAuthorityCodeArray.indexOf(code) >= 0// 资源权限
  42.   };
  43.   render() {
  44.     const { children, code } = this.props;
  45.     return (
  46.       <span style={{ display: this.checkAuthority(code) ? 'inline' : 'none' }}>{children}</span>
  47.     );
  48.   }
  49. }
  50. 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 按钮权限扩展-链接权限控制

代码地址: https://github.com/rodchen-king/ant-design-pro-v2/commit/02914330f17f11f3d6e8b7d5c1239702c6832337

背景:页面上有需要控制跳转链接的权限,有权限则可以跳转,没有权限则不能跳转。


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

通过redux-devtool,查看到codeAuthorityObject的状态值为:key:code值,value的值为true/false。true代表,有权限,false代表无权限。主要用于开发人员自己做相关处理。


b.需要控制的按钮code,通过其他方式扩展进行code计算,发送请求获取权限


c.获取数据进行数据控制

3.2.4 按钮数据权限

代码地址:https://github.com/rodchen-king/ant-design-pro-v2/commit/463514b0964c4c0187a503d315aa9f088e963f71

背景

数据权限是对于业务组件内部表格组件的数据进行的数据操作权限。列表数据可能归属于不同的数据类型,所以具有不同的数据操作权限。对于批量操作则需要判断选择的数据是否都具有操作权限,然后显示是否可以批量操作,如果有一个没有操作权限,都不能进行操作。


总体思路

场景:比如在商品列表中,每条商品记录后面的“操作”一栏下用三个按钮:【编辑】、【上架/下架】、【删除】,而对于某一个用户,他可以查看所有的商品,但对于某些品牌他可以【上架/下架】但不能【编辑】,则前端需要控制到每一个商品后面的按钮的可用状态,比如用户A对于某一条业务数据(id=1999)有编辑权限,则这条记录上的【编辑】按钮对他来说是可见的(前提是他首先要有【编辑】这个按钮的资源权限),但对于另一条记录(id=1899)是没有【编辑】权限,则这条记录上的【编辑】按钮对他来说是不可见的。


按钮【actType】属性定义

每个数据操作的按钮上加一个属性 “actType”代表这个按钮的动作类型(如:编辑、删除、审核等),这个属性是资源权限的接口返回的,前端在调这个接口时将这个属性记录下来,或者保存到对应的控件中。所以前端可以不用关于这个属性的每个枚举值代表的是什么含义,只需根据接口的返回值赋值就好。用兴趣的同学也可以参考一下actType取值如下:1 可读,2 编辑,3 可读+可写, 4 可收货,8 可发货,16 可配货, 32 可审核,64 可完结


业务接口返回权限类型字段【permissionType】

对于有权限控制的业务数据,列表接口或者详情接口都会返回一个“permissionType”的字段,这个字段代表当前用户对于这条业务数据的权限类型,如当 permissionType=2 代表这个用户对于这条数据有【编辑权限】,permisionType=4 代表这个用户对于这条业务数据有收货的权限,permisionType=6表示这个用户对于这条记录用编辑和发货的权限(6=2+4)


怎么控制按钮的可用状态?

现在列表上有三个按钮,【编辑】、【收货】、【完结】,它们对应的“actType”分别为2、4、64,某一条数据的permissionType=3,这时这三个按钮的状态怎么判断呢,permissionType=3 我们可以分解为 1+2,表示这个用户对于这条记录有“可读”+“编辑”权限,则这三个按钮中,只有【编辑】按钮是可用的。那么判断的公式为:

((data[i].permissionType & obj.actType)==obj.actType)

前端的js数据进行&判断

需要进行数据转换    data.toString(2): 将数据进行2进制转换成二进制字符串。parseInt(permissionType,2) : 二进制字符串转换成二进制数据。


代码修改

接口mock返回数据

  1. response = [{
  2.       "type"3,
  3.       "name""创建活动-10001",
  4.       "actType"0,
  5.       "code""10001"
  6.     }, {
  7.       "type"3,
  8.       "name""编辑-10002",
  9.       "actType"2,
  10.       "code""10002"
  11.     }, {
  12.       "type"3,
  13.       "name""配置-10005",
  14.       "actType"4,
  15.       "code""10005"
  16.     }, {
  17.       "type"3,
  18.       "name""订阅警报-10006",
  19.       "actType"8,
  20.       "code""10006"
  21.     }, {
  22.       "type"3,
  23.       "name""查询详情-20001",
  24.       "actType"16,
  25.       "code""20001"
  26.     }, {
  27.       "type"3,
  28.       "name""批量操作-10007",
  29.       "actType"32,
  30.       "code""10007"
  31.     }, {
  32.       "type"3,
  33.       "name""更多操作-10008",
  34.       "actType"64,
  35.       "code""10008"
  36.     }]

每一个返回的接口权限会将对应的actType一起返回。

getAuthorityForPage代码修改简单修改一下,因为之前返回的是code数组,现在返回的是对象


  1.    /**
  2.      * 获取当前页面的权限控制
  3.      */
  4.     *getAuthorityForPage({ payload }, { put, call, select }) {
  5.       // 这里的资源code都是自己加载的
  6.       const pageCodeArray = yield select(state => state.globalAuthority.pageCodeArray);
  7.       const response = yield call(getAuthority, pageCodeArray);
  8.       const hasAuthorityCodeArray = response || [];
  9.       const codeAuthorityObject = {};
  10.       pageCodeArray.forEach((value, index, array) => {
  11.         codeAuthorityObject[value] = hasAuthorityCodeArray.map(item => item.code).indexOf(value) >= 0;
  12.       });
  13.       // debugger
  14.       yield put({
  15.         type'save',
  16.         payload: {
  17.           hasAuthorityCodeArray,
  18.           codeAuthorityObject,
  19.         },
  20.       });
  21.     },

修改AuthorizedButton代码

增加数据权限判断

  1. /* eslint-disable eqeqeq */
  2. import React, { Component } from 'react';
  3. import PropTypes from 'prop-types';
  4. import { connect } from 'dva';
  5. @connect(({ globalAuthority }) => ({
  6.   globalAuthority,
  7. }))
  8. class AuthorizedButton extends Component {
  9.   static contextTypes = {
  10.     isMobile: PropTypes.bool,
  11.   };
  12.   componentWillMount() {
  13.     // extendcode 扩展表格中的code还没有出现的情况
  14.     const {
  15.       dispatch,
  16.       code,
  17.       extendCode = [],
  18.       globalAuthority: { pageCodeArray },
  19.     } = this.props;
  20.     let codeArray = [];
  21.     if (code) {
  22.       codeArray.push(code);
  23.     }
  24.     if (extendCode && extendCode.length) {
  25.       codeArray = codeArray.concat(extendCode);
  26.     }
  27.     // code已经存在,证明是页面数据渲染之后或者弹出框的按钮资源,不需要走dva了
  28.     if (pageCodeArray.indexOf(code) >= 0) {
  29.       return;
  30.     }
  31.     dispatch({
  32.       type'globalAuthority/plusCode',
  33.       payload: {
  34.         codeArray,
  35.       },
  36.     });
  37.   }
  38.   checkAuthority = code => {
  39.     const {
  40.       globalAuthority: { hasAuthorityCodeArray },
  41.     } = this.props;
  42.     return hasAuthorityCodeArray.map(item => item.code).indexOf(code) >= 0 && this.checkDataAuthority(); // 资源权限
  43.   };
  44.   /**
  45.    * 检测数据权限
  46.    */
  47.   checkDataAuthority = () => {
  48.     const {
  49.       globalAuthority: { hasAuthorityCodeArray },
  50.       code,                                         // 当前按钮的code
  51.       actType,                                      // 当前按钮的actType的值通过传递传入
  52.       recordPermissionType,                         // 单条数据的数据操作权限总和
  53.       actTypeArray
  54.     } = this.props;
  55.     if (recordPermissionType || actTypeArray) {     // 单条数据权限校验
  56.       const tempCode = hasAuthorityCodeArray.filter(item => item.code === code)
  57.       let tempActType = ''
  58.       if (actType) {
  59.         tempActType = actType
  60.       } else if (tempCode.length) {
  61.         tempActType = tempCode[0].actType
  62.       } else {
  63.         return true;                                // 默认返回true
  64.       }
  65.       if (actTypeArray) {                           // 批量操作
  66.         return !actTypeArray.some(item => !this.checkPermissionType(item.toString(2), tempActType.toString(2)))
  67.       }
  68.       // 单条数据操作
  69.       return this.checkPermissionType(recordPermissionType.toString(2), tempActType.toString(2))
  70.     } 
  71.     return true;                                    // 如果字段没有值的情况下,证明不需要进行数据权限
  72.   }
  73.   /**
  74.    * 二进制检查当前当前数据是否具有当前权限
  75.    * @param {*} permissionType 
  76.    * @param {*} actType
  77.    */
  78.   checkPermissionType = (permissionType, actType) => 
  79.     // eslint-disable-next-line no-bitwise
  80.      (parseInt(permissionType,2) & parseInt(actType,2)).toString(2) == actType
  81.   
  82.   render() {
  83.     const { children, code } = this.props;
  84.     return (
  85.       <span style={{ display: this.checkAuthority(code) ? 'inline' : 'none' }}>{children}</span>
  86.     );
  87.   }
  88. }
  89. export default AuthorizedButton;

调用方式

单条数据操作

  1. <AuthoriedButton code="10005" recordPermissionType={record.permissionType}>
  2.   <a onClick={() => this.handleUpdateModalVisible(true, record)}>配置</a>
  3. </AuthoriedButton>

批量操作

  1.  <AuthoriedButton code="10007" actTypeArray={getNotDuplicateArrayById(selectedRows, 'permissionType')}>
  2.      <Button>批量操作</Button>
  3.  </AuthoriedButton>

好了,今天的分享就到这里了,如果文章对你有帮助,你也可以点赞 + 转发, 鼓励作者持续创作。


从零搭建全栈可视化大屏制作平台V6.Dooring

从零设计可视化大屏搭建引擎

Dooring可视化搭建平台数据源设计剖析

可视化搭建的一些思考和实践

基于Koa + React + TS从零开发全栈文档编辑器(进阶实战

创作不易,加个点赞、在看 支持一下哦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/984405
推荐阅读
相关标签
  

闽ICP备14008679号