本篇是SpringBoot整合vue-admin-template的第二篇,需要了解SpringBoot如何整合vue-admin-template实现数据库登录认证的小伙伴,请参阅SpringBoot整合vue-admin-template实现登录,本文将主要介绍SpringBoot整合vue-admin-template实现从后台数据实时查询数据动态加载菜单,
删除constantRoutes中多余路由配置,只保留必要的,新增asyncRoutes路由用于加载从后台数据库获取的动态数据,新增exceptionRoutes路由用于处理错误页面
export const asyncRoutes = [
]
export const exceptionRoutes = [
{
path: '*',
redirect: '/404',
hidden: true
}
]
在api目录下新建menu.js文件,用于获取后台菜单数据
import request from '@/utils/request'
export function getRoutes() {
return request({
url: '/getRoutes',
method: 'get'
})
}
在store/modules目录下新建permission文件
import { asyncRoutes, constantRoutes, exceptionRoutes } from '@/router'
import { getRoutes } from '@/api/menu'
import Layout from '@/layout/index'
/**
* 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
}
}
export function generateMenus(routes, data, is_children = false) {
data.forEach(item => {
const menu = {
path: item.path,
component: item.component === 'Layout' ? Layout : resolve => require([`@/views/${item.component}`], resolve),
name: item.name,
meta: item.meta
}
if (is_children === false) {
menu.children = []
menu.redirect = item.redirect
}
if (item.children && is_children === false) {
generateMenus(menu.children, item.children, true)
}
routes.push(menu)
})
}
/**
* 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
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
const loadMenuData = []
getRoutes().then(res => {
Object.assign(loadMenuData, res.data)
const tmpAsyncRoutes = Object.assign([], asyncRoutes)
generateMenus(tmpAsyncRoutes, loadMenuData)
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = tmpAsyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(tmpAsyncRoutes, roles)
}
accessedRoutes = accessedRoutes.concat(exceptionRoutes)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
})
}
}
export const loadView = (view) => { // 路由懒加载
return (resolve) => require([`@/views/${view}`], resolve)
}
export default {
namespaced: true,
state,
mutations,
actions
}
在store目录下index.js文件中添加permission引用
...
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
...
permission
},
getters
})
export default store
将动态路由挂载到src目录下permission文件中
// const hasGetUserInfo = store.getters.name
const hasGetUserInfo = store.getters.roles && store.getters.roles.length > 0
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
// await store.dispatch('user/getInfo')
const { roles } = await store.dispatch('user/getInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
router.options.routes = constantRoutes.concat(accessRoutes)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
// next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
新增角色信息,完整代码如下
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
roles: []
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar, roles } = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_ROLES', roles)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
// commit('RESET_STATE')
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
// commit('RESET_STATE')
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
resolve()
})
},
async changeRoles({ commit, dispatch }, role) {
const token = state.token
commit('SET_TOKEN', token)
setToken(token)
const { roles } = await dispatch('getInfo')
resetRouter()
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
router.addRoutes(accessRoutes)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
增加角色配置
roles: state => state.user.roles
@GetMapping("/getRoutes")
public ResultData getRoutes() {
SysUserInfo userInfo = loginService.getUserInfo();
List<SysMenuInfo> menuInfos = menuInfoService.selectMenuTreeByUserId(userInfo.getId());
List<RouterMenu> routerMenus = menuInfoService.buildMenus(menuInfos);
return ResultData.success().data(routerMenus);
}
@Override
public List<SysMenuInfo> selectMenuTreeByUserId(Long userId) {
List<SysMenuInfo> menuInfoList = menuInfoMapper.selectMenuTreeByUserId(userId);
return getChildPerms(menuInfoList, 0);
}
/**
* 构建前端路由所需的菜单
* @param menuInfos
* @return
*/
@Override
public List<RouterMenu> buildMenus(List<SysMenuInfo> menuInfos) {
List<RouterMenu> routerMenus = new LinkedList<>();
for (SysMenuInfo menuInfo : menuInfos) {
RouterMenu routerMenu = new RouterMenu();
//routerMenu.setHidden(menuInfo.getVisible().intValue() == 1);
routerMenu.setName(getRouteName(menuInfo));
routerMenu.setPath(getRoutePath(menuInfo));
routerMenu.setComponent(getComponent(menuInfo));
routerMenu.setMeta(new MetaMenu(menuInfo.getMenuName(), menuInfo.getIcon()));
List<SysMenuInfo> childMenus = menuInfo.getChildren();
if (!childMenus.isEmpty() && childMenus.size() > 0 && "M".equals(menuInfo.getMenuType())) {
routerMenu.setChildren(buildMenus(childMenus));
}
routerMenus.add(routerMenu);
}
return routerMenus;
}
/**
* 获取路由名称
* @param menuInfo
* @return
*/
private String getRouteName(SysMenuInfo menuInfo) {
String routeName = StringUtils.capitalize(menuInfo.getPath());
if (menuInfo.getParentId().longValue() == 0 && "C".equals(menuInfo.getMenuType())) {
routeName = "";
}
return routeName;
}
/**
* 获取路由地址
* @param menuInfo
* @return
*/
private String getRoutePath(SysMenuInfo menuInfo) {
String routePath = menuInfo.getPath();
if (menuInfo.getParentId().longValue() == 0 && "M".equals(menuInfo.getMenuType())) {
routePath = "/" + menuInfo.getPath();
} else if (menuInfo.getParentId().longValue() == 0 && "C".equals(menuInfo.getMenuType())) {
routePath = "/";
}
return routePath;
}
/**
* 获取组件
* @param menuInfo
* @return
*/
private String getComponent(SysMenuInfo menuInfo) {
String component = "Layout";
if (StringUtils.hasLength(menuInfo.getComponent())) {
component = menuInfo.getComponent();
}
return component;
}
/**
* 根据父节点ID获取子节点
* @param menuInfoList
* @param parentId
* @return
*/
private List<SysMenuInfo> getChildPerms(List<SysMenuInfo> menuInfoList, int parentId) {
List<SysMenuInfo> menuInfos = new ArrayList<>();
for (Iterator<SysMenuInfo> iterator = menuInfoList.iterator(); iterator.hasNext(); ) {
SysMenuInfo menuInfo = iterator.next();
if (menuInfo.getParentId() == parentId) {
recursionFn(menuInfoList, menuInfo);
menuInfos.add(menuInfo);
}
}
return menuInfos;
}
/**
* 递归获取菜单列表
* @param menuInfoList
* @param menuInfo
*/
private void recursionFn(List<SysMenuInfo> menuInfoList, SysMenuInfo menuInfo) {
List<SysMenuInfo> childList = getChildList(menuInfoList, menuInfo);
menuInfo.setChildren(childList);
for (SysMenuInfo childMenu : childList) {
if (hasChild(menuInfoList, childMenu)) {
recursionFn(menuInfoList, childMenu);
}
}
}
/**
* 判断是否有子节点
* @param menuInfoList
* @param childMenu
* @return
*/
private boolean hasChild(List<SysMenuInfo> menuInfoList, SysMenuInfo childMenu) {
return getChildList(menuInfoList, childMenu).size() > 0 ? true : false;
}
/**
* 获取子节点列表
* @param menuInfoList
* @param menuInfo
* @return
*/
private List<SysMenuInfo> getChildList(List<SysMenuInfo> menuInfoList, SysMenuInfo menuInfo) {
List<SysMenuInfo> childList = new ArrayList<>();
Iterator<SysMenuInfo> iterator = menuInfoList.iterator();
while (iterator.hasNext()) {
SysMenuInfo nextMenu = iterator.next();
if (nextMenu.getParentId().longValue() == menuInfo.getId().longValue()) {
childList.add(nextMenu);
}
}
return childList;
}
[
{
"name": "System",
"path": "/system",
"component": "Layout",
"meta": {
"title": "系统管理",
"icon": "system"
},
"children": [
{
"name": "User",
"path": "user",
"component": "system/user/index",
"meta": {
"title": "用户管理",
"icon": "user"
}
},
{
"name": "Role",
"path": "role",
"component": "system/role/index",
"meta": {
"title": "角色管理",
"icon": "peoples"
}
},
{
"name": "Menu",
"path": "menu",
"component": "system/menu/index",
"meta": {
"title": "菜单管理",
"icon": "tree-table"
}
},
{
"name": "Dict",
"path": "dict",
"component": "system/dict/index",
"meta": {
"title": "字典管理",
"icon": "dict"
}
}
]
}
]