Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue后台管理系统之 视图、按钮、路由权限 #1

Open
huanglong6828 opened this issue May 28, 2018 · 0 comments
Open

Vue后台管理系统之 视图、按钮、路由权限 #1

huanglong6828 opened this issue May 28, 2018 · 0 comments
Labels
vue vue相关

Comments

@huanglong6828
Copy link
Owner

huanglong6828 commented May 28, 2018

需求

后台管理系统关系到各种权限相关,是最复杂也是最重要的最近基础的一块。
一般权限程度由浅到深:浅程度为路由权限,做到最简单的控制路由用户只能访问对应的页面,深程度为按钮,视图权限,用户都可以查看公共页面数据,却没有操作权限或者管理员视图不能查看。了解清楚自己想做到的程度后我们就由浅到深配置权限吧。

前端的权限控制实质上就是用于展示,让操作变得更加友好,真正的安全实际上是由后端控制的

由浅——路由权限

首先我们了解清楚,路由权限由前端or后端来控制。如果后端返回路由请跳过当前块。
当前模块学习 vue-element-admin 开源项目,再在基础上加自己需要的东西。

  1. 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
  2. 当用户登录后,获取用roles/types,将roles/types和路由表/按钮视图表进行权限作比较,生成最终用户可访问的路由表和按钮视图表。
  3. 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由,和使用自定义指令 v-permission控制按钮视图
  4. 使用vuex管理路由表和按钮视图表。

router.js

// router.js
import Vue from 'vue';
import Router from 'vue-router';

import Login from '../views/login/';
const dashboard = resolve => require(['../views/dashboard/index'], resolve);
//使用了vue-routerd的[Lazy Loading Routes](https://router.vuejs.org/en/advanced/lazy-loading.html)

//所有权限通用路由表 
//如首页和登录页和一些不用权限的公用页面
export const routerMap = [
    //hidden为自定义属性,侧边栏那章会纤细解释
    {
        path: '/login',
        component: Login,
        hidden: true
    },
    {
        path: '/',
        component: Layout,
        redirect: '/dashboard',
        name: '首页',
        children: [{
            path: 'dashboard',
            component: dashboard
        }]
    },
]

//实例化vue的时候只挂载routerMap
export default new Router({
    routes: routerMap
});

//异步挂载的路由
//动态需要根据权限加载的路由表 
//maintenance 代表维保单位typeid(1) m1(主管理员) m2(子管理员) m3(普通员工)
//property 代表使用单位typeid(2) p1(主管理员) p2(子管理员) p3(普通员工)
//government 代表质检单位typeid(3) g1(主管理员) g2(子管理员) g3(普通员工)
export const asyncRouterMap = [{
        path: '/Permission',
        component: Layout,
        redirect: '/Permission/index',
        children: [{
            path: 'index',
            component: Permission,
            name: 'Permission',
            meta: {
                roles: ['m1', 'p1', 'g1'],
                types: ['government', 'property', 'maintenance']
            }
        }]
    },
    {
        path: '*',
        redirect: '/404',
        hidden: true
    }
];

这里我们根据vue-router官方推荐的方法通过meta标签来标示改页面能访问的权限有哪些。

如meta: { roles: ['m1', 'p1', 'g1' ] }表示该页面只有维保单位和使用单位的主管理员才能有资格进入。

注意事项:
这里有一个需要非常注意的地方就是404页面一定要最后加载,如果放在constantRouterMap一同声明了404,后面的所以页面都会被拦截到404,详细的问题见addRoutes when you've got a wildcard route for 404s does not work

router.beforeEach

结合router.beforeEach处理路由权限,此代码包含按钮权限。

// permission.js
router.beforeEach((to, from, next) => {
  if (store.getters.token) { // 判断是否有token
    if (to.path === '/login') {
      next({
        path: '/'
      });
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(res => { // 拉取info
          //区别当前用户类型和用户权限匹配符合的路由和按钮视图等
          let type, userRoleId
          if (res.data.typeid == 1) type = 'maintenance'
          if (res.data.typeid == 2) type = 'property'
          if (res.data.typeid == 3) type = 'government'
          userRoleId = type[0] + res.data.userRoleId

          const obj = {
            roles: [userRoleId],
            types: [type]
          }
          store.dispatch('GenerateRoutes', obj).then(() => { // 根据权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch(err => {
          console.log(err);
        });
      } else {
        next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next();
    } else {
      next('/login'); // 否则全部重定向到登录页
    }
  }
});

Vuex——permission

通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。此代码包含了按钮和视图权限。

import { asyncRouterMap, routerMap } from '@/router'
import { constantButtonPermission } from '@/utils/buttonPermission'
/**
 * 通过meta.role判断是否与当前用户权限匹配
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.indexOf(role) >= 0)
  } else {
    return true //此处为true因为递归,首先要保证父级router的一直存在
  }
}

/**
 * 通过meta.type判断当前端
 * @param types
 * @param route
 */
function hasTypes(types, route) {
  if (route.meta && route.meta.types) {
    return types.some(type => route.meta.types.indexOf(type) >= 0)
  } else {
    return true
  }
}

/**
 * 递归过滤异步路由表,返回符合用户角色权限的路由表
 * @param asyncRouterMap
 * @param roles
 * @param types
 */
function filterAsyncRouter(asyncRouterMap, roles, types) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (hasPermission(roles, route) && hasTypes(types, route)) {
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, roles, types)
      }
      return true
    }
    return false
  })
  return accessedRouters
}
/**
 * 过滤按钮视图,返回符合用户按钮和视图
 * @param constantButtonPermission
 * @param roles
 * @param types
 */
function filterButton(constantButtonPermission, roles, types) {
  const accessedButton = []
  constantButtonPermission.filter(Button => {
    if (hasPermission(roles, Button) && hasTypes(types, Button)) {
      accessedButton.push(Button.name)
      return true
    }
    return false
  })
  return accessedButton
}
/**
 * 返回所有用户按钮和视图
 * @param constantButtonPermission
 */
function AllButton(constantButtonPermission) {
  const AllPermission = {}
  for (let i = 0; i < constantButtonPermission.length; i++) {
    AllPermission[constantButtonPermission[i].name] = constantButtonPermission[i].name
  }
  return AllPermission
}
const permission = {
  state: {
    routers: routerMap,
    addRouters: [],
    AllPermission: AllButton(constantButtonPermission),
    constantButton: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers
      state.routers = routerMap.concat(routers)
    },
    SET_BUTTON_PERMISSIONS: (state, buttonPermissions) => {
      state.constantButton = buttonPermissions
    },
  },
  actions: {
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        const roles = data.roles
        const types = data.types
        let accessedRouters, accessedButtons
        //  当前if判断是某个权限拥有所有菜单 则不需要判断,现在每个权限不同 则需要每次判断
        // if (roles.indexOf('1') >= 0 && types.indexOf('government') >= 0) {
        // accessedRouters = asyncRouterMap
        // } else {
        accessedRouters = filterAsyncRouter(asyncRouterMap, roles, types)
        // }
        accessedButtons = filterButton(constantButtonPermission, roles, types)
        commit('SET_ROUTERS', accessedRouters)
        commit('SET_BUTTON_PERMISSIONS', accessedButtons)
        resolve()
      })
    }
  }
}

export default permission

现在当前用户的权限菜单就获取到了,剩下的菜单渲染就靠自己去实现了。

到深——按钮、视图权限

上面的代码已经判断了用户类型和用户角色,当下我们需要一张按钮和视图数据表。判断按钮是否显示的逻辑和路由基本一致。

buttonPermission

//配置符合当权限的数据表
export const constantButtonPermission = [
{
  name: 'person_details',
  title: '管理员详情',
  meta: {
    roles: ['m1', 'm2', 'm3'],
    types: ['maintenance']
  }
},
{
  name: 'elevator_delete',
  title: '删除按钮',
  meta: {
    roles: ['m1', 'm2', ],
    types: ['maintenance']
  }
}, ]

自定义指令

v-if的响应特性是把双刃剑,因为判断表达式在运行过程中会频繁触发,但实际上在一个用户会话周期内其权限并不会发生变化,因此如果只需要校验权限的话,用v-if会产生大量不必要的运算,这种情况只需在视图载入时校验一次即可,可以通过自定义指令实现:

Vue.directive('permission', {
  bind(el, binding) {
    setTimeout(() => {
      if (!store.state.permission.constantButton.includes(binding.value)) {
        el.parentNode.removeChild(el);
      }
    }, 0)
  }
});

// 传入当前按钮或者视图,自动匹配是否支持当前用户
v-permission='$store.state.permission.AllPermission.person_details'

总结

vue后台管理系统的路由、按钮、视图权限就配置完成。因为本人公司需求有多种用户类型和不同类型有不同权限,结合优秀的@花裤衩的文章,所以很多东西直接复制添加了自己需要的东西,因为他描述很详细本人就不在解释。
代码分享提供大家学习交流,有问题欢迎大家提问。

@huanglong6828 huanglong6828 added the vue vue相关 label May 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
vue vue相关
Projects
None yet
Development

No branches or pull requests

1 participant