欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-09-08 11:44:28
项目背景:物业管理后台,不同角色拥有不同权限 采用技术:vue.js + vuex + element ui 实现 rbac 权限管理需要后端接口支持,这里仅提供前端解...

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

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

存在的问题

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

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