vue3 动态路由的实现

53 阅读3分钟

vue3动态路由的实现

前置知识

  1. vue-router的路由守卫,导航守卫 | Vue Router (vuejs.org)
  2. addRoute为路由添加一个新记录接口:Router | Vue Router (vuejs.org)

使用vue-router的路由守卫,在进入路由前先判断一下,路由是否已导入,如果还没有导入的,可以动态的导入。需要为路由添加的信息有path,component

主要的问题是,组件的动态导入 之前使用webpack工具的时候可以使用request方法实现模块的动态导入

component: (resolve) => require([`@/view${item.curl}`], resolve)

现在vue3使用vite作为构建工具,和webpack搭不上边。 不过还好vite的官方文档也表示支持动态导入

下面摘自官方文档

Vite 也支持带变量的动态导入。

const module = await import(`./dir/${file}.js`)

注意变量仅代表一层深的文件名。如果 file 是 foo/bar,导入将会失败。对于更进阶的使用详情,你可以使用 glob 导入 功能。

Glob 导入 Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块: 以上将会被转译为下面的样子:

// vite 生成的代码
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
}

你可以遍历 modules 对象的 key 值来访问相应的模块:

for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}

匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入 { eager: true } 作为第二个参数:

const modules = import.meta.glob('./dir/*.js', { eager: true })

所以,我们要导入某个文件夹下面的所有文件就是 modules = import.meta.glob('../views/content/**'), 需要访问对应的模块就是

modules[`../views/content/${path}.vue`]

当然如果你配置的component名称只是一个文件名的话,可以像上面提到的直接用import(字符串拼接),就没有必要使用import.meta.blog了

下面是动态路由的完整代码

import store from '@/store'

router.beforeEach((to, from, next) => {
  const whiteList = ['/login'] // 白名单
  let token = window.sessionStorage.getItem('refresh_token')
  let menuList = JSON.parse(window.sessionStorage.getItem('menuList'))
  let hasRoutes = store.state.hasRoutes
  if (token) {
    if (!hasRoutes) {
      bindRoute(menuList) //这个是动态路由的绑定
      store.commit('SET_ROUTES_STATE', true)
    }
    next()
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
})

// 动态绑定路由
const bindRoute = (menuList) => {
  let newRoutes = router.options.routes
  menuList.forEach((menu) => {
    if (menu.children) {
      menu.children.forEach((m) => {
        let route = menuToRoute(m, menu.name)
        if (route) {
          newRoutes[0].children.push(route)
        }
      })
    }
  }) // 重新添加到路由
  newRoutes.forEach((route) => {
    router.addRoute(route)
  })
}

// 获取views目录下的所有的文件 不要使用@别名
// const modules = import.meta.glob('../views/content/**')
const modules = import.meta.glob('../views/content/**')

// 解析组件
const resolveComponent = (path) => {
  console.log(modules)
  // 拿到views下面的所有文件之后,动态拼接`key`去获取value
  const importPage = modules[`../views/content/${path}.vue`]
  if (!importPage) {
    throw new Error(`Unknown page ${path}. Is it located under Pages with a .vue extension?`)
  }
  return importPage
}

// 菜单对象转成路由对象
const menuToRoute = (menu, parentName) => {
  if (!menu.component) {
    return null
  } else {
    let route = {
      name: menu.name,
      path: menu.path,
      meta: {
        parentName: parentName,
      },
    }
    //注意变量仅代表一层深的文件名。如果 file 是 foo/bar,导入将会失败 ,这是vite官方文档的原话,多级目录使用import.meta.glob
    // route.component = () => import('@/views/content/' + menu.component + '.vue')
    route.component = resolveComponent(menu.component)
    return route
  }
}

export default router


下面的是我的目录json-menuList,需要的可以结合代码参考

[
  {
    "id": 1,
    "children": [
      {
        "id": 3,
        "name": "用户管理",
        "icon": "user",
        "order_num": 1,
        "path": "/sys/user",
        "component": "sys/user/TheUser",
        "menu_type": "C",
        "remark": "用户管理菜单",
        "parent": 1
      },
      {
        "id": 4,
        "name": "角色管理",
        "icon": "peoples",
        "order_num": 2,
        "path": "/sys/role",
        "component": "sys/role/TheRole",
        "menu_type": "C",
        "remark": "角色管理菜单",
        "parent": 1
      },
      {
        "id": 5,
        "name": "菜单管理",
        "icon": "tree-table",
        "order_num": 3,
        "path": "/sys/menu",
        "component": "sys/menu/TheMenu",
        "menu_type": "C",
        "remark": "菜单管理菜单",
        "parent": 1
      }
    ],
    "name": "系统管理",
    "icon": "system",
    "order_num": 1,
    "path": "/sys",
    "component": "",
    "menu_type": "M",
    "remark": "系统管理目录",
    "parent": null
  },
  {
    "id": 2,
    "children": [
      {
        "id": 6,
        "name": "部门管理",
        "icon": "tree",
        "order_num": 8,
        "path": "/bsns/department",
        "component": "bsns/TheDepartment",
        "menu_type": "C",
        "remark": "部门管理菜单",
        "parent": 2
      },
      {
        "id": 7,
        "name": "岗位管理",
        "icon": "post",
        "order_num": 7,
        "path": "/bsns/post",
        "component": "bsns/ThePost",
        "menu_type": "C",
        "remark": "岗位管理菜单",
        "parent": 2
      },
      {
        "id": 9,
        "name": "菜品管理",
        "icon": "food",
        "order_num": 2,
        "path": "/bsns/food",
        "component": "bsns/food/TheFood",
        "menu_type": "C",
        "remark": "",
        "parent": 2
      },
      {
        "id": 10,
        "name": "店铺管理",
        "icon": "store",
        "order_num": 1,
        "path": "/bsns/store",
        "component": "bsns/store/TheStore",
        "menu_type": "C",
        "remark": "",
        "parent": 2
      },
      {
        "id": 11,
        "name": "类别管理",
        "icon": "category",
        "order_num": 4,
        "path": "/bsns/category",
        "component": "bsns/category/TheCategory",
        "menu_type": "C",
        "remark": "",
        "parent": 2
      },
      {
        "id": 12,
        "name": "订单查看",
        "icon": "order",
        "order_num": 3,
        "path": "/bsns/order",
        "component": "bsns/order/TheOrder",
        "menu_type": "C",
        "remark": "",
        "parent": 2
      }
    ],
    "name": "业务管理",
    "icon": "monitor",
    "order_num": 2,
    "path": "/bsns",
    "component": "",
    "menu_type": "M",
    "remark": "业务管理目录",
    "parent": null
  }
]

参考文章 功能 | Vite 官方中文文档 (vitejs.cn)

vue3+vue-router+vite 实现动态路由_vite 动态路由-CSDN博客

vue3+vue-router+vite 实现动态路由_vite 动态路由-CSDN博客