本文详细讲解在Vue3结合Vite环境下如何实现动态路由。通过后端返回路由配置数据,利用vue-router的addRoute方法动态注册路由,并结合导航守卫处理权限校验。重点解决Vite项目中通过import.meta.glob动态加载组件以及刷新页面导致404或白屏的问题,提供完整的代码实现方案。
动态路由是指根据用户权限或业务需求,在运行时动态添加或修改路由配置的技术。

动态路由通常应用于需要权限控制的场景,例如后台管理系统中不同角色拥有不同的访问权限。
以常见的后台管理系统为例,系统通常包含超级管理员、管理员、运维、财务等多个角色。不同角色登录后看到的菜单和按钮权限不同,如超级管理员拥有全部权限,而财务角色仅能访问财务相关模块。实现此类需求主要有两种常见方案。
方案一:前端维护路由表
在此方案中,前端预先定义好各角色对应的权限路由表。后端仅返回当前用户的角色标识,前端通过过滤函数获取该角色可用的菜单和按钮,并动态添加路由。该方案的缺点在于,若新增角色或调整权限,前端需修改代码并重新部署,维护成本较高且不够灵活。
方案二:后端返回路由表数据
这是目前更常用的方式。系统通常包含菜单管理、角色管理、用户管理等权限中心功能。前端页面路由信息由后端维护,开发时需约定规则,例如所有页面组件存放于/src/views目录下。通过菜单管理配置数据,并在角色管理中分配角色菜单,最后在用户管理中为用户配置角色。这样即使新增角色或修改菜单,前端也无需改代码,只需在后台维护数据即可。后端返回的数据格式通常如下所示:
[
{
path: "/application",
name: "application",
component: "Layout",
title: "应用管理",
show: true,
icon: "",
children: [
{
path: "",
name: "application-index",
component: "/application/index.vue",
},
],
},
{
path: "/permission",
name: "permission",
component: "Layout",
title: "权限管理",
show: true,
icon: "",
children: [
{
path: "menu",
name: "permission-menu",
component: "/permission/menu/index.vue",
title: "菜单管理",
show: true,
icon: "",
},
{
path: "user",
name: "permission-user",
component: "/permission/user/index.vue",
title: "用户管理",
show: true,
icon: "",
},
{
path: "role",
name: "permission-role",
component: "/permission/role/index.vue",
title: "角色管理",
show: true,
icon: "",
},
],
},
]
观察上述数据结构,可以发现path、name、component、children等属性与我们手动维护路由表时使用的属性相似,后端返回的数据结构可直接用于前端路由配置。
动态路由的实现方法
实现动态路由其实就要用到vue-router提供的一个方法,叫addRoute在之前版本的时候是addRoutes,现在非版本的vue-router给废除了
addRoute()如何使用呢?可以看一下官方文档
关于的具体使用方式,可参考官方文档。当我们需要添加一个主路由时,可以通过该方法进行注册。
router.addRoute({ path: '/permission', name: 'permission', component: () => import('xxxxx')})
添加子路由(即嵌套路由)时,同样可以使用该方法指定父级路由的name进行嵌套注册。
router.addRoute('主路由的name', { path: 'settings', component: AdminSettings })

既然了解了addRoute()方法的使用,接下来我们可以实现具体的动态路由逻辑。
首先参考官方文档中关于导航守卫的内容,导航守卫用于控制路由跳转权限。


在之前的版本中,导航守卫需要使用next()参数来控制是否放行及跳转目标。但在新版本的vue-router中,可以选择不使用next(),当然使用也是允许的。
我们可以新建一个permission.ts文件来集中处理权限逻辑。
import router from "./index";
import { useSessionStorage } from "@vueuse/core";
import { StorageEnum } from "@/enum";
import { useUserStore } from "@/store";
const whiteList = ["/login", "/404"];
router.beforeEach(async (to) => {
const token = useSessionStorage(StorageEnum.TOKEN, "").value;
// 如果在白名单里面 并且 token 不存在
if (whiteList.includes(to.path) && !token) {
return true;
} else {
const { menuList, getMenuList } = useUserStore();
// 如果为空数组,name就请求接口重新获取后端维护的路由数据
if (!menuList.length) {
await getMenuList();
console.log("获取全部路由 =>", router.getRoutes());
// 触发重定向 不这样写会导致刷新找不到路由 两种写法都行
// return { path: to.fullPath };
return to.fullPath;
}
}
});
在代码中可能会看到return true和return to.fullpath两种写法。通过官方导航守卫的介绍可知,这两种写法均可实现路由跳转控制。

根据官方文档动态路由关于动态路由的说明,我们需要特别注意页面刷新时的处理。

因此,return to.fullpath是官方推荐的写法,旨在解决动态路由页面刷新时出现页面空白或404的问题。例如,如果路由表中配置了如下404路由映射:
{
path: "/:pathMatch(.*)",
name: "page404",
component: () => import("@/views/system/404.vue"),
}
上述内容主要涉及全局导航守卫中的使用及注意事项。可以看到,此处并未直接使用addRoute()方法,也未展示将后端数据转换为路由表的完整代码,但存在一个getMenuList()函数。在开发中,我们需要考虑代码复用和复杂逻辑的抽取。动态路由的实现涉及多个知识点接口请求,vuex或者pinia状态维护,vue-router,vite里面怎么获取文件等,下面将介绍如何获取到接口给的数据,转换成vue-router能够识别的数据,即如何获取后端数据并转换为vue-router可识别的数据格式。首先,我们需要了解vite官方提供的一个功能:获取后端返回的文件路径(如代码中的component字段,例如/application/index.vue),该文件可能位于本地项目的src/views/application/index.vue路径下。

我们需要将上述数据转换为如下格式:() => import('xxxx')。为了实现动态拼接获取文件路径,我们需要利用vite的特性。
vite官方文档中对此有明确说明。

我们需要使用import.meta.glob,此时可以打印import.meta.glob("../views/**")以查看返回结果。

返回结果是一个对象,对象的key可用于拼接获取路径,而value正是我们需要的动态文件路径。
以下代码仅供参考,实际项目中需完善多处细节,此处仅用于演示。
import type { RouterType } from "@/router/type";
import router from "@/router";
import type { RouteRecordRaw } from "vue-router";
export const useRouterConfig = () => {
// 获取views目录下的所有的文件 不要使用@别名
const modules = import.meta.glob("../views/**");
const asyncRoutes = ref<RouterType[]>([]);
const addRoutes = (menus: RouterType[]) => {
asyncRoutes.value = menus;
filterAsyncRouter();
// 动态添加 / 路由
router.addRoute({
path: "/",
redirect: asyncRoutes.value[0].path,
});
};
const filterAsyncRouter = () => {
const routerLoop = (routes: RouterType[], ParentName?: string) => {
routes.forEach((item) => {
if (item.component === "Layout") {
item.component = () => import("@/layout/index.vue");
} else {
item.component = resolveComponent(item.component);
}
const { title, show, icon, name, path, component, children } = item;
const route: RouteRecordRaw = {
component,
path,
name,
meta: {
title,
show,
icon,
},
children: children as any,
};
// 动态添加路由
if (ParentName) {
router.addRoute(ParentName, route);
} else {
router.addRoute(route);
}
if (item.children && item.children.length > 0) {
routerLoop(item.children, item.name);
}
});
};
routerLoop(asyncRoutes.value);
};
const resolveComponent = (path: string) => {
console.log(modules);
// 拿到views下面的所有文件之后,动态拼接`key`去获取value
const importPage = modules[`../views${path}`];
if (!importPage) {
throw new Error(
`Unknown page ${path}. Is it located under Pages with a .vue extension?`
);
}
return importPage;
};
return { addRoutes };
};
综上所述:
要实现vite+vue-router的动态路由,我们需要用到以下核心技术和方法:
addRoute()import.meta.glob()获取后端tree数据,递归循环,可能业务会有type类型,比如分为模块,菜单,页面,按钮等,到时候结合业务去实现逻辑导航守卫使用时的注意事项,否则会出现刷新白屏,或者路由访问死循环等
总结
本文介绍了Vue3+vue-router+vite实现动态路由的全过程,更多相关内容可搜索本站历史文章或继续浏览后续文章,感谢支持!