SpringBoot2.3整合vue-admin-template实现动态路由

1. 概述

本篇是SpringBoot整合vue-admin-template的第二篇,需要了解SpringBoot如何整合vue-admin-template实现数据库登录认证的小伙伴,请参阅SpringBoot整合vue-admin-template实现登录,本文将主要介绍SpringBoot整合vue-admin-template实现从后台数据实时查询数据动态加载菜单,

2. 前端代码实现

2.1. router/index.js

删除constantRoutes中多余路由配置,只保留必要的,新增asyncRoutes路由用于加载从后台数据库获取的动态数据,新增exceptionRoutes路由用于处理错误页面

export const asyncRoutes = [

]
export const exceptionRoutes = [
  {
    
    
    path: '*',
    redirect: '/404',
    hidden: true
  }
]

2.2. api/menu.js

在api目录下新建menu.js文件,用于获取后台菜单数据

import request from '@/utils/request'
export function getRoutes() {
    
    
  return request({
    
    
    url: '/getRoutes',
    method: 'get'
  })
}

2.3. store/modules/permission.js

在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
}

2.4. store/index.js

在store目录下index.js文件中添加permission引用

...
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
    
    
  modules: {
    
    
    ...
    permission
  },
  getters
})
export default store

2.5. src/permission.js

将动态路由挂载到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()
  }
}

2.6. src/modules/user.js

新增角色信息,完整代码如下

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
}

2.7. store/getters.js

增加角色配置

roles: state => state.user.roles

3. 后端代码实现

3.1. 控制层代码

@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);
}

3.2. 业务层代码

@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;
}

3.3. 路由数据

[
	{
    
    
		"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"
				}
			}
		]
	}
]

4. 验证

分别使用用户admin和editor登录系统
admin登录
editor登录

猜你喜欢

转载自blog.csdn.net/liu320yj/article/details/120524268