<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端 B 端權(quán)限控制

          共 44942字,需瀏覽 90分鐘

           ·

          2021-07-21 15:38


          點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)

          回復(fù)算法,加入前端編程面試算法每日一題群


          后臺(tái)管理平臺(tái)內(nèi)部權(quán)限大部分涉及到到兩種方式:資源權(quán)限 & 數(shù)據(jù)權(quán)限

          說(shuō)明:下面的代碼是react + ant design pro的例子。

          1. 基本介紹

          • 資源權(quán)限:菜單導(dǎo)航欄 & 頁(yè)面 & 按鈕 資源可見(jiàn)權(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)形式還是樹(shù)結(jié)構(gòu)的展現(xiàn)形式,因?yàn)閷?duì)應(yīng)的菜單-頁(yè)面-按鈕是一個(gè)樹(shù)的從主干到節(jié)點(diǎn)的數(shù)據(jù)流向。

          2. 權(quán)限數(shù)據(jù)錄入與展示

          采用樹(shù)結(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)限流程圖

          image.png

          4 前端權(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。

          4.1 菜單權(quán)限

          菜單權(quán)限控制需要了解兩個(gè)概念:

          • 一個(gè)是可見(jiàn)的菜單頁(yè)面 :左側(cè)dom節(jié)點(diǎn)
          • 一個(gè)是可訪問(wèn)的菜單頁(yè)面 :系統(tǒng)當(dāng)中路由這一塊

          這里說(shuō)的意思是:我們所說(shuō)的菜單權(quán)限控制,大多只是停留在菜單是否可見(jiàn),但是系統(tǒng)路由的頁(yè)面可見(jiàn)和頁(yè)面上的菜單是否可見(jiàn)是兩回事情。假設(shè)系統(tǒng)路由/path1可見(jiàn),盡管頁(yè)面上的沒(méi)有/path1對(duì)應(yīng)的菜單顯示。我們直接在瀏覽器輸入對(duì)應(yīng)的path1,還是可以訪問(wèn)到對(duì)應(yīng)的頁(yè)面。這是因?yàn)橄到y(tǒng)路由那一塊其實(shí)我們是沒(méi)有去處理的。

          了解了這個(gè)之后,我們需要做菜單頁(yè)面權(quán)限的時(shí)候就需要去考慮兩塊,并且是對(duì)應(yīng)的。

          4.1.1 路由權(quán)限 【代碼地址[2] demo地址[3]

          這里是有兩種做法:

          • 第一種,控制路由的配置,當(dāng)然不是路由配置文件里去配置。而是生效的路由配置里去做。
          • 第二種,完全不做這里的路由控制,而是在路由跳轉(zhuǎn)到?jīng)]有權(quán)限的頁(yè)面,寫(xiě)邏輯校驗(yàn)是否有當(dāng)前的權(quán)限,然后手動(dòng)跳轉(zhuǎn)到403頁(yè)面。

          這里還是先用第一種做法來(lái)做:因?yàn)檫@里用第一種做了之后,菜單可見(jiàn)權(quán)限自動(dòng)適配好了。會(huì)省去我們很多事情。

          a. 路由文件,定義菜單頁(yè)面權(quán)限。并且將exception以及404的路由添加notInAut標(biāo)志,這個(gè)標(biāo)志說(shuō)明:這兩個(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',
                  notInAuttrue,
                  hideInMenutrue,
                  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',
                      hideInMenutrue,
                      component'./Exception/TriggerException',
                    },
                  ],
                },
                {
                  notInAuttrue,
                  component'404',
                },
              ],
            },
          ];

          復(fù)制代碼

          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 => {
              // 沒(méi)有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) {
                    element.routes = customerErgodicRoutes(element.routes);

                    element.routes = element.routes.filter(item => !item.isNeedDelete);
                  }
                } else {
                  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();
          }

          復(fù)制代碼

          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: {
              statusundefined,
            },

            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ù)通過(guò)接口返回菜單頁(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: {
                    statusfalse,
                    currentAuthority'guest',
                  },
                });
                reloadAuthorized();
                yield put(
                  routerRedux.push({
                    pathname'/user/login',
                    search: stringify({
                      redirectwindow.location.href,
                    }),
                  }),
                );
              },
            },

            reducers: {
              changeLoginStatus(state, { payload }) {
                setAuthority(payload.currentAuthority);
                return {
                  ...state,
                  status: payload.status,
                  type: payload.type,
                };
              },
            },
          };

          復(fù)制代碼

          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}`);
          }

          復(fù)制代碼

          4.1.2 路由權(quán)限 菜單可見(jiàn)權(quán)限

          參照上面的方式,這里的菜單可見(jiàn)權(quán)限不用做其他的操作。

          4.2 按鈕權(quán)限 【代碼地址[4] demo地址[5]

          按鈕權(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)限邏輯介紹:

          1. PageHeaderWrapper包含的業(yè)務(wù)頁(yè)面存在按鈕權(quán)限
          2. 按鈕權(quán)限通過(guò)AuthorizedButton包含處理,需要添加code。但是業(yè)務(wù)頁(yè)面因?yàn)槭菃为?dú)頁(yè)面發(fā)送當(dāng)前頁(yè)面code集合去查詢權(quán)限code,然后在AuthorizedButton進(jìn)行權(quán)限邏輯判斷。
          3. 所以AuthorizedButtoncomponentWillMount生命周期進(jìn)行當(dāng)前業(yè)務(wù)頁(yè)面的code累加。累加完成之后,通過(guò)PageHeaderWrappercomponentDidMount生命周期函數(shù)發(fā)送權(quán)限請(qǐng)求,拿到權(quán)限code,通過(guò)公有g(shù)lobalAuthority model讀取數(shù)據(jù)進(jìn)行權(quán)限邏輯判斷。
          4. 對(duì)于業(yè)務(wù)頁(yè)面的調(diào)用參考readme進(jìn)行使用。因?yàn)閷?duì)于彈出框內(nèi)部的code,在業(yè)務(wù)列表頁(yè)面渲染的時(shí)候,組件還未加載,所以通過(guò)extencode提前將code累加起來(lái)進(jìn)行查詢權(quán)限。

          數(shù)據(jù)權(quán)限介紹:

          1. 涉及數(shù)據(jù)權(quán)限,則直接將對(duì)應(yīng)的數(shù)據(jù)規(guī)則放進(jìn)AuthorizedButton內(nèi)部進(jìn)行判斷,需要傳入的數(shù)據(jù)則直接通過(guò)props傳入即可。因?yàn)閿?shù)據(jù)權(quán)限的規(guī)則不同,這里就沒(méi)有舉例子。
          2. 需要注意的邏輯是資源權(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: [], // 用來(lái)存儲(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,用來(lái)發(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,
                };
              },
            },
          };

          復(fù)制代碼

          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);

          復(fù)制代碼

          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還沒(méi)有出現(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;

          復(fù)制代碼

          d. 添加AuthorizedButton readme文件
          github.com/rodchen-kin…[6]

          4.3 按鈕權(quán)限擴(kuò)展-鏈接權(quán)限控制 【代碼地址[7] demo地址[8]

          背景:頁(yè)面上有需要控制跳轉(zhuǎn)鏈接的權(quán)限,有權(quán)限則可以跳轉(zhuǎn),沒(méi)有權(quán)限則不能跳轉(zhuǎn)。

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

          image.png

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

          image.png

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

          image.png

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

          image.png

          4.4 按鈕數(shù)據(jù)權(quán)限

          • demo分支:github.com/rodchen-kin…[9]
          • demo代碼:github.com/rodchen-kin…[10]

          背景

          數(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è)沒(méi)有操作權(quán)限,都不能進(jìn)行操作。

          image.png

          總體思路

          場(chǎng)景:
          比如在商品列表中,每條商品記錄后面的“操作”一欄下用三個(gè)按鈕:【編輯】、【上架/下架】、【刪除】,而對(duì)于某一個(gè)用戶,他可以查看所有的商品,但對(duì)于某些品牌他可以【上架/下架】但不能【編輯】,則前端需要控制到每一個(gè)商品后面的按鈕的可用狀態(tài)。

          比如用戶A對(duì)于某一條業(yè)務(wù)數(shù)據(jù)(id=1999)有編輯權(quán)限,則這條記錄上的【編輯】按鈕對(duì)他來(lái)說(shuō)是可見(jiàn)的(前提是他首先要有【編輯】這個(gè)按鈕的資源權(quán)限),但對(duì)于另一條記錄(id=1899)是沒(méi)有【編輯】權(quán)限,則這條記錄上的【編輯】按鈕對(duì)他來(lái)說(shuō)是不可見(jiàn)的。

          按鈕【actType】屬性定義

          每個(gè)數(shù)據(jù)操作的按鈕上加一個(gè)屬性 “actType”代表這個(gè)按鈕的動(dòng)作類型(如:編輯、刪除、審核等),這個(gè)屬性是資權(quán)限的接口返回的,前端在調(diào)這個(gè)接口時(shí)將這個(gè)屬性記錄下來(lái),或者保存到對(duì)應(yīng)的控件中。所以前端可以不用關(guān)于這個(gè)屬性的每個(gè)枚舉值代表的是什么含義,只需根據(jù)接口的返回值賦值就好。用興趣的同學(xué)也可以參考一下actType取值如下:1 可讀,2 編輯,3 可讀+可寫(xiě), 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)
          復(fù)制代碼

          前端的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"
              }]
          復(fù)制代碼

          每一個(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,
                  },
                });
              },
          復(fù)制代碼
          image.png

          修改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還沒(méi)有出現(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的值通過(guò)傳遞傳入
                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;                                    // 如果字段沒(méi)有值的情況下,證明不需要進(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) => 
               (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;

          復(fù)制代碼

          調(diào)用方式

          單條數(shù)據(jù)操作

          <AuthoriedButton code="10005" recordPermissionType={record.permissionType}>
            <a onClick={() => this.handleUpdateModalVisible(true, record)}>配置</a>
          </AuthoriedButton>
          復(fù)制代碼

          批量操作

           <AuthoriedButton code="10007" actTypeArray={getNotDuplicateArrayById(selectedRows, 'permissionType')}>
               <Button>批量操作</Button>
           </AuthoriedButton>
          復(fù)制代碼

          關(guān)于本文

          來(lái)源:耳東蝸牛

          https://juejin.cn/post/6969799489519353863


          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會(huì)很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對(duì)你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜芽av最新网址 蜜芽欧洲无码精品 | 毛片免费网站 | 大香蕉伊人精品 | 午夜黄色| 九九精品在线观看 |