欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue 实现动态生成路由(动态生成菜单,根据菜单动态生成路由)

程序员文章站 2022-07-07 20:50:40
在后台管理系统中,常常会因为不同的权限,展现不同的菜单。但这仅仅是显示控制,而我们要做的是没有的菜单权限,直接输入URL也不可访问,真正的实现菜单权限控制。一、动态菜单显示1. 后端返回的菜单数据处理后端返回的菜单数据一般有两种,处理好的树状结构、或者未处理的列表数据(这种情况需要我们去转换成树状结构,可看我的另外一篇博客【js树形结构操作】)。下面是后端返回的数据:[ { "id": "1", "pid": "0", "name": "工作台", "url....

在后台管理系统中,常常会因为不同的权限,展现不同的菜单。但这仅仅是显示控制,而我们要做的是没有的菜单权限,直接输入URL也不可访问,真正的实现菜单权限控制。

一、动态菜单显示

1. 后端返回的菜单数据处理

后端返回的菜单数据一般有两种,处理好的树状结构、或者未处理的列表数据(这种情况需要我们去转换成树状结构,可看我的另外一篇博客【js树形结构操作】)。

下面是后端返回的数据:

[
  {
    "id": "1",
    "pid": "0",
    "name": "工作台",
    "url": "/dashboard",
    "icon": "el-icon-s-platform",
    "children": []
  },
  {
    "id": "2",
    "pid": "0",
    "name": "教务管理",
    "icon": "el-icon-s-opportunity",
    "children": [
      {
        "id": "21",
        "pid": "2",
        "name": "学员中心",
        "url": "/educate/student"
      },
      {
        "id": "22",
        "pid": "2",
        "name": "班级管理",
        "url": "/educate/class"
      },
      {
        "id": "23",
        "pid": "2",
        "name": "课程管理",
        "url": "/educate/course"
      },
      {
        "id": "24",
        "pid": "2",
        "name": "课表管理",
        "url": "/educate/table"
      }
    ]
  },
  {
    "id": "3",
    "pid": "0",
    "name": "系统设置",
    "icon": "el-icon-s-opportunity",
    "children": [
      {
        "id": "31",
        "pid": "3",
        "name": "基础信息",
        "url": "/setting/base"
      },
      {
        "id": "32",
        "pid": "3",
        "name": "职员管理",
        "url": "/setting/user"
      },
      {
        "id": "33",
        "pid": "3",
        "name": "岗位管理",
        "url": "/setting/role"
      }
    ]
  },
  ...
]

拿到后端的数据后,使用Vuex管理这些菜单数据。

2. 显示菜单列表

在菜单组件中,使用Vuex的中的菜单数据回显:

<!-- 菜单组件 -->
<el-menu
  class="sidebar-menu"
  router
  :default-active="$route.path"
  :collapse="!sidebar.opened"
  :collapse-transition="false"
  :show-timeout="200"
  unique-opened
  background-color="#272a36"
  text-color="#e7e7e7"
  active-text-color="#ff6600">
  <el-scrollbar wrap-class="scrollbar-wrapper" style="height: 100%;">
    <template v-for="group in menuList">
      <el-submenu v-if="group.children && group.children.length > 0" :key="group.id" :index="group.id">
        <template slot="title">
          <i :class="group.icon"></i>
          <span slot="title">{{group.name}}</span>
        </template>
        <el-menu-item v-for="menu in group.children" :key="menu.id" :index="menu.url">
          <i class="icon iconfont icon-pointer" style="vertical-align: baseline;"/>
          {{menu.name}}
        </el-menu-item>
      </el-submenu>
      <el-menu-item v-else :key="group.id" :index="group.url">
        <i :class="group.icon"></i>
        <span slot="title">{{group.name}}</span>
      </el-menu-item>
    </template>
  </el-scrollbar>
</el-menu>

通过本页面的$route的URL进行菜单匹配,并不是全路径的URL。Vue 实现动态生成路由(动态生成菜单,根据菜单动态生成路由)
至此,通过后端返回的菜单数据动态生成菜单已完成。但是细心的小伙伴会发现,手动输入没有菜单的URL,仍然可以查看显示,这是不安全的,并且菜单栏无匹配项。

二、动态生成路由

1. 把路由分类

路由需要分成两类,静态路由动态路由。静态路由是任何菜单权限下都能查看的界面路由;动态路由是根据菜单权限动态生成的路由集合。这里的动态路由与VueRouter的动态路由概念没有任何关系。
比如:

/** router.js */

/**
 * 静态路由
 */
export const constantRouterMap = [
  {
    path: '/',
    redirect: '/dashboard'
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login')
  },
  {
    path: '/forgetPassword',
    name: 'ForgetPassword',
    component: () => import('@/views/ForgetPassword')
  },
  {
    path: '/register',
    name: 'Register',
    component: () => import('@/views/Register')
  },
  {
    path: '*',
    name: '404',
    component: () => import('@/views/404')
  }
]

/**
 * 需要动态添加的路由
 */
export const asyncRouterMap = [
  {
	  path: '/educate',
	  name: 'Educate',
	  component: Layout,
	  meta: {
	    title: '教务管理'
	  },
	  children: [
	    {
	      path: 'student',
	      name: 'Student',
	      component: () => import('_v/educate/student'),
	      meta: {
	        keepAlive: true,
	        title: '学员中心-桃李云帮'
	      }
	    },
	    {
	      path: 'studentEnroll',
	      name: 'StudentEnroll',
	      component: () => import('_v/educational/studentEnroll'),
	      meta: {
	        isLeaf: true,
	        title: '学员报名-桃李云帮'
	      }
	    },
	    {
	      path: 'class',
	      name: 'Class',
	      component: () => import('_v/educate/class'),
	      meta: {
	        // keepAlive: true,
	        title: '班级管理-桃李云帮'
	      }
	    },
	    {
	      path: 'timetable',
	      name: 'Timetable',
	      component: () => import('_v/educate/table'),
	      meta: {
	        // 不需要缓存
	        title: '课表管理-桃李云帮'
	      }
	    },
	    {
	      path: 'course',
	      name: 'Course',
	      component: () => import('_v/educate/course'),
	      meta: {
	        keepAlive: true,
	        title: '课程管理-桃李云帮'
	      }
	    }
	  ]
	},
  executiveRoute,
  ....
]

export default new Router({
  mode: 'hash',
  base: process.env.BASE_URL,
  routes: constantRouterMap // 这里只返回静态路由
})

2. 动态匹配路由

使用VueRouter的router.addRoutes(routes: Array<RouteConfig>)方法,动态添加路由。
注意:这里是通过URL匹配,你也可以根据其他方式匹配。 isLeaf表示不在菜单中的路由,应该根据权限来判断是否动态添加路由

/** store.js */
addUserMenus({ commit }, menus) {
   commit('setMenuList', menus)
   // 扁平化菜单数据
   const menuList = treeToList(menus.concat([]))
   const addRoutes = []
   let tempPath = ''
   let tempList = []
   // 这里只做了两层的路由处理
   asyncRouterMap.forEach(item1 => {
     tempPath = item1.path || ''
     tempList = []
     if (item1.children) {
       item1.children.forEach(item2 => {
         // TODO isLeaf是写死的,应该按照权限判断
         if ((item2.meta && item2.meta.isLeaf) || menuList.find(o => o.url && path.join(tempPath, item2.path).toLowerCase() === o.url.toLowerCase())) {
           tempList.push(item2)
         }
       })
     }
     if (tempList.length > 0) {
       addRoutes.push(Object.assign({}, item1, {
         children: tempList
       }))
     }
   })
   // 添加授权路由页面
   Router.addRoutes(addRoutes)
 }

treeToList方法

/**
 * 树 转 列表
 * 广度优先,先进先出
 * @param {Array} tree 树状数据
 * @param {String} childKey children的key
 */
export function treeToList(tree, childKey = 'children') {
  let stack = tree.concat([])
  let data = []
  while (stack.length !== 0) {
    // 从stack中拿出来分析
    let shift = stack.shift() // stack.pop()  先进后出
    data.push(shift)
    let children = shift[childKey]
    if (children) {
      for (let i = 0; i < children.length; i++) {
        // 把数据放入stack中
        stack.push(children[i])
      }
    }
  }
  return data
}

本文地址:https://blog.csdn.net/tly599167/article/details/107378249