vue-element-template动态菜单获取全过程

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

整体表结构(resource不相关,可不用)vue-element-template动态菜单获取全过程_第1张图片
菜单表

vue-element-template动态菜单获取全过程_第2张图片
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;

/**
 * 

* 后台菜单表 *

* * @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>

vue-element-template动态菜单获取全过程_第3张图片

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

你可能感兴趣的:(java,javaweb,vue,vue,spring,boot)