尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

若依前后端分离版动态菜单路由实现原理与踩坑指南

发布时间:2026/9/13 15:38:49

资讯中心
01
ARTICLE

若依前后端分离版动态菜单路由实现原理与踩坑指南

若依前后端分离版动态菜单路由实现原理与踩坑指南
拿到这个标题我得先说实话动态菜单路由这块是若依前后端分离版里最绕、但也最值得搞明白的一段。很多初学者一开始觉得它神秘无非是因为它不像普通项目那样在代码里把路由写死而是等用户登录以后根据后端返回的菜单数据临时“拼”出一套路由来。这背后牵扯到数据库表、后端查询逻辑、Vuex状态管理、Vue Router动态注册好几个环节任何一个环节脱节表现出来的就是刷新页面就白屏或者菜单半天出不来。这篇文章我就围绕“获取动态菜单路由”这条主线把若依的前后端配合逻辑掰开揉碎讲清楚包括后端到底返回了什么、前端拿到数据以后做了什么处理、路由是怎么动态挂载的以及我实际开发中踩过的一堆坑。先说清楚这套机制到底解决了什么问题。大部分管理系统权限模型是固定的谁的账号进来就看到那么几套页面权限控制基本靠前端页面显示隐藏来做。但若依不是这样玩它的菜单、按钮权限全部存数据库用户登录后系统根据用户身份动态决定他能看到哪些菜单、进入哪些页面。这就是“动态菜单路由”存在的意义菜单不是写死在代码里的而是服务端下发、前端渲染。也就是说同一套前端工程配置不同的角色权限登录后看到的界面可以完全不一样。1. 搞懂若依动态路由的前后端设计思路1.1 动态菜单路由到底是怎么个动态法要理解若依的动态菜单路由首先要跳出普通 Vue 项目的思维定势。普通项目通常在router/index.js里把首页、列表页、详情页全部定义好用户访问哪个路径就渲染哪个组件。若依的做法完全不同它把菜单结构存到了数据库的sys_menu表里每个菜单还带上了菜单类型、路由地址、组件路径、权限标识、显隐状态、排序等一堆字段。用户登录以后后端会根据当前用户所拥有的角色去sys_menu里查询出他能访问的菜单然后返回一棵符合前端路由要求的结构树。前端拿到这棵树之后再通过 Vue Router 的addRoute方法把这些路由“塞”进路由表。这个过程就是动态路由的完整闭环。这里要注意若依动态菜单路由和简单的“按角色加载不同菜单”有本质区别。它不只是控制菜单的可见性而是整套前端路由表本身就是运行时生成的。后端给什么菜单前端就注册什么路由。哪怕两个用户登录的是同一个前端工程他们所能访问到的 URL 也是不一样的——没有权限的路径在路由表里压根就不存在。1.2 为什么费这么大劲直接把路由全部注册不行吗很多人一开始会问我把所有路由全部注册进去菜单上不显示不就好了这样后端都不用返回菜单了不是更省事吗这个问题的答案是能省事但会埋下安全隐患。前端路由无法真正保护后端接口只做页面级隐藏只是掩耳盗铃。如果一个没有权限的用户猜到了管理后台某个 URL直接输入地址访问前端路由完全匹配得到页面就渲染出来了页面里就会正常执行请求后端的操作。而若依的动态路由机制没有权限的页面根本不在路由表里用户直接改 URL 访问Vue Router 直接就报没有匹配到的 404 了。所以在若依的设计里动态路由和动态菜单是同一套机制在支撑。前端侧保障的是“看不到也进不去”后端侧保障的是“接口访问会被拦截”两层配合才是完整权限体系。如果你把前端路由写死那就等于放弃了一层重要的防线也放弃了这个脚手架最核心的设计思想。1.3 若依菜单表的字段含义先看再理解后端返回的数据本质上就是sys_menu表里的记录组装而成的。你要真正理解前端为什么这样处理数据必须先认识这张表的关键字段。我直接挑重点说明。字段名含义作用说明menu_id菜单ID主键父子关系靠它关联parent_id父菜单ID根菜单的parent_id为0menu_name菜单名称显示在侧边栏上的名称path路由地址对应前端路由的pathcomponent组件路径前端组件的相对路径如system/user/indexmenu_type菜单类型M目录、C菜单、F按钮perms权限标识按钮级别的权限编码如system:user:listvisible显示状态0显示1隐藏status菜单状态0正常1停用is_frame是否外链1是外链0否icon菜单图标侧边栏图标这里最关键的字段是component。前端就是根据这个字段去加载对应组件的。目录类型通常是Layout菜单类型的component则指向具体的 Vue 组件路径比如system/user/index就对应前端views/system/user/index.vue这个文件。2. 后端接口返回了什么动态路由的数据源头2.1 获取路由的核心接口 /getRouters若依前后端分离版中后端获取动态路由的接口路径一般是/getRouters完整路由通常是/getRouters由SysLoginController或SysMenuController暴露。前端调用的地址在接口封装文件里通常写成/getRouters。前端请求这个接口时请求头会带上登录后的 token后端根据 token 解析出当前登录用户再查询该用户拥有的菜单权限。这个接口返回的结构是嵌套树形结构父菜单下有 children子菜单还有 children依次递归。前端通过递归的方式来处理这套数据最终转换成 Vue Router 能识别的路由表结构。返回数据的核心部分大致是这样的结构{ code: 200, data: { menus: [ { name: System, path: /system, component: Layout, meta: {title: 系统管理, icon: system, isLink: null}, children: [ { name: User, path: user, component: system/user/index, meta: {title: 用户管理, icon: user, isLink: null} } ] } ] } }从这里可以看到两个关键点第一menus是一个数组代表了最终渲染菜单树的顶层节点第二数据的结构设计刻意贴合了前端路由的概念path、component、name这些字段和 Vue Router 的路由配置高度对齐。也就是说后端返回的数据已经为前端做路由转换做好了准备。2.2 后端权限查询的逻辑简述后端层面权限数据是围绕角色来组织的。一个用户可能拥有多个角色一个角色可能关联多个菜单。用户登录时后端只存了一个登录标记也就是 token后续每次请求都通过 token 去查这个用户是谁。在/getRouters这个接口里后端会根据用户 ID 去sys_user_role、sys_role_menu、sys_menu这些关联表里查这个用户能看的所有菜单。查询出来的菜单会经过一次排序、组装构建成树形结构返回给前端。如果你自己改了数据库里的菜单数据刷新页面后前端拿到的动态菜单就会跟着变化。这就是这套框架权限管理灵活的地方。2.3 为什么后端姓“菜单”而不是姓“路由”看代码时你可能会好奇为什么后端的表叫sys_menu返回的数据结构却叫路由结构。这其实是若依的一个设计特色菜单表和路由信息混在一起存储菜单天然就携带了前端路由需要的字段。这样做的好处很明显管理后台可以在同一个菜单管理界面里既维护前端路由又维护页面标题、图标、权限标识甚至还能维护按钮级别的权限点。在菜单管理中维护好路由信息前端就不需要再为权限页面单独维护一套路由映射表了。但这也带来了一个容易让人困惑的点前端代码里找不到完整的路由表。如果你非要在前端代码里找某个页面的路由配置你最终会找到permission.js里从接口拉取菜单、动态注册路由的逻辑而不是某一个现成的路由数组。理解了这一点你才算真正入门了这套动态路由机制。3. 前端获取动态菜单路由的完整链路3.1 路由守卫里发生了什么前端动态路由的起点不在页面组件里而在src/permission.js这个文件里。这个文件注册了一个全局前置守卫只要路由发生变化这个守卫就会先执行。重点逻辑大致是这样的先判断有没有 token。没有 token说明用户还没有登录那就直接跳转到登录页。有 token再判断用户信息是否已经拉取过。如果还没有就进入“加载用户信息动态路由”的流程。这个流程里最关键的一步是调用 Vuex 里的GenerateRoutes注意不同版本方法名有细微差别这个方法会发起/getRouters请求拿到当前用户的菜单数据后会用一个递归方法把它转换成 Vue Router 需要的路由配置格式然后通过router.addRoutes或者新版 Vue Router 的addRoute逐个注册进去。3.2 Vuex里权限模块的角色若依的 Vuex 里专门有一个permission模块用来管理路由侧的数据。这个模块中有两个相当重要的概念一个是动态路由转换方法另一个是保存最终路由结果的 state。permission模块会暴露一个状态数据routes这个数据用于侧边栏菜单渲染还有一个dynamicRoutes用于保存动态注册的路由。前端侧边栏组件并不直接读取动态路由注册的结果而是读取 Vuex 里的routes数据来渲染菜单。这么做的好处是菜单渲染和路由注册虽然来源一致但数据流是分开的互不干扰。在这里我想重点强调一下addRoute注册的是路由而 Vuex 里存的是菜单数据。两者有关系但不是一回事。路由注册上去以后浏览器地址栏输入对应 URL 能访问菜单渲染逻辑是另外一套根据 Vuex 里的routes生成侧边栏列表。如果你做二次开发时自定义了路由结构一定要保证这两份数据保持一致否则可能出现菜单不显示、但输入 URL 能访问的情况UI 和权限就出现矛盾了。3.3 路由转换的核心逻辑剖析若依前端代码里最容易被拿来二次改造的就是路由转换的逻辑。后端返回的菜单结构是嵌套的前端要先拿到这个结构然后做一次映射把标准的菜单树转成 Vue Router 能识别的路由配置。转换逻辑的核心代码风格大概是这样function loadView(view) { return (resolve) require([/views/${view}], resolve) }这段代码是动态路由加载组件的关键。如果component是Layout需要映射成Layout组件如果是普通页面路径就通过loadView动态加载对应的 .vue 文件。这里的/views/前缀说明后端配置的组件路径需要和前端src/views目录结构保持一致。实际转换时遍历的是树形结构逐层判断。每个菜单会生成一个路由对象path直接取菜单的path字段name取菜单名meta里存放标题、图标、是否外链等信息。对于目录类型M组件映射为Layout对于菜单类型C组件映射为Layout/或者具体的页面组件。这个细节有很多坑我在后面章节详细展开。4. 菜单路由拿到手之后页面是怎么渲染出来的4.1 侧边栏组件的数据来源侧边栏菜单的渲染入口在布局组件Layout里它引用了Sidebar子组件而Sidebar的核心数据来源就是 Vuex 中permission模块的routes。这是菜单渲染和数据流最直接的联系。Sidebar组件内部使用递归组件SidebarItem对routes做遍历渲染。每遍历到一个父子节点就渲染出对应的菜单项。如果是外链isLink就以链接形式打开如果有子菜单就递归渲染子菜单。所以你在页面上看到的多级菜单本质上就是 Vuex 里的routes数据生成的。这里有一个很实用的排查技巧如果你改了数据库菜单但前端页面没变化先去浏览器控制台里打印 Vuex 的permission/routes看看到底拿到的是什么数据。很快就能定位问题出在后端返回还是前端转换。这种排查方式比看代码猜要快得多。4.2 动态路由注册成功与组件渲染的关系路由最终能访问起决定性作用的是router.addRoute这个动作。每次动态注册一个路由时Vue Router 都会把路由加入路由匹配表。这之后用户在地址栏输入对应路径Vue Router 才能匹配到对应的组件并渲染。这里有一个容易忽视的细节动态路由注册之后如果你马上用router.push跳转到对应页面可能第一次跳转失败。原因是路由解析时动态路由尚未注册完成或者异步回调没有落到正确的时机。若依源码在路由守卫里做了处理保证菜单拉取和路由注册完成后再放行跳转。我在自己项目里遇到过一种情况就是在GenerateRoutes这个 action 还有异步操作没完成时前端就提前调用next()导致刷新页面后动态路由还没有注册上去页面却已经结束了守卫流程最终白屏或 404。这个问题的典型表现就是第一次进系统一切正常F5 刷新之后菜单就消失了或者跳转 404 了。排查思路非常明确直接在路由守卫里打点看是异步请求未完成就放行了还是addRoute没执行到。4.3 404页面为什么容易踩坑若依的动态路由里有个经典坑位404 页面的路由位置。如果你把 404 路由定义在静态路由表里同时动态路由又还没有注册完刷新一个深层页面时很可能先匹配到 404而不是你期望的实际页面。若依框架处理这个问题的思路是在动态路由的最末尾追加一个 catch-all 路由{ path: *, redirect: /404 }这样等动态路由全部注册完它才会匹配最终结果避免提前拦截。这个细节在二次开发时特别重要。如果你自己往路由表里加了路由却始终进不去页面一直停在 404大部分情况下是 catch-all 路由的位置不对或者动态路由注册顺序不对。我自己使用的是在动态路由生成的函数里最后返回一个 catch-all 路由节点确保它排在最末尾。这种做法在若依原版代码里就是这样设计的二次开发时千万别手欠把这个逻辑给删了。5. 实操中常见的问题与排查5.1 刷新页面之后动态路由丢失怎么办这是若依动态路由最常遇到的面试题和实战坑。刷新页面后前端的 Vuex 状态会全部清空也就是所有异步拉取的数据都没了。如果没有重新拉取菜单和动态注册路由那浏览器在地址栏里带着路径刷新就会找不到对应路由。解决方案就在路由守卫里它通过 token 和用户信息判断用户是否已经“登录过”如果用户信息不存在但存在 token说明是刷新场景就重新拉取用户信息和动态路由。保证刷新的时候把动态路由再次注册进去。我实际排查时发现很多刷新 404 的案例并不是框架逻辑问题而是项目里的某个自定义组件抛了异常导致路由守卫后续代码没有执行完。因此遇到刷新白屏先打开控制台看报错不要一股脑认定是路由问题。5.2 动态菜单拿到了但组件加载报错这种情况一般出现在后端配置菜单时component字段写错了。比如前端组件实际存放在views/system/user/index.vue后端却写成了system/user那require加载就会失败。或者文件路径大小写不匹配Linux 服务器环境下会直接报找不到模块的错误。排查方式就是直接看浏览器控制台的精确报错信息提示哪个文件加载失败然后去src/views下核对路径和文件名。还有一点组件的名称要尽量和路由 name 一致否则配合 keep-alive 时缓存会失效页面状态保存不住。5.3 多级菜单和组件路径参考 Layout 的特殊处理若依菜单管理的顶级菜单组件路径一般填Layout二级菜单的component才会指向真实页面。这个设计需要特别注意如果你在后端配置一个三级菜单但component没有指向具体的页面组件而是又填了Layout前端渲染可能就不会按预期工作。多级目录这种场景父级目录的component固定是Layout子目录往下走每一层目录如果你也想渲染一个布局壳子那就需要自行创建对应的布局组件然后在后端菜单里配置对应的路径。如果你现在只是做业务开发不搞复杂的嵌套布局最简单的方式就是尽量控制菜单层级保持在两级第三级就放页面。一个更稳妥的做法是页面配置好以后先在本地把菜单路径和前端目录结构核对清楚再保存到数据库里避免线上反反复复调试菜单。5.4 按钮权限如何配合动态路由使用动态菜单解决的是“用户能看到哪些页面”的问题按钮权限解决的是“用户能在这个页面里操作哪些按钮”的问题。若依的按钮权限和菜单权限同样存储在sys_menu表区别只是menu_type为 F。前端通过自定义指令v-hasPermi根据权限标识控制按钮的显示隐藏而这些权限标识也需要在用户信息加载时一并获取。如果你在二次开发时发现按钮权限没有生效先看在用户信息接口里返回的permissions字段是否包含了对应的按钮权限标识。如果后端没返回前端再怎么判断也没有用。前后端权限体系是联动的单端排查很难解决问题。6. 个人实操中的一点心得与技巧我前前后后基于若依做过几个管理系统对动态菜单路由的理解也是一步步踩坑踩出来的。最后说几个我认为能在实际开发中提高效率的使用习惯。第一个心得强烈建议在开发环境打开 Vue Devtools 的 Vuex 选项卡动态路由流程走一遍亲眼看一下permission模块里数据的变化。这比读源码直观多了。你会看到登录后routes从空数组变成一堆路由配置那种感觉就是一下子通了。第二个心得尽量规范数据库菜单配置path统一用驼峰或短横线风格component统一以前端路径为准所有维护菜单的人都要遵守同一份约定。多人协作时菜单配置混乱是最大的隐性成本。第三个心得动态路由的二次开发大概率不是去改它的“动态”部分而是去扩展你业务自己的路由和权限点。这时候核心思路是先加菜单记录进数据库再确保前端views目录下有对应组件最后让权限分配给角色。如果你因为这个流程导致菜单不显示别慌路由守卫和 Vuex 的数据链路仔细走一遍很快就能定位到哪一步断了。还有个非常实用的小技巧在开发阶段后端接口挂了常导致登录后一片空白很容易误判成前端问题。可以先在浏览器的 Network 面板确认/getRouters接口是否返回正常先解决后端问题再回来调前端。很多初学者一看到白屏就怀疑动态路由出了问题其实大多数情况是网络请求挂掉了。第四个心得若依的动态菜单路由在前后端分离场景下本质上是把权限模型和 Vue Router 的运行时机制做了深度的绑定。做二次开发时如果不能完全驾驭这套机制宁可先沿用它的规则也不要轻易另起炉灶。我有一次为了做一个特殊布局把动态路由的生成逻辑改得面目全非结果后续所有菜单调整都要靠改代码完成数据库里配菜单反而没用了效率严重下降。后来我放弃了自定义方案回归到若依的标准流程把布局差异通过组件内部样式解决。路由就用标准方式生成这才是更高效的路子。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。