前两篇关于vue权限路由文章的填坑,说了一堆理论,是时候操做一波了。html
选择d2-admin是由于element-ui的相关开源项目里,d2-admin的结构和代码是让我感到最舒服的,并且基于d2-admin实现RBAC权限管理也很方便,对d2-admin没有大的侵入性的改动。html5
预览地址git
Githubgithub
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
不了解RBAC,能够看这里企业管理系统先后端分离架构设计 系列一 权限模型篇vuex
菜单
与功能
,一个菜单下能够有多个功能,菜单
类型的permission
字段标识访问这个菜单须要的功能权限,功能
类型的permission
字段至关于此功能的别称,因此菜单
类型的permission
字段为其某个功能
类型子节点的permission
值permission
字段过滤出用户所能访问的路由使用d2admin
的原有登陆逻辑,全局路由守卫中判断是否已经拉取权限信息,获取后标识为已获取。数据库
const token = util.cookies.get('token')
if (token && token !== 'undefined') {
//拉取权限信息
if (!isFetchPermissionInfo) {
await fetchPermissionInfo();
isFetchPermissionInfo = true;
next(to.path, true)
} else {
next()
}
} else {
// 将当前预计打开的页面完整地址临时存储 登陆后继续跳转
// 这个 cookie(redirect) 会在登陆后自动删除
util.cookies.set('redirect', to.fullPath)
// 没有登陆的时候跳转到登陆界面
next({
name: 'login'
})
}
复制代码
//标记是否已经拉取权限信息
let isFetchPermissionInfo = false
let fetchPermissionInfo = async () => {
//处理动态添加的路由
const formatRoutes = function (routes) {
routes.forEach(route => {
route.component = routerMapComponents[route.component]
if (route.children) {
formatRoutes(route.children)
}
})
}
try {
let userPermissionInfo = await userService.getUserPermissionInfo()
permissionMenu = userPermissionInfo.accessMenus
permissionRouter = userPermissionInfo.accessRoutes
permission.functions = userPermissionInfo.userPermissions
permission.roles = userPermissionInfo.userRoles
permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)
permission.isAdmin = userPermissionInfo.isAdmin == 1
} catch (ex) {
console.log(ex)
}
formatRoutes(permissionRouter)
let allMenuAside = [...menuAside, ...permissionMenu]
let allMenuHeader = [...menuHeader, ...permissionMenu]
//动态添加路由
router.addRoutes(permissionRouter);
// 处理路由 获得每一级的路由设置
store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
// 设置顶栏菜单
store.commit('d2admin/menu/headerSet', allMenuHeader)
// 设置侧边栏菜单
store.commit('d2admin/menu/fullAsideSet', allMenuAside)
// 初始化菜单搜索功能
store.commit('d2admin/search/init', allMenuHeader)
// 设置权限信息
store.commit('d2admin/permission/set', permission)
// 加载上次退出时的多页列表
store.dispatch('d2admin/page/openedLoad')
await Promise.resolve()
}
复制代码
后端须要返回的权限信息包括权限过滤后的角色编码集合,功能编码集合,接口信息集合,菜单列表,路由列表,以及是否系统管理员标识。格式以下element-ui
{
"statusCode": 200,
"msg": "",
"data": {
"userName": "MenuManager",
"userRoles": [
"R_MENUADMIN"
],
"userPermissions": [
"p_menu_view",
"p_menu_edit",
"p_menu_menu"
],
"accessMenus": [
{
"title": "系统",
"path": "/system",
"icon": "cogs",
"children": [
{
"title": "系统设置",
"icon": "cogs",
"children": [
{
"title": "菜单管理",
"path": "/system/menu",
"icon": "th-list"
}
]
},
{
"title": "组织架构",
"icon": "pie-chart",
"children": [
{
"title": "部门管理",
"icon": "html5"
},
{
"title": "职位管理",
"icon": "opencart"
}
]
}
]
}
],
"accessRoutes": [
{
"name": "System",
"path": "/system",
"component": "layoutHeaderAside",
"componentPath": "layout/header-aside/layout",
"meta": {
"title": "系统设置",
"cache": true
},
"children": [
{
"name": "MenuPage",
"path": "/system/menu",
"component": "menu",
"componentPath": "pages/sys/menu/index",
"meta": {
"title": "菜单管理",
"cache": true
}
},
{
"name": "RoutePage",
"path": "/system/route",
"component": "route",
"componentPath": "pages/sys/route/index",
"meta": {
"title": "路由管理",
"cache": true
}
},
{
"name": "RolePage",
"path": "/system/role",
"component": "role",
"componentPath": "pages/sys/role/index",
"meta": {
"title": "角色管理",
"cache": true
}
},
{
"name": "UserPage",
"path": "/system/user",
"component": "user",
"componentPath": "pages/sys/user/index",
"meta": {
"title": "用户管理",
"cache": true
}
},
{
"name": "InterfacePage",
"path": "/system/interface",
"component": "interface",
"meta": {
"title": "接口管理"
}
}
]
}
],
"accessInterfaces": [
{
"path": "/menu/:id",
"method": "get"
},
{
"path": "/menu",
"method": "get"
},
{
"path": "/menu/save",
"method": "post"
},
{
"path": "/interface/paged",
"method": "get"
}
],
"isAdmin": 0,
"avatarUrl": "https://api.adorable.io/avatars/85/abott@adorable.png"
}
}
复制代码
将固定菜单(/menu/header
、/menu/aside
)与后端返回的权限菜单(accessMenus
)合并后,存入相应的vuex store模块中json
...
let allMenuAside = [...menuAside, ...permissionMenu]
let allMenuHeader = [...menuHeader, ...permissionMenu]
...
// 设置顶栏菜单
store.commit('d2admin/menu/headerSet', allMenuHeader)
// 设置侧边栏菜单
store.commit('d2admin/menu/fullAsideSet', allMenuAside)
// 初始化菜单搜索功能
store.commit('d2admin/search/init', allMenuHeader)
复制代码
默认使用routerMapComponents
的方式处理后端返回的权限路由
//处理动态添加的路由
const formatRoutes = function (routes) {
routes.forEach(route => {
route.component = routerMapComponents[route.component]
if (route.children) {
formatRoutes(route.children)
}
})
}
...
formatRoutes(permissionRouter)
//动态添加路由
router.addRoutes(permissionRouter);
// 处理路由 获得每一级的路由设置
store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
复制代码
路由处理方式及区别可看vue权限路由实现方式总结二
将角色编码集合,功能编码集合,接口信息集合,以及是否系统管理员标识存入相应的vuex store模块中
...
permission.functions = userPermissionInfo.userPermissions
permission.roles = userPermissionInfo.userRoles
permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)
permission.isAdmin = userPermissionInfo.isAdmin == 1
...
// 设置权限信息
store.commit('d2admin/permission/set', permission)
复制代码
支持使用角色编码,功能编码以及接口权限进行控制,以下
export function getMenuList() {
return request({
url: '/menu',
method: 'get',
interfaceCheck: true,
permission:["p_menu_view"],
loading: {
type: 'loading',
options: {
fullscreen: true,
lock: true,
text: '加载中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.8)'
}
},
success: {
type: 'message',
options: {
message: '加载菜单成功',
type: 'success'
}
}
})
}
复制代码
interfaceCheck: true
表示使用接口权限进行控制,若是vuex store中存储的接口信息与当前要请求的接口想匹配,则可发起请求,不然请求将被拦截。
permission:["p_menu_view"]
表示使用角色编码和功能编码进行权限校验,若是vuex store中存储的角色编码或功能编码与当前表示的编码相匹配,则可发起请求,不然请求将被拦截。
源码位置在libs/permission.js
,可根据本身需求进行修改
loading
配置相关源码在libs/loading.js
,根据本身需求进行配置,success
也是如此,源码在libs/loading.js
。 照此思路能够自行配置其它功能,好比请求失败等。
使用指令v-permission
:
<el-button v-permission:function.all="['p_menu_edit']" type="primary" icon="el-icon-edit" size="mini" @click="batchEdit" >批量编辑</el-button>
复制代码
参数可为function
、role
,代表以功能编码或角色编码进行校验,为空则使用二者进行校验。
修饰符all
,表示必须所有匹配指令值中全部的编码。
源码位置在plugin/permission/index.js
,根据本身实际需求进行修改。
使用v-if
+全局方法:
<el-button v-if="canAdd" type="primary" icon="el-icon-circle-plus-outline" size="mini" @click="add" >添加</el-button>
复制代码
data() {
return {
canAdd: this.hasPermissions(["p_menu_edit"])
};
},
复制代码
默认同时使用角色编码与功能编码进行校验,有一项匹配便可。
相似的方法还要hasFunctions
,hasRoles
。
源码位置在plugin/permission/index.js
,根据本身实际需求进行修改。
不要使用
v-if="hasPermissions(['p_menu_edit'])"
这种方式,会致使方法屡次执行
也能够直接在组件中从vuex store读取权限信息进行校验。
页面级别的组件放到pages/
目录下,而且在routerMapCompnonents/index.js
中以key-value的形式导出
不须要权限控制的固定菜单放到menu/aside.js
和menu/header.js
中
不须要权限控制的路由放到router/routes.js
frameIn
内
须要权限控制的菜单与路由经过界面的管理功能进行添加,确保菜单的path
与路由的path
相对应,路由的name
与页面组件的name
一致才能使keep-alive
生效,路由的component
在routerMapCompnonents/index.js
中能经过key匹配到。
开发阶段菜单与路由的添加可由开发人员自行维护,并维护一份清单,上线后将清单交给相关的人去维护便可。
若是以为麻烦,不想菜单与路由由后端返回,能够在前端维护一份菜单和路由(路由中的
component
仍是使用字符串,参考mock/permissionMenuAndRouter.js
),而且在菜单和路由上面维护相应的权限编码,通常都是使用功能编码。后端就不须要返回菜单和路由信息了,可是其余权限信息,好比角色编码,功能编码等仍是须要的。经过后端返回的功能编码列表,在前端过滤出用户具有权限的菜单和路由,过滤处理后后的菜单与路由格式与以前由后端返回的格式一致,而后将处理后的菜单与路由当作后端返回的同样处理便可。
数据mock使用lazy-mock修改而来的d2-admin-server,数据真实来源于后端,相比其余工具,支持数据持久化,存储使用的是json文件,不须要安装数据库。简单的配置便可自动生成增删改查的接口。
后端使用中间件控制访问权限,好比:
.get('/menu', PermissionCheck(), controllers.menu.getMenuList)
复制代码
PermissionCheck
默认使用接口进行校验,校验用户所能访问的API中是否匹配当前API,支持使用功能编码与角色编码进行校验PermissionCheck(["p_menu_edit"],["r_menu_admin"],true)
,第一个参数为功能编码,第二个为角色编码,第三个为是否使用接口进行校验。
更多详细用法可看lazy-mock文档
前端代码生成还在开发中...