核心是解析后端JSON菜单树并动态生成Vue路由:约定字段(path/name/component/meta/children)→ 递归映射为路由配置 → 调用router.addRoute添加;需处理异步组件、权限过滤、嵌套路由及守卫校验。

直接根据后端返回的 JSON 菜单树生成 Vue 路由表,核心在于「解析菜单结构 → 映射为路由配置 → 动态添加到 router 实例」。关键不是硬编码路由,而是建立菜单字段与路由属性(path、name、component、meta)之间的可靠映射规则,并处理好异步组件加载、权限过滤和嵌套路由。
1. 约定后端菜单 JSON 结构
前后端需提前约定字段语义,避免解析歧义。推荐基础字段如下:
- id:唯一标识(可选,用于权限比对)
- path:前端路由 path(如 "/user/list"),支持带参数("/user/:id")
- name:路由唯一 name(如 "UserList"),用于编程式导航和 keep-alive
- component:组件路径字符串(如 "views/user/UserList.vue"),非绝对路径
- redirect:重定向目标(如 "/user/list")
- meta:扩展信息对象,含 title、icon、hidden、permission 等
- children:子菜单数组(递归)
示例片段:
[
{
"path": "/user",
"name": "User",
"component": "layout/RouteView",
"meta": { "title": "用户管理", "icon": "user" },
"children": [
{
"path": "list",
"name": "UserList",
"component": "views/user/UserList.vue",
"meta": { "title": "用户列表" }
}
]
}
]
2. 编写菜单转路由的递归解析函数
在 router/index.js 或单独 utils 文件中实现,重点处理:
- 路径拼接(父级 path + 子级 path,自动补 /)
- 异步组件动态 import(用 defineAsyncComponent 或直接 import())
- 跳过无 component 的目录级节点(仅作分组,不生成真实路由)
- 过滤掉 meta.hidden === true 的项(不显示在菜单,但可能仍需路由)
Vue 3 + Vue Router 4 示例代码:
import { createRouter, createWebHashHistory } from 'vue-router'
import { defineAsyncComponent } from 'vue'
const layout = () => import('@/layout/RouteView.vue')
// 根据菜单项生成单个路由记录
function genRouteFromMenu(menu) {
const route = {
path: menu.path,
name: menu.name,
meta: menu.meta || {},
redirect: menu.redirect
}
// 有 component 才是真实页面路由
if (menu.component) {
// 若是 layout,则用 layout;否则动态加载页面组件
const compPath = menu.component.startsWith('layout/')
? `@/layout/${menu.component.split('layout/')[1]}`
: `@/views/${menu.component.replace('.vue', '')}`
route.component = defineAsyncComponent(() => import(compPath))
} else {
route.component = layout // 默认用 Layout 包裹子路由
}
// 递归处理 children
if (Array.isArray(menu.children) && menu.children.length) {
route.children = menu.children.map(genRouteFromMenu)
}
return route
}
// 导出生成函数(供后续调用)
export function generateRoutesFromMenus(menus) {
return menus.map(genRouteFromMenu)
}
3. 在登录后动态添加路由
不能在初始 router 中写死所有路由。典型流程:
- 用户登录成功,调用接口获取菜单 JSON
- 用 generateRoutesFromMenus() 解析成路由数组
- 调用 router.addRoute() 或 router.addRoutes()(v4.1+ 推荐逐个 addRoute)
- 手动刷新导航(router.replace(...) 避免重复 history 记录)
在登录逻辑中(如 store action 或 setup):
import { useRouter } from 'vue-router'
import { generateRoutesFromMenus } from '@/router/generator'
const router = useRouter()
// 假设已拿到 menus 数组
const asyncRoutes = generateRoutesFromMenus(menus)
asyncRoutes.forEach(route => {
router.addRoute(route) // 动态注册
})
// 确保新路由生效,跳转到首个可访问页面
await router.replace({ path: asyncRoutes[0]?.children?.[0]?.path || asyncRoutes[0]?.path || '/home' })
4. 注意权限与守卫配合
动态路由只是第一步。还需结合路由守卫做权限控制:
- 全局前置守卫中检查当前路由 meta.permission 是否在用户权限列表内
- 菜单 JSON 中的 permission 字段(如 ["user:list", "user:add"])需与后端权限码一致
- 若无权限,重定向到 403 页面或首页
示例守卫逻辑:
router.beforeEach((to, from, next) => {
const permissions = store.state.user.permissions // 用户权限数组
const requiredPerms = to.meta.permission || []
if (requiredPerms.length && !requiredPerms.some(p => permissions.includes(p))) {
next({ name: '403' })
} else {
next()
}
})
不复杂但容易忽略:动态添加的路由不会出现在 router.getRoutes() 的初始列表里,但会实时生效;keep-alive 缓存需用 include 匹配 name,所以 name 字段必须唯一且稳定;生产环境注意 Webpack 懒加载 chunk 名冲突,可通过 webpackChunkName 注释优化。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!









