iview admin 目前是没有给我处理好权限这块的逻辑。所以,权限这块还是得我们自己去撸。(脸上笑嘻嘻、心里 mmp!)
- 思路
做权限,说到底就是为了让不同权限的用户,可以访问不同的功能模块。比如我是 admin 权限,我就可以为所欲为了,你服不服???那我如果是只有某个权限,可能我只能使用某一个或者某几个功能(也就是前端页面)。页面内的具体功能也是一样的道理,举个栗子:我是 admin 权限,我可以对某个表格进行:增、删、改、查、上传等一系列操作。如果我不是,可能就收到权限的限制,只能进行查看和上传,其他的功能是无权限进行操作的。好了,大概的思路就是这样的,光 BB 谁不会啊;那具体看一下基于 iview admin 的权限是如何实现。
- 具体实现逻辑
我们从上面的思路具体分析一下,首先,不同用户可以访问不同的模块(页面),所以我们要做到不同身份的用户进入系统的时候,就会根据这个用户的身份显示不同的页面,显示不同的菜单。我们的项目正常是路由(router.js)是在本地配置好的一个路由文件,所以,要想实现动态的路由,我们就必须让 router.js 实现动态生成。我选用的方案是 vue-router 2.2 版本新增了一个 router.addRoutes(routes) 方法去实现。(可能还有一些“巨佬”是用的其他方案,’ 佩服三连 ’)。那用了 addRouter 方法之后呢,实际上我们本地的 router.js 是只需要一些基本的路由了,其他都可以删掉了。感觉有点啰嗦了,我先贴代码压压惊吧。
import Main from '@/components/main' /** * iview-admin 中 meta 除了原生参数外可配置的参数: * meta: {* title: { String|Number|Function} * 显示在侧边栏、面包屑和标签栏的文字 * 使用 '{{多语言字段}}' 形式结合多语言使用,例子看多语言的路由配置; * 可以传入一个回调函数,参数是当前路由对象,例子看动态路由和带参路由 * hideInBread: (false) 设为 true 后此级路由将不会出现在面包屑中,示例看 QQ 群路由配置 * hideInMenu: (false) 设为 true 后在左侧菜单不会显示该页面选项 * notCache: (false) 设为 true 后页面在切换标签后不会缓存,如果需要缓存,无需设置这个字段,而且需要设置页面组件 name 属性和路由配置的 name 一致 * access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由 * icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线 '_' * beforeCloseName: (-) 设置该字段,则在关闭当前 tab 页时会去 '@/router/before-close.js' 里寻找该字段名对应的方法,作为关闭前的钩子函数 * } */ var Router = [{ path: '/', name: '_home', redirect: '/home', component: Main, meta: { icon: 'md-home', title: '首页', hideInMenu: true }, children: [{ path: '/home', name: 'home', meta: { icon: 'md-home', title: '首页' }, component: () => import('@/view/home/home.vue') }] }, { path: '/401', name: 'error_401', meta: {hideInMenu: true}, component: () => import('@/view/error-page/401.vue') }, { path: '/500', name: 'error_500', meta: {hideInMenu: true}, component: () => import('@/view/error-page/500.vue') }, { path: '*', name: 'error_404', meta: {hideInMenu: true}, component: () => import('@/view/error-page/404.vue') } ] export default Router
呐~ 我本地路由文件只留下了这些基本的路由,!!!那我其他的路由怎么办??— 不慌,具体的业务路由代码先不用管,交给 addRouter 这位兄 dei 和你们后端的兄 dei 就好了。你要做的就是拿到后端给你返回的 list,然后用 addRouter 方法添加到 router 里就好了。
接下来,你把你之前的路由文件原封不动的 ctrl+ c 给后端的兄 dei,就是你删除掉的那些路由,这里说明一下:(list 的格式必须和 router.js 里的格式一致,可以和后端兄弟商量一下了,让他帮你把格式造好直接返给你。)
接下来进入关键的一步了,终于又可以贴代码了!!!
在 vuex 的 app.js 我定义了一个获取 router 文件的方法:
getUserRouters({commit}) {const code = getParams(window.location.href).code
return new Promise((resolve, reject) => {
try {callBack(code).then(res => {let routers = backendMenusToRouters(res.data.resultData.route)
commit('setRouters', routers)
setToken(res.data.resultData.token)
localSave('dataMenuList',JSON.stringify(res.data.resultData.route))
resolve(res.data.resultData.route)
}).catch(err => {reject(err)
})
} catch (error) {reject(error)
}
})
}
ok,我为什么没有用 iview admin 的登陆逻辑呢,是因为我们这边的登陆会走一个平台的验证,公司统一的,其实和 iview admin 的 差不多的。—- 上面的函数里,我在本地存了一下路由的文件,后面会用到。
成功拿到路由文件之后,我们就可以再 main.js 里让 addRouter 兄 dei 登场了。代码:
const token = getToken()
const queryCode = getParams(window.location.href).code
if (!token && !queryCode) {// 调登陆逻辑} else if (!token && queryCode) {
// 调用 app.js 里的 getUserRouters 方法
store.dispatch('getUserRouters').then(res => {
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App),
mounted() {const routers = backendMenusToRouters(res)
router.addRoutes(routers)
},
})
})
} else {
// 如果是登陆过的,后者是正常刷新操作,只要从 localStorage 里拿数据就好了
router.addRoutes(backendMenusToRouters(JSON.parse(localRead('dataMenuList'))))
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
}
上面的 backendMenusToRouters 函数我贴出来,这个函数就是为了处理路由文件的,路由挂载 component 是一个函数,所以需要特殊处理。
/**
* @description 将后端菜单树转换为路由树
* @param {Array} menus
* @returns {Array}
*/
export const backendMenusToRouters = (menus) => {let routers = []
forEach(menus, (menu) => {
// 将后端数据转换成路由数据
let route = backendMenuToRoute(menu)
// 如果后端数据有下级,则递归处理下级
if (menu.children && menu.children.length !== 0) {route.children = backendMenusToRouters(menu.children)
}
routers.push(route)
})
return routers
}
/**
* @description 将后端菜单转换为路由
* @param {Object} menu
* @returns {Object}
*/
const backendMenuToRoute = (menu) => {
// 具体内容根据自己的数据结构来定,这里需要注意的一点是
// 原先 routers 写法是 component: () => import('@/view/error-page/404.vue')
// 经过 json 数据转换,这里会丢失,所以需要按照上面提过的做转换,下面只写了核心点,其他自行处理
let route = Object.assign({}, menu)
route.component = resolve => require([`@/${menu.component}`], resolve)
return route
}
关于处理 component 需要配置个依赖:
npm install babel-plugin-syntax-dynamic-import
.babelrc 增加
{
“plugins”: [“syntax-dynamic-import”]
}
这里加一句,vuex 里 app.js 的 menuList 方法需做稍稍的小改动:
menuList: (state, getters, rootState) => getMenuByRouter(state.routers, rootState.user.access)
main.vue 的 menuList 赋值:
menuList( ) {return this.$store.getters.menuList},
到这里,你的动态路由可以说已经完成了。
那又有同学问了,那还有一些页面是子页面,不需要在菜单里显示怎么处理???别慌,我喝口水慢慢跟你说。
我们可以看一下路由的配置,在 meta 的对象里,有个 hideInMenu 属性,妥了,那我们搞起来吧。其实 iview admin 已经帮我写好了这块的逻辑处理,代码在 util.js 里:
/**
* @param {Array} list 通过路由列表得到菜单列表
* @returns {Array}
*/
export const getMenuByRouter = (list, access) => {let res = []
forEach(list, item => {if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
let obj = {icon: (item.meta && item.meta.icon) || '',
name: item.name,
meta: item.meta
}
if ((hasChild(item) || (item.meta && item.meta.showAlways))) {obj.children = getMenuByRouter(item.children, access)
}
if (item.meta && item.meta.href) obj.href = item.meta.href
// if (showThisMenuEle(item, access)) res.push(obj)
res.push(obj)
}
})
return res
}
-,- ok,大功告成!慢着 … 那如果我想对菜单进行增删改操作怎么办??
好办!你就前端写页面吧,这里我推荐一个很成熟的方案,我目前项目就是参考他的做的。传送门:xBoot 管理系统
个人认为他们的菜单管理做的真的很棒!!我又不要脸的借鉴了他们的功能权限,进行了我们项目的功能权限管理的设计。
- 具体实现逻辑
首先,每个角色的具体功能权限是在 meta 的 access 里携带进来的,access 是一个数组,[‘del’,’add’….] 我和后端定义好了每个字段代表什么功能权限。比如:’del’ 代表删除按钮、’upload’ 代表上传功能等等 … 当我们进入不同的页面的时候,根据 access 的功能列表给用户设置不同的功能权限。这里是借鉴了网上的实现逻辑,自定义一个指令,然后每个按钮根据功能绑定不同的字段,做是否 remove 动作。好了,又开始 bb 了,贴代码吧,在 libs 定义一个 hasPermission.js 文件:
const hasPermission = {install (Vue, options) {
Vue.directive('hasPermission', {inserted (el, binding, vnode) {
let permissionList = vnode.context.$route.meta.access;
if (!permissionList.includes(binding.value)) {el.remove()
}
}
});
}
};
export default hasPermission;
在 main.js 里全局定义:
import hasPermission from '@/libs/hasPermission.js'
Vue.use(hasPermission)
页面中具体按钮的使用:
<Icon type="md-cloud-download" v-hasPermission="'output'" @click="exportTaskExcel" />
注意:绑定的字段必须是字符串格式。
具体页面上的按钮权限的分配在前端页面是怎么控制的,完全可以去 xBoot 里借鉴。
我也不知道我写的大家看不看得懂,如果看不懂,再多看一遍,再看不懂欢迎留言或者加我 QQ 互相学习:602353272。
最后,再 BB 一句,有巨佬有更好的方案欢迎赐教!