NavMenu第一篇的几个例子,固定的菜单中菜单层级是在<el-menu>只写死的,动态菜单权限中层级也是由后台代码排好的(父子关系),到前端接收的menuList可以直接循环渲染了。现在后端只传回有权限的菜单编码,层级关系放在前端路由配置,然后根据路由与后端返回的菜单编码过滤用户权限。

一、多级路由:之前的路由均为二级路由(当然也可以写成多级路由),如果需要配置多级路由,嵌套多层children就可以了,其次要注意component。最外层component指向导航页即NavMenu菜单页面,其他的父级路由component指向一个空的路由跳转页面(否则会出现多个菜单)。如三级菜单:

children:[
{
  path: '/my/user',
  name: 'myUser',
  meta: {
    title: '用户',
    noControl: true,
    icon: 'el-icon-help',
  },
  component: MultiLevelMenu,
  children: [
    {
      path: '/my/user1',
      name: 'user1',
      meta: {
        title: '用户1页面',
        noControl: true,
        showAlways: true
      },
      component: MultiLevelMenu,
      children: [
        {
          path: '/my/user1/user11',
          name: 'overview',
          meta: {
            title: '用户11页面',
            headerConfig: { isStartFlag: true, isUserCenter: true },
          },
          component: resolve => require(['@/views/user/index11'], resolve)
        },
        {
          path: '/my/user1/user12',
          name: 'overview',
          meta: {
            title: '用户12页面',
            headerConfig: { isStartFlag: true, isUserCenter: true },
          },
          component: resolve => require(['@/views/user/index12'], resolve)
        },
      ]
    },
    //详情页面
    {
      path: '/my/userDetail',
      name: 'userDetail',
      meta: {
        title: '详情',
        headerConfig: { isStartFlag: true, isUserCenter: true },
        noControl: true,
        hideInMenu: true,
        isDetailPage: true,
      },
      component: resolve => require(['@/views/user/detail/index'], resolve)
    }
  ]
}
]

空页面代码:

<!-- @format -->

<template>
  <keep-alive><router-view></router-view></keep-alive>
</template>

<script>
export default {
  name: 'midMenu'
};
</script>

<style scoped></style>

二、动态权限:后端返回有权限的菜单编码,前端匹配权限和菜单层级关系。

 

代码:

1、route路由:

这里我把新增的navbar菜单新建了一个asyncCodeMenu目录

(1)asyncCodeMenu/index.js:

/** @format */
//导航页
import MenuIndex from '@/views/asyncCodeNavbar';
//用户菜单
import UserMenu from './userMenu';
import SchoolMenu from './schoolMenu';

export default [
  {
    // 进入平台后的默认首页
    path: '/asyncCodeNavbar',
    name: 'asyncCodeNavbar',
    code:'asyncCodeNavbar',
    meta: {
      title: '菜单',
      noControl: true,
      hideInMenu: true,
      
    },
    redirect: '/asyncCodeNavbar/main',
    component: MenuIndex,
    children: [
      {
        path: '/asyncCodeNavbar/main',
        name: 'asyncCodeNavbarMain',
        code:'asyncCodeNavbarMain',
        meta: {
          title: '首页',
          noControl: true,
          hideInMenu: true,
          
        },
        component: resolve => require(['@/views/asyncCodeNavbar/main/index'], resolve)
      },
      {
        path: '/asyncCodeNavbar/address',
        name: 'asyncCodeNavbarAddress',
        code:'asyncCodeNavbarAddress',
        meta: {
          title: '地址',
          icon: 'el-icon-check',
        },
        component: resolve => require(['@/views/asyncCodeNavbar/address/index'], resolve)
      },
      UserMenu,SchoolMenu
    ]
  }
];

(2)asyncCodeMenu/schoolMenu.js:

/** @format */
import MultiLevelMenu from '@/views/asyncCodeNavbar/multiLevelMenu';
export default {
  path: '/asyncCodeNavbar/school',
  name: 'asyncCodeNavbarSchoolManage',
  code:'asyncCodeNavbarSchoolManage',
  meta: {
    title: '学校',
    noControl: true,
    icon: 'el-icon-check',
    showAlways: true
  },
  component: MultiLevelMenu,
  children: [
    {
      path: '/asyncCodeNavbar/schoolInfo',
      name: 'asyncCodeNavbarSchoolInfo',
      code:'asyncCodeNavbarSchoolInfo',
      meta: {
        title: '学校管理',
        noControl: true,
        showAlways: true
      },
      component: MultiLevelMenu,
      children: [
        {
          path: '/asyncCodeNavbar/schoolInfo/school1',
          name: 'asyncCodeNavbarSchool1',
          code:'asyncCodeNavbarSchool1',
          meta: {
            title: '学校1',
          },
          component: resolve => require(['@/views/asyncCodeNavbar/schoolManage/schoolInfo/school1/index'], resolve)
        },
        {
            path: '/asyncCodeNavbar/schoolInfo/school2',
            name: 'asyncCodeNavbarSchool2',
            code:'asyncCodeNavbarSchool2',
            meta: {
              title: '学校2',
            },
            component: resolve => require(['@/views/asyncCodeNavbar/schoolManage/schoolInfo/school2/index'], resolve)
          },
      ]
    },
    //详情页面
  ]
};

 (3)asyncCodeMenu/userMenu.js:

/** @format */
import MultiLevelMenu from '@/views/asyncCodeNavbar/multiLevelMenu';
export default {
  // 用户信息
  path: '/asyncCodeNavbar/user',
  name: 'asyncCodeNavbarUserInfo',
  code:'asyncCodeNavbarUserInfo',
  meta: {
    title: '用户信息',
    noControl: true,
    icon: 'el-icon-check',
    showAlways: true
  },
  component: MultiLevelMenu,
  children: [
    {
      path: '/asyncCodeNavbar/user/user1',
      name: 'asyncCodeNavbarUser1',
      code:'asyncCodeNavbarUser1',
      meta: {
        title: '用户信息1',
      },
      component: resolve => require(['@/views/asyncCodeNavbar/user/user1/index'], resolve)
    },
    {
        path: '/asyncCodeNavbar/user/user2',
        name: 'asyncCodeNavbarUser2',
        code:'asyncCodeNavbarUser2',
        meta: {
          title: '用户信息2',
        },
        component: resolve => require(['@/views/asyncCodeNavbar/user/user2/index'], resolve)
      },
    //详情页面
    {
      path: '/asyncCodeNavbar/user/userDetail',
      name: 'asyncCodeNavbarUserDetail',
      code:'asyncCodeNavbarUserDetail',
      meta: {
        title: '用户详情',
        noControl: true,
        hideInMenu: true,
        isDetailPage: true,
      },
      component: resolve => require(['@/views/asyncCodeNavbar/user/user1/detail/index'], resolve)
    }
  ]
};

(4)引用到router/index.js下:import AsyncCodeNavbar from './asyncCodeMenu/index'

import Vue from 'vue'
import Router from 'vue-router'

// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading

Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'
import AsyncCodeNavbar from './asyncCodeMenu/index'

export const constantRouterMap = [
  { path: '/404', component: () => import('@/views/404'), hidden: true },
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  {
    path: '/midtest',
    name: 'midtest',
    hidden:true,
    component: () => import('@/views/midtest/index'),
    children: [
      {
        path: '/midtest',
        redirect: 'midtest1'
      },
     
      {
        path: '/midtest/midtest1',
        name: 'midtest1',
        component: () => import('@/views/midtest/midtest1')
      },
      {
        path: '/midtest/midtest2',
        name: 'midtest2',
        component: () => import('@/views/midtest/midtest2')
      },
      {
        path: 'midtest3',
        name: 'midtest3',
        component: () => import('@/views/midtest/midtest3')

      },
      {
        path: 'midtest41',
        name: 'midtest41',
        component: () => import('@/views/midtest/midtest4/midtest41')

      },
      {
        path: 'midtest42',
        name: 'midtest42',
        component: () => import('@/views/midtest/midtest4/midtest42')

      },
      {
        path: 'midtest51',
        name: 'midtest51',
        component: () => import('@/views/midtest/midtest5/midtest51')

      },
      {
        path: 'midtest521',
        name: 'midtest521',
        component: () => import('@/views/midtest/midtest5/midtest521')

      },
      {
        path: 'midtest522',
        name: 'midtest522',
        component: () => import('@/views/midtest/midtest5/midtest522')

      }
    ]
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: '首页',
    icon: '首页',
    hidden: true,
    children: [{
      path: '/dashboard',
      component: () => import('@/views/dashboard/index')
    }]
  },
  ...AsyncCodeNavbar
]

export const asyncRouterMap = [
  // { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  {
    path: '/roleManage',
    redirct: '/roleManage/index',
    name: 'roleManage',
    component: Layout,
    meta: { title: '角色管理', authority: ['role_manage'] },
    noDropdown: true,
    children: [
      {
        path: 'index',
        name: 'index',
        component: () => import('@/views/roleManage/index'),
        meta: { title: '首页', authority: ['role_manage'], keepAlive: false }
      },
      {
        path: 'detail',
        name: 'detail',
        // hidden: true,
        component: () => import('@/views/roleManage/detail'),
        meta: { title: '详情', authority: ['role_manage'], keepAlive: false }
      }
    ]
  },
  {
    path: '/userManage',
    redirct: '/userManage/index',
    name: 'userManage',
    component: Layout,
    meta: { title: '用户管理', authority: ['user_manage'] },
    noDropdown: true,
    children: [
      {
        path: 'index',
        name: 'index',
        component: () => import('@/views/userManage/index'),
        meta: { title: '用户管理', authority: ['user_manage'], keepAlive: false }
      }
    ]
  },
  //分页查询
  {
    path: '/userPager',
    redirct: '/pager/userpager',
    name: 'userpager',
    component: Layout,
    meta: { title: '用户分页', authority: ['user_manage'] },
    noDropdown: true,
    children: [
      {
        path: 'index',
        name: 'index',
        component: () => import('@/views/pager/userpager'),
        meta: { title: '用户分页', authority: ['user_manage'], keepAlive: false }
      }
    ]
  },
  //...其他权限菜单...
]



export default new Router({
  // mode: 'history', // 后端支持可开
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

2、页面:

(1)导航页:

<!-- @format -->
<template>
  <el-container class="container">
    <el-aside class="aside" width="200px">
      <el-header style="height:50px">
      </el-header>
      <el-menu
        style="width:200px"
        :default-active="$route.path"
        router
        class="el-menu-vertical-demo"
        @open="handleOpen"
        @close="handleClose"
        background-color="#538dd5"
        text-color="#FFFFFF"
        active-text-color="#7d9ec6"
      >
        <div v-for="item in menuList" :key="item.id">
          <!--无子菜单-->
          <el-menu-item :index="item.path" v-if="isNoChildrenMenuShow(item)" class="menuname">
            <i :class="item.meta.icon"></i>
           
            {{ item.meta.title }}
          </el-menu-item>
          <!--有二级菜单-->
          <el-submenu v-if="item.children && item.children.length > 0" :index="item.id">
            <template slot="title">
              <i :class="item.meta.icon"></i>
              <span class="menuname">{{ item.meta.title }}</span>
            </template>
            <!--是否有三级菜单-->
            <div v-for="second in item.children" :key="second.id">
              <!--无三级菜单-->
              <el-menu-item :index="second.path" v-show="isNoChildrenMenuShow(second)" class="mini-menuname">
                <i :class="second.meta.icon"></i><span>{{ second.meta.title }}</span>
              </el-menu-item>
              <!--有三级菜单-->
              <el-submenu v-show="second.children && second.children.length > 0" :index="second.id" class="menuname">
                <template slot="title"> <i :class="second.meta.icon"></i>{{ second.meta.title }} </template>
                <el-menu-item
                  :index="third.path"
                  v-for="third in second.children"
                  :key="third.id"
                  class="mini-menuname"
                >
                  <i :class="third.meta.icon"></i>{{ third.meta.title }}
                </el-menu-item>
              </el-submenu>
            </div>
          </el-submenu>
        </div>
      </el-menu>
    </el-aside>

    <el-container>
      <el-header class="main-header" height="30px">
       <div>顶部</div>
      </el-header>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import menus from '@/router/asyncCodeMenu/index.js'
export default {
  data() {
    return {
      menuList: []
    };
  },
  created() {
      //这里省略接口请求,menuCodes为接口返回的权限
    let menuCodes = [
        'asyncCodeNavbarAddress',
        'asyncCodeNavbarSchool2',
        'asyncCodeNavbarUser2',
        'asyncCodeNavbarSchoolManage'
      ];
      this.menuList = this.filterAsyncRouter(menus, menuCodes);
      console.info('菜单' + JSON.stringify(this.menuList));
      console.info(this.menuList);
  
  },
  methods: {
    /**
     * 权限过滤
     * routes所有路由
     * userMenus 用户权限集合,菜单编码
     */
    filterAsyncRouter(routes, userMenus) {
      let res = [];
      for (let index = 0; index < routes.length; index++) {
        let route = routes[index];
        if (!this.hasPermission(userMenus, route)) {
          continue;
        }
        //有权限
        //1、无子菜单
        if (!route.children) {
          const tmp = { ...route };
          res.push(tmp);
          continue;
        }
        //2、有子菜单
        //2.1、父菜单是否需要展示
        if (!route.meta.hideInMenu) {
          const tmp = { ...route };
          tmp.children = this.filterAsyncRouter(route.children, userMenus);
          res.push(tmp);
          continue;
        }
        //2.2、父菜单不展示,直接处理子菜单
        route.children.forEach(child => {
          let tmp = { ...child };
          tmp = this.filterAsyncRouter(new Array(child), userMenus);
          res = res.concat(tmp);
        });
      }
      return res;
    },
    // hasPermission1(urlList, route) {
    //   //debugger;
    //   if (route.meta.noControl) return true; //该页面不需要权限控制
    //   return urlList.includes(route.path);
    // },
    /**
     * 是否有权限看到
     * userMenus 用户菜单权限集合
     * route 路由对象
     */
    hasPermission(userMenus, route) {
      debugger;
      //1、该路由不需要权限控制,且不是详情页面
      if (route.meta.noControl && !route.meta.isDetailPage) {
        //1.1该路由无子路由,则放开
        if (!route.children || route.children.length === 0) {
          return true;
        }
        //1.2有子路由,且用户权限含有子路由,则可见
        let hasChildPermission = false;
        for (let childIndex = 0; childIndex < route.children.length; childIndex++) {
          hasChildPermission = this.hasPermission(userMenus, route.children[childIndex]);
          if (hasChildPermission) {
            return hasChildPermission;
          }
        }

        return hasChildPermission;
      }
      //2、该路由需要权限控制
      return userMenus.includes(route.code);
    },
    isNoChildrenMenuShow(item) {
      if (!item.children || item.children.length === 0) {
        if (item.meta) {
          return !item.meta.hideInMenu;
        }
        return true;
      }
      return false;
    },
    goToMain() {
      this.$router.push({ name: 'main' });
    },
    goToJgpt() {
      this.$router.push({ name: 'jgpt' });
    },
    goToDashboardHandler() {
      this.$router.replace({ name: 'dashboard' });
    }
  }
};
</script>

<style scoped >
::-webkit-scrollbar {
    width: 1px;
  }

  ::-webkit-scrollbar-track {
    --webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
    border-radius: 3px;
  }

  ::-webkit-scrollbar-thumb {
    background: #538dd5;
    --webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
  }

  ::-webkit-scrollbar-thumb:window-inactive {
    background: #538dd5;
  }
.container {
  height: 100%;
}
.aside {
    height: 100%;
    background-color: #538dd5;
}
  .el-header {
      padding: 0px;
    }
    .header.white {
      background-color: #538dd5;
    }
    .el-icon-right {
      font-size: 16px;
    }
    i {
      color: #ffffff;
      font-size: 16px;
    }
    .el-menu-item.is-active {
      background-color: #366092 !important;
     
    }
  
  .main-header {
    padding: 0px;
    padding-right: 10px;
   
  }
   .btn-txt {
      height: 100%;
      line-height: 30px;
      font-size: 16px;
      float: right;
      padding-left: 15px;
      text-decoration: none;
      padding-top: 0px;
      font-weight: bold;
    }
  .el-main {
    padding: 20px;
    padding-top: 0px;
  }

  .menuname {
    color: #ffffff;
    font-size: 16px;
  }

  .mini-menuname {
    color: #ffffff;
    font-size: 12px;
  }

</style>

 (2)默认页面:/main/index

<template>
    <div>首页</div>
</template>

(3)用户页:

<template>
    <div>用户2-二级菜单
        <el-button @click="goToDetail">详情</el-button>
    </div>
</template>
<script>
export default {
    methods:{
        goToDetail(){
            this.$router.push({path:'/asyncCodeNavbar/user/userDetail'})
        }
    }
}
</script>

(4)空路由页:/multiLevelMenu/index

<!-- @format -->

<template>
  <keep-alive><router-view></router-view></keep-alive>
</template>

<script>
export default {
  name: 'mid'
};
</script>

<style scoped></style>

三、测试:如果不设置中间空路由,如

则点击用户菜单会出现菜单嵌套 

如果空路由改成具体的页面,如

 则点击用户菜单也跳转到该页

Logo

前往低代码交流专区

更多推荐