vue-element-admin 这个项目路由最初是配置在 src/route/index.js 文件中,再根据src/store/modules/permission.js#generateRoutes 方法按角色过滤出对应的路由并动态挂载。
我希望有个新效果:服务器根据系统用户拥有的角色获得对应的菜单路由,再去动态挂载路由。
这样不同角色的用户登录系统看到的侧边栏就不同,日后新增页面等操作只需要操作数据库相关表而不需要修改路由文件。
基本思路:
我这里 根据用户查询路由中的路由已经是过滤的,也就是说服务器返回的不是完整的路由是已经根据角色过滤好的!你想返回完整菜单由前端过滤也是可行的
后端路由表一条记录形式为(
路径 | 组件 | sort | title | parent | …… |
---|---|---|---|---|---|
/system | Layout | 1 | 系统页面 | 0 | …… |
路由表字段看着src/router/index 文件设置字段就行了,父子组件的判断通过parent这个字段,为0说明自己是父组件(layout),非0值存的就是父组件的id
在后端参考
public class JobMenuServiceImpl implements JobMenuService {
@Resource
PermissionMapper permissionMapper;
@Override
public List<JobMenu> transferMenu2RouterJSON(String role_key) {
// 根据角色获取对应的菜单路由
List<JobMenu> jobMenusList = permissionMapper.getUserMenuListByRole(role_key);
return jobMenusList.stream()
.filter(menu -> menu.getParent() == 0)
.peek(menu -> menu.setChildren(getChildren(menu, jobMenusList)))
.sorted(Comparator.comparing(JobMenu::getSort))
.collect(Collectors.toList());
}
public List<JobMenu> getChildren(JobMenu root, List<JobMenu> allMenus) {
return allMenus.stream()
.filter(menu -> isIDEqual(menu.getParent(), root.getId()))
.peek(menu -> menu.setChildren(getChildren(menu, allMenus)))
.sorted(Comparator.comparing(JobMenu::getSort))
.collect(Collectors.toList());
}
public boolean isIDEqual(int a, int b) {
return (a == b);
}
}
jobmenu 这个entity在数据库没有children字段,所以xml里记得自行限制下select列
@Data
public class JobMenu {
private int id;
private int parent;
private int sort;
private String path;
private String title;
private String name;
private String component;
private int type;
private String permission;
private String icon;
private int hidden;
private int nocache;
private List<JobMenu> children;
}
xml参考,roleKey就是前端传来的roles
<mapper namespace="com.wugui.datax.admin.mapper.JobMenuMapper">
<sql id="base_column">
jm.id,
jm.parent,
jm.sort,
jm.path,
jm.title,
jm.name,
jm.component,
jm.icon,
jm.hidden,
jm.nocache
</sql>
<select id="getMenusByRouteRole" parameterType="java.util.HashMap" resultType="com.wugui.datax.admin.entity.JobMenu">
select <include refid="base_column"/>
from job_role jr
left join job_role_menu jrm on jr.id=jrm.role_id
left join job_menu jm on jm.id=jrm.menu_id
<where>
<if test="roleKey!=null and roleKey!=''">
jr.role_key = #{roleKey}
</if>
</where>
</select>
</mapper>
原本的代码:
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
//roles如果返回多个权限,需要过滤出我们想要的。能确保只有一个role就不需要该步骤!
// join将数组元素放到字符串中!
const routeRoleArray = [‘ROLE_USER’, ‘ROLE_ADMIN’]
const currentUserRouteRole = roles.filter(item => { return routeRoleArray.includes(item)}.join()
假定 服务器可以通过 roles 常量 返回对应菜单记录。此时menus是已经根据角色过滤好的!
const menus = await store.dispatch(‘user/getUserMenuListByRole’, roles)
我们根据返回结果生成vuex能识别的路由数据(前端组装)
const accessRoutes = await store.dispatch(‘permission/generateRoutes’, { roles, menus })
最终src/permission.js 代码
// get user info
const { roles } = await store.dispatch('user/getInfo')
// 从返回的角色组中过滤出目录角色
const currentUserRouteRole = roles.filter(item => {
return routeRoleArray.includes(item)
}).join()
// 后端根据角色获取原始菜单列表
const menus = await store.dispatch('user/getUserMenuListByRole', currentUserRouteRole)
// 根据角色和原始菜单列表生成可访问的路由
// dispatch只有两个参数,大于两个参数用{}对象形式传入
const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, menus })
console.log(accessRoutes)
// 动态添加可访问的路由
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
getUserMenuListByRole方法参考(generateRoutes在下面)
getUserMenuListByRole({ commit }, currentUserRouteRole) {
return new Promise((resolve, reject) => {
getUserMenuListByRole({ 'role_key': currentUserRouteRole }).then(response => {
const menus = response.content
commit('SET_MENUS', menus)
resolve(menus)
}).catch(error => {
reject(error)
})
})
},
1.1 组装路由的逻辑在src/store/modules/permission.js ,generateRoutes 原内容我们直接注释,编写新逻辑
generateRoutes({ commit }, data) {
const { roles, menus } = data // eslint-disable-line no-unused-vars
return new Promise(resolve => {
let accessedRoutes = [] // eslint-disable-line prefer-const
generateMenu(accessedRoutes, menus)
// localStorage.setItem('user_route', JSON.stringify(accessedRoutes))
commit('SET_ROUTES', accessedRoutes)
accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
resolve(accessedRoutes)
})
}
最后accessedRoutes.push 404一定要加上,源项目也说了。generateMenu也是我们新加的方法用来组装路由:
export function generateMenu(routes, menus) {
menus.forEach(item => {
const menu = {
path: item.path,
component: item.component === 'Layout' ? Layout : (resolve) => require([`@/views${item.component}`], resolve),
redirect: '',
name: item.name,
meta: { title: item.title, icon: item.icon },
// 数据库字段hidden 0 隐藏,1 显示 默认显示
hidden: item.hidden === 0,
// 数据库字段nocache 0 不缓存 1 缓存 默认缓存
nocache: item.nocache === 1,
children: []
}
if (item.children && item.children.length > 0) {
menu.redirect = item.children[0].path
generateMenu(menu.children, item.children)
} else {
delete menu.redirect
delete menu.children
}
routes.push(menu)
})
}
注意点:
component: item.component === ‘Layout’ ? Layout : (resolve) => require([
@/views${item.component}
], resolve)
这句需要导入Layout ,同时不能再使用原来的 ()=>import(‘@/views/xx’),需要模板字符串和resolve 解决。
import Layout from ‘@/layout’
完成。
完整的 src/store/modules/permission.js
import { constantRoutes } from '@/router'
// import { asyncRoutes,constantRoutes } from '@/router'
import Layout from '@/layout'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
// 根据后端得到的原始路由json转换成vue可以挂载的路由json
export function generateMenu(routes, menus) {
menus.forEach(item => {
const menu = {
path: item.path,
component: item.component === 'Layout' ? Layout : (resolve) => require([`@/views${item.component}`], resolve),
redirect: '',
name: item.name,
meta: { title: item.title, icon: item.icon },
// 数据库字段hidden 0 隐藏,1 显示 默认显示
hidden: item.hidden === 0,
// 数据库字段nocache 0 不缓存 1 缓存 默认缓存
nocache: item.nocache === 1,
children: []
}
if (item.children && item.children.length > 0) {
menu.redirect = item.children[0].path
generateMenu(menu.children, item.children)
} else {
delete menu.redirect
delete menu.children
}
routes.push(menu)
})
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, data) {
const { roles, menus } = data // eslint-disable-line no-unused-vars
return new Promise(resolve => {
let accessedRoutes = [] // eslint-disable-line prefer-const
generateMenu(accessedRoutes, menus)
localStorage.setItem('user_route', JSON.stringify(accessedRoutes))
commit('SET_ROUTES', accessedRoutes)
accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
resolve(accessedRoutes)
// 如果角色包含管理员字符串 直接添加动态路由
// if (roles.includes('ROLE_ADMIN')) {
// accessedRoutes = asyncRoutes || []
// } else {
// // 否则对动态路由进行过滤
// accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
// }
// commit('SET_ROUTES', accessedRoutes)
// resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}