Vue权限设计开发

373 阅读4分钟

一、理解权限控制

  1. 前端做权限控制,主要是为了满足不同租户使用系统,能看到的系统页面和操作功能有所区别,特定的用户只能访问权限范围内的资源,从而实现系统的权限制衡,或者根据用户的付费等级,进行功能的使用限制。
  2. 权限控制可以大体分为两种,【操作/接口】级别、【页面/路由】级别。
    • 【操作/接口】级别:即页面上的【新增】【编辑】【删除】【查询】等按钮的操作,页面上的操作,同时对应接口的增删改查操作,可以录入同一份数据,同时对应接口/操作。
    • 【页面/路由】级别:系统的页面对应前端层面的路由控制,所以有人会把它称作【页面权限】,也有人称它为【路由权限】。

二、实现权限控制

  1. 实现【操作/接口】权限
    • 在操作权限模块,录入系统所有的接口信息,主要有接口的【名称】和【权限标识】。
    • 在配置角色的时候,勾选当前角色拥有的接口访问/按钮操作权限,用户关联角色,这样就可以获取到每个用户的接口权限,如图所示。 image.png
    • 用户登陆成功后(注意:有部分接口是不需要权限控制的,像登陆、验证码、用户信息等必须访问的接口,以及一些业务上不必要添加权限的接口),请求后端接口,返回当前用户所有的接口权限信息,并存储到vuex中(这里存到了permissionButtons)。
    • 通过自定义指令,判断当前按钮的key是否存在permissionButtons数组中,如果存在,则有权限,显示按钮,如果不存在,则当前用户没有该按钮的操作权限,在页面上隐藏当前按钮。
      // 自定义permission指令
      import Vue from 'vue';
      import store from '@/store';
      Vue.directive('permission', {
          inserted(el, binding) {
              const permissionCode = binding.value;
              if (permissionCode) {
                  const permissionButtons = store.getters.permissionButtons;
                  if (_.indexOf(permissionButtons, permissionCode) === -1) {
                      el.parentNode.removeChild(el);
                  }
              }
          }
      });
      
      // 页面按钮权限绑定
      <el-button v-permission="'system:role:add'">新增</el-button>
      
    • 接口的权限需要后端配合处理,前端隐藏按钮,只是视图层面的控制,如果用户通过postman等工具或其他途径直接访问接口,还是会绕开视图层面的限制,访问接口,所以,需要后端配合进行接口层级上的处理。后端需要把当前用户的权限经过处理,生成对应token,前端携带token访问,后端通过处理token,判断当前用户是否有该接口的访问权限,如果有,则允许访问,否则拒绝访问,从而实现接口层级的权限控制。
  2. 实现【页面/路由】权限
    • 在菜单权限模块,录入系统所有需要控制权限的页面,主要有页面的【名称】【路由地址】【组件位置】【图标】等信息。
    • 同样和【操作/接口】权限一样,通过给角色配置页面权限,用户关联角色,即可获取到当前用户的所有页面权限。
    • 同样在用户登陆成功后,获取当前用户所有页面权限,并对路由进行动态注册(注意:有部分页面是不需要动态注册,所有用户都可以访问的,像登陆页面、404页面、403页面等,可以写死到代码中,其类似于白名单),返回的所有路由信息放到permissionMenus。
      import Vue from 'vue';
      import VueRouter from 'vue-router';
      import store from '@/store';
      import {getToken} from '@/utils/storage';
      import NProgress from 'nprogress';
      import 'nprogress/nprogress.css';
      NProgress.configure({showSpinner: false});
      
      Vue.use(VueRouter);
      
      export const constantRoutes = [
          {
              path: '/login',
              component: () => import('@/pages/login'),
              hidden: true
          }
      ];
      
      const createRouter = () =>
          new VueRouter({
              routes: constantRoutes
          });
      
      const router = createRouter();
      
      const whiteList = ['/login'];
      
      router.beforeEach((to, from, next) => {
          NProgress.start();
          if (getToken()) {
              // 如果登陆后 还跳转至登陆页 则跳转到系统默认页
              if (to.path === '/login') {
                  next({path: '/'});
              }
              else {
                  if (_.isEmpty(store.getters.userInfo)) {
                      // 获取用户信息,处理当前用户路由信息
                      store.dispatch('user/getUserInfo').then(() => {
                          const newRouter = createRouter();
                          router.matcher = newRouter.matcher;
                          const getRouter = store.getters.permissionMenus;
                          getRouter.push({path: '*', redirect: '/'});
                          router.options.routes = [...constantRoutes, ...getRouter];
                          router.addRoutes(getRouter);
                          store.dispatch('user/getUserFieldConfig').then(res => {
                              next({...to, replace: true});
                          }).catch(() => {
                              next({path: '/login', replace: true});
                          });
                      }).catch(err => {
                          store.dispatch('user/logout');
                      });
                  }
                  else {
                      next();
                  }
              }
          }
          else {
              // 没有token
              if (whiteList.indexOf(to.path) !== -1) {
                  // 在免登录白名单,直接进入
                  next();
              }
              else {
                  // 否则全部重定向到登录页
                  next(`/login?redirect=${to.fullPath}`);
              }
          }
      });
      router.afterEach(() => {
          NProgress.done();
          window.scrollTo(0, 0);
      });
      export default router;
      

三、小结

权限的控制实现还有很多方法,这里只是列举了其中一种。