这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
背景
在管理后台系统中,vue-router 往往要承担更多的事情。
因为在管理后台,一个非常重要也非常常见的功能,就是用户权限的区分。一般用户,管理用户,超级管理员,审计用户等等。
针对不同的用户,既要做接口的检验,也要对页面的权限进行划分。
比如:
- 超级管理员拥有所有的权限,能查看所有的页面,能进行所有的操作
- 管理用户,则不能添加,删除,变更系统里的用户
- 审计用户,只能查看需要审计的内容,如系统操作日志等
以上种种,其实都是跟页面的展示,路由的访问,接口的校验强相关的。当前用户如果是审计用户,那么只能展示某些页面,如果是管理用户,那要隐藏某些页面。
另外,管理项目都有一套侧边栏作为不同功能页面的入口。
假设我们的路由和侧边栏是分开的逻辑,那么就有以下的问题:
- 每次更新一次路由,就要更新一次侧边栏,
- 路由中做了权限,侧边栏还要单独加一个权限管理的逻辑
这样会显得我们前端开发很呆板。
所以总结来说,vue-router 要实现这样的功能
- 挂载组件到页面
- 根据不同的用户角色,如:超级管理员,审计用户,普通用户等来做路由权限管理
- 根据路由生成复杂的侧边栏
那么,如何使用一套定义好的 router 来做到以上三个需求呢?
分析
首先,我们来细致的分析一下这三个需求有哪些具体需要
挂载组件到页面中
想必不用多说了,vue-router 本来就是做这个的
区分不同用户角色
用户角色一般是保存在数据库中,用户登录之后由后台的接口返回的,比如这个字段是user_role
,那么这里怎么实现动态的路由区分呢?
- 首先,我们可以把这个字段存储在 vuex 中,这样可以做到全局访问,全局自动更新
- 其次,在 vue-router 的每一个路由对象中插入一个字段,比如叫
permission
,来记录当前这个路由能够被谁访问,如permission: [0,1,2]
- 根据
user_role
和permission
来匹配筛选所有的路由,筛选后的结果,就是当前用户能够访问的路由 - 使用 vue-router 的方法将得到的路由添加到项目的路由对象里,地址
生成复杂的侧边栏
侧边栏是管理后台都有的功能,这里参考 vue-element-admin 的实现。
本质上 router 是一个javascript 的数组对象,遍历这个对象,利用 elementui 的 el-menu 组件来生成侧边栏就可以了。
如果 router 变化了 那么侧边栏也会随之变化。
实现
我们先定义一个整体的 router 对象,这个对象是整个项目里所有的页面路由,也是超级管理员默认的访问权限,这个对象的每一个route 对象,都可以含有如下的属性或者方法:
- hidden: 是否显示在侧边栏
- name: 侧边栏对应的文字内容,当name不存在,则children中的路由为一级路由,否则为二级路由,侧边栏最多显示到二级路由
- iconName:侧边栏的 icon 图标
- permission:可以访问辞路由的用户列表
// routerData.js
const router1 = [
{
path: '/',
redirect: '/homepage',
hidden: true,
meta: { permission: [1, 2, 3] }
},
{
path: '/homepage',
meta: { permission: [1, 2, 3] },
redirect: '/homepage/dashboard',
component: layout,
children: [
{
path: 'dashboard',
name: '安全状态监控',
meta: { iconName: 'icon-erji-loudongsaomiaoguanli', permission: [1, 2, 3] },
component: () => import('../views/dashboard/index.vue'),
},
]
},
{
path: '/assetManagement',
name: '资产管理',
meta: { iconName: 'icon-erji-loudongsaomiaoguanli', permission: [1, 2, 3] },
redirect: '/assetManagement/assetList',
component: layout,
children: [
{
path: 'assetList',
name: '资产列表',
meta: { permission: [1, 2, 3] },
component: () => import('../views/assetManagement/assetList.vue'),
},
{
path: 'assetDetail',
name: '资产详情',
meta: { permission: [1, 2, 3] },
component: () => import('../views/assetManagement/assetList.vue'),
},
]
},
{ path: '*', redirect: '/', hidden: true, meta: { permission: [1, 2, 3] }, }
]
export commonRoute = [{
path: '/login',
component: () => import('@/views/login.vue'),
hidden: true,
meta: { permission: [1, 2, 3] },
}]
export default router1
复制代码
然后我们在 index.js 中实现 router 的挂载
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import { commonRoute } from './routerData'
import layout from '@/layout'
Vue.use(VueRouter)
export const permissionRouter = function(router, roleType) {
return router.filter(item => {
if(item.children)
item.children = permissionRouter(item.children)
return item.meta.permission.includes(roleType)
})
}
export default new VueRouter({
commonRoute
})
复制代码
当用户登录后,我们先获取用户的角色,然后放到 vuex 中存储后,就可以根据用户角色,动态的计算出权限路由了
import store from '@/store'
import router1 from '@/router/routerData'
import router, {permissionRouter} from '@/router/index'
const roleType = store.getters.user_role
const permissionRouterList = permissionRouter(router1, roleType)
router.addRoute(permissionRouterList)
复制代码
到这里整个 router 就写好了,router 接下来就可以准备动态的生成侧边栏,侧边栏逻辑复杂一点,所以我也是拆成了三个文件
这是文件的目录:
sidebar
├── index.vue
├── item.vue
├── sidebarItem.vue
复制代码
然后来实现动态侧边栏的逻辑
// index.vue
<template>
<div class="sidebar-container">
<el-menu :unique-opened="true"
:collapse-transition="false"
background-color="rgba(0,0,0,0)"
:default-active="activeMenu">
<sidebar-item v-for="(item, index) in permissionRouterList"
:key="index"
:item="item" />
</el-menu>
</div>
</template>
<script>
import store from '@/store'
import router1 from '@/router/routerData'
import {permissionRouter} from '@/router/index'
import SidebarItem from './sidebarItem.vue'
export default {
components: {
SidebarItem
},
computed: {
activeMenu() {
return this.$route.path
},
permissionRouterList(){
const roleType = store.getters.user_role
return permissionRouter(router1, roleType)
}
}
}
</script>
复制代码
// item.vue
<template>
<el-menu-item :index="compilePath(child.path)"
@click="clickEvent(child)"
v-if="!child.hidden">
<i v-if="child.meta"
class="iconfont"
:class="child.meta.iconName"></i>
<span slot="title">{{child.name}}</span>
</el-menu-item>
</template>
<script>
import path from 'path'
export default {
props: {
child: {
type: Object
},
basePath:{
type: String
}
},
methods: {
compilePath(childpath) {
return path.resolve(this.basePath, childpath)
},
clickEvent(child) {
const path = this.compilePath(child.path)
this.$router.push(path)
}
}
}
</script>
复制代码
// sidebarItem.vue
<template>
<el-submenu v-if="(item.children && hasName(item)) && !item.hidden"
:index="item.path">
<template slot="title">
<i v-if="item.meta"
class="iconfont"
:class="item.meta.iconName"></i>
<span slot="title">{{item.name}}</span>
</template>
<menu-item v-for="child in item.children"
:key="compilePath(item.path,child.path)"
:child="child"
:basePath="item.path" />
</el-submenu>
<menu-item :child="item.children[0]"
v-else-if="item.children && !item.hidden"
:basePath="item.path" />
</template>
<script>
import path from 'path'
import MenuItem from './item.vue'
export default {
props: {
item: {
type: Object
}
},
components: {
MenuItem
},
methods: {
compilePath(itempath, childpath) {
return path.resolve(itempath, childpath)
},
hasName(item){
return item.name? true: false
}
}
}
</script>
复制代码
这样,我们的整个需求就完成了,然后将 sidebar 组件正常引入,加入自己的样式,就可以了
最后的最后,代码未经测试,有任何问题请私信或评论,谢谢。