详解利用 Vue.js 实现前后端分离的RBAC角色权限管理


Posted in Javascript onSeptember 15, 2017

项目背景:物业管理后台,不同角色拥有不同权限

采用技术:Vue.js + Vuex + Element UI

实现 RBAC 权限管理需要后端接口支持,这里仅提供前端解决方案。

因代码篇幅较大,对代码进行了删减,文中 “...” 即为省略的一部分代码。

大致思路:
首先登录成功后,从后台拉取用户当前可显示的菜单和可用权限列表,分别将其存入 store 的 nav(菜单导航) 和 auth(用户可用权限) 中,在用户切换路由时,判断是否存在 auth ,如果不存在,则重新获取,判断当前访问地址 to.meta.alias 是否在用户可用权限列表中,如果不存在,则提示无权限,否则进入路由。

1. 路由与侧边菜单分离

侧边菜单相关代码 Main.vue

<template>
<!-- ... -->
  <aside :class="collapsed?'menu-collapsed':'menu-expanded'">
    <!--导航菜单-->
    <el-menu :default-active="$route.path"
         class="el-menu-vertical-aliyun" 
         @open="handleopen"
         @close="handleclose"
         @select="handleselect"
         :collapse="collapsed"
         unique-opened router>
      <template v-for="(item,index) in nav">
        <!-- 二级菜单 -->
        <el-submenu :index="index+''"
              v-if="item.children && item.children.length > 0">
          <!-- 二级菜单顶级 -->
          <template slot="title">
            <i :class="['icon',item.iconCls]"></i>
            <span slot="title">{{item.name}}</span>
          </template>
          <!-- 二级菜单下级 -->
          <el-menu-item-group>
            <!--<span slot="title">{{item.name}}</span>-->
            <!-- && child.url-->
            <template v-for="child in item.children">
              <!--无三级菜单-->
              <el-menu-item
                  :index="child.url"
                  :key="child.url"
                  v-if="!child.children">
                {{child.name}}
              </el-menu-item>
              <!--有三级菜单-->
              <el-submenu
                  :index="child.url"
                  :key="child.url"
                  v-if="child.children">
                <span slot="title">{{child.name}}</span>
                <el-menu-item v-for="subChild in child.children"
                       :index="subChild.url"
                       :key="subChild.url">
                  {{subChild.name}}
                </el-menu-item>
              </el-submenu>
            </template>
          </el-menu-item-group>
        </el-submenu>
        <!-- 一级菜单 -->
        <el-menu-item v-if="!item.children"
               :index="item.url">
          <i :class="['icon',item.iconCls]"></i>
          <span slot="title">{{item.name}}</span>
        </el-menu-item>
  
      </template>
    </el-menu>
  </aside>
<!-- ... -->
</template>

<script>
  export default {
    // ...
    computed: {
     // 从 Vuex 中获取导航菜单
     nav() {
      return this.$store.state.nav;
     }
    }
    // ...
  }
</script>

2. 路由切换前进行鉴权

路由定义的部分代码,对每个路由添加了 meta 属性,用于鉴权。

这里 component 采用了异步引入的方式。

定义路由

// ...
// 系统管理
{
path: '/system',
component: Main,
name: '系统管理',
redirect: '/system/organization',
children: [{
 path: '/system/organization',
 component: () => import ('@/views/System/Organization.vue'),
 name: '组织结构',
 // requiresAuth 用于确认此地址是否需要验证
 // alias 用于获取后端返回rbac权限对应的前端路由地址和导航菜单图标
 meta: {requiresAuth: true, alias: 'Pmsadmin/Oragnize/list'}
},
 {
  path: '/system/user',
  component: () => import ('@/views/System/User.vue'),
  name: '人员管理',
  redirect: '/system/user/index',
  children: [
  {
   path: '/system/user/index',
   component: () => import ('@/views/System/UserList.vue'),
   name: '职员列表',
   meta: {requiresAuth: true, alias: 'Pmsadmin/Admin/list'}
  }
  ]
 },
 {
  path: '/system/auth',
  component: () => import ('@/views/System/Auth.vue'),
  name: '角色管理',
  meta: {requiresAuth: true, alias: 'Pmsadmin/Role/list'}
 }
]
}
// ...

路由钩子 beforeEach

router.beforeEach((to, from, next) => {
 document.title = `${configs.title} - ${to.name}`;
 const {hasAuth, auth} = store.state.user;
 // 未拿到权限,则获取
 if (!hasAuth) {
  store.dispatch('getUserAuth');
  console.log('重新获取用户权限');
  // next();
 }
 // 如果未登录,跳转
 if (window.localStorage.getItem('IS_LOGIN') === null && to.path !== '/login') {
  console.log('未登录状态');
  next({
   path: '/login',
   query: {redirect: to.fullPath}
   // 将跳转的路由path作为参数,登录成功后跳转到该路由
  })
 } else {
  // 需要鉴权的路由地址
  console.log(to, auth.indexOf(to.meta.alias), auth);
  if (to.meta.requiresAuth) {
   if (auth.indexOf(to.meta.alias) > -1) {
    console.log('有权限进入');
    next();
   } else {
    if(auth.length > 0) {
     Message.error({
      message: '当前用户权限不足,无法访问',
      showClose: true,
     });
    } else {
     next();
    }
   }
  } else {
   next();
  }
 }
});

在 Vuex 的 state 中,定义好 nav 对象

// 登录用户信息
const user = {
 name: '', // 用户名
 avatar: '', // 用户头像
 auth: [], // 用户权限
 hasAuth: false // 是否已经加载用户权限
};
// 导航菜单
const nav = [];

通过 action 异步获取数据

// 获取用户权限
const getUserAuth = async ({commit}) => {
 const res = await http.post('YOUR_URL', {});
 if (res === null) return;
 console.log('getUserAuth', res.param);
 commit('SET_USER_AUTH', res.param.auth);
 commit('SET_SIDE_NAV', res.param.nav);
};

Vuex 中的 mutation 的相关代码

// 设置用户权限
const SET_USER_AUTH = (state, auth) => {
 state.user.auth = auth.concat('欢迎使用');
 state.user.hasAuth = true;
};
// 设置导航菜单
const SET_SIDE_NAV = (state, nav) => {
 // 导航菜单
 let _nav = [{
  name: '欢迎使用',
  url: "/main",
  iconCls: 'fa fa-bookmark'
 }];
 // 权限菜单对应的路由地址
 const route = {
  "系统管理": {iconCls: 'fa fa-archive', url: ''},
  "Pmsadmin/Oragnize/list": {iconCls: '', url: '/system/organization'},
  "Pmsadmin/Admin/list": {iconCls: '', url: '/system/user/index'},
  "Pmsadmin/Role/list": {iconCls: '', url: '/system/auth'},
  "Pmsadmin/Log/record": {iconCls: '', url: '/system/logs'},
  "项目管理": {iconCls: 'fa fa-unlock-alt', url: ''},
  "Pmsadmin/Project/list": {iconCls: '', url: '/project/list/index'},
  "Pmsadmin/House/list": {iconCls: '', url: '/project/house'},
  "Pmsadmin/Pack/list": {iconCls: '', url: '/project/pack'},
  "广告位": {iconCls: 'fa fa-edit', url: ''},
  "Pmsadmin/Place/list": {iconCls: '', url: '/adsplace/list'},
  "投诉建议": {iconCls: 'fa fa-tasks', url: ''},
  "Pmsadmin/Scategory/list": {iconCls: '', url: '/complain/type'},
  "Pmsadmin/Complain/list": {iconCls: '', url: '/complain/list'},
  "Pmsadmin/Suggest/list": {iconCls: '', url: '/complain/suggestion'},
  "报事报修": {iconCls: 'fa fa-user', url: ''},
  "Pmsadmin/Rcategory/list": {iconCls: '', url: '/rcategory/type'},
  "Pmsadmin/Rcategory/info": {iconCls: '', url: '/rcategory/public'},
  "Pmsadmin/Repair/list": {iconCls: '', url: '/rcategory/personal'},
  "便民服务": {iconCls: 'fa fa-external-link', url: ''},
  "Pmsadmin/Bcategory/list": {iconCls: '', url: '/bcategory/type'},
  "Pmsadmin/Service/list": {iconCls: '', url: '/bcategory/list'},
  "首座推荐": {iconCls: 'fa fa-file-text', url: ''},
  "Pmsadmin/stcategory/list": {iconCls: '', url: '/stcategory/type'},
  "Pmsadmin/Store/list": {iconCls: '', url: '/stcategory/list'},
  "招商租赁": {iconCls: 'fa fa-leaf', url: ''},
  "Pmsadmin/Bussiness/list": {iconCls: '', url: '/bussiness/list'},
  "Pmsadmin/Company/list": {iconCls: '', url: '/bussiness/company'},
  "Pmsadmin/Question/list": {iconCls: '', url: '/bussiness/question'},
  "停车找车": {iconCls: 'fa fa-ra', url: ''},
  "Pmsadmin/Cplace/list": {iconCls: '', url: '/cplace/cmanage'},
  "Pmsadmin/Clist/list": {iconCls: '', url: '/cplace/clist'},
  "Pmsadmin/Cquestion/list": {iconCls: '', url: '/cplace/cquestion'},
 };
 for (let key in nav) {
  let item = nav[key];
  let _temp = {};
  let subItems = []; // 二级菜单临时数组
  if (item.children && item.children.length > 0) {
   // 二级菜单
   item.children.forEach(subItem => {
    subItems.push(Object.assign({}, {
     name: subItem.name || '',
     url: route[subItem.url].url || '',
     iconCls: route[subItem.url].iconCls || '',
    }))
   });
   // 一级菜单
   _temp = Object.assign({}, {
    name: item.name || '',
    url: item.url || '',
    iconCls: route[item.name].iconCls || '',
    children: subItems.slice(0)
   });
   _nav.push(_temp);
  }
 }
 state.nav = _nav;
};

3. 后端接口返回内容

{
  "status": 200,
  "info": "数据查询成功!",
  "param": {
    "nav": {
      "1": {
        "name": "系统管理",
        "url": "",
        "children": [
          {
            "name": "组织结构",
            "url": "Pmsadmin/Oragnize/list"
          },
          {
            "name": "人员管理",
            "url": "Pmsadmin/Admin/list"
          },
          {
            "name": "角色管理",
            "url": "Pmsadmin/Role/list"
          },
          {
            "name": "日志管理",
            "url": "Pmsadmin/Log/record"
          }
        ]
      },
      "61": {
        "name": "广告位",
        "url": "",
        "children": [
          {
            "name": "广告位列表",
            "url": "Pmsadmin/Place/list"
          }
        ]
      }
    },
    "auth": [
      "系统管理",
      "Pmsadmin/Oragnize/list",
      "Pmsadmin/Admin/list",
      "Pmsadmin/Role/list",
      "Pmsadmin/Log/record",
      "广告位",
      "Pmsadmin/Place/list"
    ]
  }
}

存在的问题

  • 新增 修改 删除 按钮还无法实现根据用户权限控制其显示
  • 代码上还存在着不足,期待大神能够有更优的解决方案。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 定义新对象方法
Feb 20 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
Dec 16 Javascript
基于jQuery实现的图片切换焦点图整理
Dec 07 Javascript
javascript中的遍历for in 以及with的用法
Dec 22 Javascript
jQuery异步获取json数据方法汇总
Dec 22 Javascript
原生js实现类似弹窗抖动效果
Apr 02 Javascript
使用three.js 画渐变的直线
Jun 05 Javascript
数据结构中的各种排序方法小结(JS实现)
Jul 23 Javascript
jQuery Mobile漏洞会有跨站脚本攻击风险
Feb 12 Javascript
玩转vue的slot内容分发
Sep 22 Javascript
基于element-ui组件手动实现单选和上传功能
Dec 06 Javascript
jQuery擦除插件eraser使用方法详解
Jan 11 jQuery
动态创建Angular组件实现popup弹窗功能
Sep 15 #Javascript
Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)
Sep 15 #Javascript
angular4模块中给标签添加背景图的实现方法
Sep 15 #Javascript
基于Vue生产环境部署详解
Sep 15 #Javascript
基于Vue单文件组件详解
Sep 15 #Javascript
json2.js 入门教程之使用方法与实例分析
Sep 14 #Javascript
php main 与 iframe 相互通讯类(js+php同域/跨域)
Sep 14 #Javascript
You might like
PHP获取短链接跳转后的真实地址和响应头信息的方法
2014/07/25 PHP
php短信接口代码
2016/05/13 PHP
Yii2-GridView 中让关联字段带搜索和排序功能示例
2017/01/21 PHP
PHP编译configure时常见错误的总结
2017/08/17 PHP
JavaScript 使用技巧精萃(.net html
2009/04/25 Javascript
各浏览器对click方法的支持差异小结
2011/07/31 Javascript
javascript学习笔记(九) js对象 设计模式
2012/06/19 Javascript
有关于JS辅助函数inherit()的问题
2013/04/07 Javascript
JQuery通过AJAX从后台获取信息显示在表格上并支持行选中
2015/09/15 Javascript
JavaScript仿微博发布信息案例
2016/11/16 Javascript
Angular.js自动化测试之protractor详解
2017/07/07 Javascript
解决JavaScript layui 下拉框不显示的问题
2018/08/14 Javascript
解决layer弹层遮罩挡住窗体的问题
2018/08/17 Javascript
浅谈Javascript常用正则表达式应用
2019/03/08 Javascript
vue 地区选择器v-distpicker的常用功能
2019/07/23 Javascript
js实现时钟定时器
2020/03/26 Javascript
jQuery实现增删改查
2020/12/22 jQuery
[03:17]DOTA2-DPC中国联赛1月29日Recap集锦
2021/03/11 DOTA
python 爬虫出现403禁止访问错误详解
2017/03/11 Python
Python读取指定目录下指定后缀文件并保存为docx
2017/04/23 Python
Python爬虫之xlml解析库(全面了解)
2017/08/08 Python
Python利用turtle库绘制彩虹代码示例
2017/12/20 Python
python http接口自动化脚本详解
2018/01/02 Python
Python如何基于rsa模块实现非对称加密与解密
2020/01/03 Python
Pandas时间序列基础详解(转换,索引,切片)
2020/02/26 Python
pycharm最新激活码有效期至2100年(亲测可用)
2021/02/05 Python
用css3写出气球样式的示例代码
2017/09/11 HTML / CSS
JPA的优势都有哪些
2013/07/04 面试题
最受欢迎的自我评价
2013/12/22 职场文书
四风问题自查报告剖析材料
2014/02/08 职场文书
迟到早退检讨书
2014/02/10 职场文书
我的老师教学反思
2014/05/01 职场文书
消防宣传口号
2014/06/16 职场文书
慈善献爱心倡议书
2015/04/27 职场文书
五年级作文之成长
2019/09/16 职场文书
python执行js代码的方法
2021/05/13 Python