文章目录
- Vue2 动态路由失效的根因与正确解法:用 Vuex 管理权限路由
- 一、问题复现:路由加了,页面却没变
- 二、根因分析:vue-router 不具备响应式能力
- 1️⃣ `addRoutes` 只影响「路由匹配」
- 2️⃣ `router.options.routes` 不是响应式数据
- 三、正确架构:菜单 = Vuex 路由状态
- 核心设计思想(重点)
- 四、最小可用实现(去掉项目噪音版)
- 1️⃣ permission 模块(核心)
- 2️⃣ 路由守卫(必须 replace)
- 3️⃣ 侧边栏只从 Vuex 读
- 五、我在项目中踩过的几个真实坑
- 坑 1:动态 import 导致菜单不显示
- 坑 2:后端路径错误直接导致路由崩溃
- 坑 3:数据判断不严谨导致“假空数组”
- 坑 4:computed 重复定义导致菜单逻辑失效
- 六、最终收益
Vue2 动态路由失效的根因与正确解法:用 Vuex 管理权限路由
在 Vue2 项目中做动态路由时,很多人都会遇到一个诡异问题:
接口已经返回了动态路由,
router.addRoutes()也调用了,但页面菜单就是不刷新。
我在实际项目中完整踩过这个坑,最终总结出一个稳定、可维护、可扩展的解决方案:
路由只负责跳转,菜单必须交给 Vuex 管理。
一、问题复现:路由加了,页面却没变
典型现象:
- 接口返回 9 个路由
router.addRoutes(accessRoutes)执行成功- 控制台打印
router.options.routes是完整的 - 侧边栏菜单仍然只有 2 个
这说明一件事:
路由层生效了,但 UI 层完全不知道发生了变化。
二、根因分析:vue-router 不具备响应式能力
这是 Vue2 动态路由最容易被忽略的一点:
1️⃣addRoutes只影响「路由匹配」
router.addRoutes(accessRoutes)它只做一件事:
- 让 URL 能匹配到组件
但它不会:
- 触发组件重新渲染
- 更新侧边栏
- 通知 Vue 进行响应式更新
2️⃣router.options.routes不是响应式数据
如果你的菜单是这样写的:
this.$router.options.routes那菜单永远不会自动刷新,因为:
- 它不是 Vue data
- 不是 Vuex state
- Vue 根本监听不到变化
三、正确架构:菜单 = Vuex 路由状态
核心设计思想(重点)
后端返回菜单 / 权限 ↓ Vuex generateRoutes ↓ Vuex.state.routes ←—— 侧边栏渲染 ↓ router.addRoutes ←—— 路由跳转一句话总结:
vue-router 管跳转,Vuex 管“展示用的路由数据”
四、最小可用实现(去掉项目噪音版)
1️⃣ permission 模块(核心)
// store/modules/permission.js import { constantRoutes } from '@/router' const state = { routes: [], // 侧边栏使用 addRoutes: [] // 动态路由 } const mutations = { SET_ROUTES(state, routes) { state.addRoutes = routes state.routes = constantRoutes.concat(routes) } } const actions = { generateRoutes({ commit }, asyncRoutes) { commit('SET_ROUTES', asyncRoutes) return asyncRoutes } } export default { namespaced: true, state, mutations, actions }2️⃣ 路由守卫(必须 replace)
router.beforeEach(async (to, from, next) => { const hasToken = getToken() if (!hasToken) { return next('/login') } const hasRoles = store.getters.roles?.length > 0 if (hasRoles) { return next() } try { await store.dispatch('user/getInfo') const accessRoutes = await store.dispatch( 'permission/generateRoutes', asyncRoutesFromApi ) router.addRoutes(accessRoutes) // 关键:重新触发导航 next({ ...to, replace: true }) } catch (e) { next('/login') } })❗ 不加
replace: true,新路由本次不会生效
3️⃣ 侧边栏只从 Vuex 读
computed: { routes() { return this.$store.state.permission.routes } }🚫 禁止使用:
this.$router.options.routes五、我在项目中踩过的几个真实坑
坑 1:动态 import 导致菜单不显示
// ❌ Vue2 场景下不稳定 component: () => import(`@/views/${path}`) // ✅ 稳定方案 component: resolve => require([`@/views/${path}`], resolve)坑 2:后端路径错误直接导致路由崩溃
解决方案:兜底 Layout
if (!item.component || item.component === 'Layout') { menu.component = Layout }坑 3:数据判断不严谨导致“假空数组”
// ❌ if (res.data && res.data.length > 0) // ✅ if (Array.isArray(res.data) && res.data.length > 0)坑 4:computed 重复定义导致菜单逻辑失效
// ❌ 后者会覆盖前者 computed: {...} computed: {...} // ✅ computed: { ...mapGetters([]), sidebar() {} }六、最终收益
采用“菜单 = Vuex 路由状态”后:
- 菜单 100% 响应式
- 动态路由稳定生效
- 权限逻辑集中管理
- 后端可完全控制前端菜单结构
- 架构清晰,可维护性明显提升
这套方案已经在真实业务项目中长期运行,适合中大型 Vue2 管理系统。