首页 > 编程 > JavaScript > 正文

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

2019-11-19 15:26:19
字体:
来源:转载
供稿:网友

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

采用技术: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"    ]  }}

存在的问题

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表