一、后端采用springboot+mybatis-plus。改造自macrozheng/mall-tiny(https://github.com/macrozheng/mall-tiny,他这边是前端需要在router/index.js中配置路由信息,然后从数据库查询路由信息,进行匹配后,再显示出来,需要前后端匹配的。)
1、数据库表

整体表结构(resource不相关,可不用)

elementui动态菜单的前后端如何设置 vue element template动态菜单_ico


菜单表

elementui动态菜单的前后端如何设置 vue element template动态菜单_vue_02


2、部分代码

菜单表对于的实体SysMenu:

package com.jc.pms.modules.sys.model;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;

import com.jc.pms.modules.sys.dto.SysMenuMeta;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 后台菜单表
 * </p>
 *
 * @author macro
 * @since 2020-08-21
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_menu")
@ApiModel(value="SysMenu对象", description="后台菜单表")
public class SysMenu implements Serializable {

    private static final long serialVersionUID=1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "父级ID")
    private Long parentId;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "菜单名称")
    private String title;

    @ApiModelProperty(value = "菜单级数")
    private Integer level;

    @ApiModelProperty(value = "菜单排序")
    private Integer sort;

    @ApiModelProperty(value = "前端组件名称")
    private String name;

    @ApiModelProperty(value = "前端图标")
    private String icon;

    @ApiModelProperty(value = "前端隐藏")
    private Integer hidden;

    @ApiModelProperty(value = "访问路径")
    private String path;

    @ApiModelProperty(value = "组件路径")
    private String component;

    @ApiModelProperty(value = "菜单类型")
    private Integer type;

    @ApiModelProperty(value = "状态")
    private Integer status;

//前端用以图标和面包屑
    @TableField(exist = false)
    private JSONObject meta;

    public JSONObject getMeta() {
        return Meta(this.getTitle(),this.getIcon());
    }

    //为meta构建JSONObejct对象值,前端用以图标和面包屑
    public JSONObject Meta(String title, String icon){
        SysMenuMeta menuMeta= new SysMenuMeta();
        menuMeta.setIcon(icon);
        menuMeta.setTitle(title);
        return JSONUtil.parseObj(menuMeta, true, true);
    }
}

上面用到的SysMenuMeta对象:

package com.jc.pms.modules.sys.dto;

import lombok.Data;

@Data
public class SysMenuMeta {
    private String icon;
    private String title;

}
********

下面用到的SysMenuNode子菜单对象:

package com.jc.pms.modules.sys.dto;

import com.jc.pms.modules.sys.model.SysMenu;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
 * 后台菜单节点封装
 * Created by macro on 2020/2/4.
 */
@Getter
@Setter
public class SysMenuNode extends SysMenu {
    @ApiModelProperty(value = "子级菜单")
    private List<SysMenuNode> children;
}

SysMenuServiceImpl中关于MenuTree获取的代码:

@Override
public List<SysMenuNode> treeList() {
    List<SysMenu> menuList = list();//此处默认查所有的,可以改成根据用户id去查菜单,或者用户role去查询菜单,mapper里面配置自己写下
    List<SysMenuNode> result = menuList.stream()
            .filter(menu -> menu.getParentId().equals(0L))
            .map(menu -> covertMenuNode(menu, menuList)).collect(Collectors.toList());
    return result;
}

* 将UmsMenu转化为UmsMenuNode并设置children属性
 */
private SysMenuNode covertMenuNode(SysMenu menu, List<SysMenu> menuList) {
    SysMenuNode node = new SysMenuNode();
    BeanUtils.copyProperties(menu, node);
    List<SysMenuNode> children = menuList.stream()
            .filter(subMenu -> subMenu.getParentId().equals(menu.getId()))
            .map(subMenu -> covertMenuNode(subMenu, menuList)).collect(Collectors.toList());
    node.setChildren(children);
    return node;
}

最终需要的menu数据json:

{"code":200,"message":"操作成功","data":[{"id":21,"parentId":0,"createTime":"2020-02-07T08:29:13.000+00:00","title":"系统管理","level":0,"sort":0,"name":"","icon":"ums","hidden":0,"path":"/ums","component":"Layout","type":1,"status":0,"meta":{"icon":"ums","title":"系统管理"},"children":[{"id":22,"parentId":21,"createTime":"2020-02-07T08:29:51.000+00:00","title":"用户列表","level":1,"sort":0,"name":"adminList","icon":"ums-admin","hidden":0,"path":"/ums/admin","component":"ums/admin/index","type":1,"status":0,"meta":{"icon":"ums-admin","title":"用户列表"},"children":[]},{"id":23,"parentId":21,"createTime":"2020-02-07T08:30:13.000+00:00","title":"角色列表","level":1,"sort":0,"name":"roleList","icon":"ums-role","hidden":0,"path":"/ums/role","component":"ums/role/index","type":1,"status":0,"meta":{"icon":"ums-role","title":"角色列表"},"children":[]},{"id":24,"parentId":21,"createTime":"2020-02-07T08:30:53.000+00:00","title":"菜单列表","level":1,"sort":0,"name":"menuList","icon":"ums-menu","hidden":0,"path":"/ums/menu","component":"ums/menu/index","type":1,"status":0,"meta":{"icon":"ums-menu","title":"菜单列表"},"children":[]},{"id":25,"parentId":21,"createTime":"2020-02-07T08:31:13.000+00:00","title":"资源列表","level":1,"sort":0,"name":"resourceList","icon":"ums-resource","hidden":0,"path":"/ums/resource","component":"ums/resource/index","type":1,"status":0,"meta":{"icon":"ums-resource","title":"资源列表"},"children":[]}]}]}

二、前端是vue-element-template
1、跨域、api之类的配置省略。
2、store/modules/user.js增加一个存储菜单信息的menus字段和获取菜单json信息的函数。(获取用户信息跟菜单信息分开来)

// get router info
getAntRouter({ commit, state }) {
  return new Promise((resolve, reject) => {
    dongtRouter().then(response => {
      const { data } = response
      if (!data) {
        reject('验证失败,请重新登录')
      }
      const menus = data
      console.log(menus)
      menus.push({
        path: '/404',
        component: '404',
        hidden: true
      }, {
        path: '*',
        redirect: '/404',
        hidden: true
      })
      commit('SET_MENUS', menus)
      resolve(data)
    }).catch(error => {
      reject(error)
    })
  })
},

3、src/permission.js 直接贴出来:

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import Layout from '@/layout'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login'] // no redirect whitelist
const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法
router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      // determine whether the user has obtained his permission roles through getInfo
      // const hasRoles = store.getters.roles && store.getters.roles.length > 0
      // if (hasRoles) {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // 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)
          await store.dispatch('user/getInfo') // 请求获取用户信息
          await store.dispatch('user/getAntRouter') // 请求获取路由信息
          if (store.getters.menus.length < 1) {
            global.antRouter = []
            next()
          }
          const menus = filterAsyncRouter(store.getters.menus) // 1.过滤路由
          console.log(menus)

          router.addRoutes(menus) // 2.动态添加路由
          global.antRouter = menus // 3.将路由数据传递给全局变量,做侧边栏菜单渲染工作
          next({
            ...to,
            replace: true
          })

          // 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 })
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error({ message: error || 'Has Error' })
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

// // 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') {
        route.component = Layout
      } else {
        route.component = _import(route.component) // 导入组件
      }
    }
    if (route.hidden) {
      // eslint-disable-next-line eqeqeq
      if (route.hidden == '1') {
        route.hidden = false
        // eslint-disable-next-line eqeqeq
      } else if (route.hidden == '0') {
        route.hidden = true
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })

  return accessedRouters
}

4、layout/components/sidebar/index.vue略改。

<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'


export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      // 'permission_routes',
      'sidebar'
    ]),
    routes() {
      // return this.$router.options.routes
      return this.$router.options.routes.concat(global.antRouter) // 把路由concat进去
    },
    activeMenu() {
      const route = this.$route
      const { meta, path } = route
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

elementui动态菜单的前后端如何设置 vue element template动态菜单_List_03

ok了。后端菜单信息动态获取,生成前端菜单路由。不需要再在前端配置权限菜单的路由信息了。注意不要忘记要把菜单对应的文件建起来。