
前言在第 17 篇中我们掌握了 Vue Router 4 的基础配置和导航守卫。但在实际的企业级项目中仅仅能用路由是不够的你还需要面对三个核心问题首屏加载太慢怎么办 → 路由懒加载深度优化页面切换后滚动位置不对 → 滚动行为控制不同角色看到不同菜单 → 动态权限路由这一篇我们把这三个问题逐一击破给出可落地的完整方案。一、路由懒加载深度优化1️⃣ 为什么需要懒加载假设你的项目有 50 个页面如果不做懒加载import Home from /views/Home.vue import User from /views/User.vue import Order from /views/Order.vue // ... 50 个页面全部 import❌ 后果首屏加载需要下载所有页面的代码用户可能永远用不到某些页面但也得下载首屏时间可能从 1s 变成 10s2️⃣ 基础懒加载{ path: /user, component: () import(/views/User.vue) }✅ 访问/user时才会加载User.vue对应的 chunk3️⃣ 魔法注释自定义 chunk 名称{ path: /user, component: () import(/* webpackChunkName: user */ /views/User.vue) }Vite 中同样支持使用rollupOptions或vite-plugin-webpackchunkname4️⃣ 按业务模块分组推荐 ✅// 用户模块 const UserList () import(/views/user/List.vue) const UserDetail () import(/views/user/Detail.vue) // 订单模块 const OrderList () import(/views/order/List.vue) const OrderDetail () import(/views/order/Detail.vue)同一模块的页面打包到同一 chunk减少 HTTP 请求数5️⃣ 预加载关键路由// 在空闲时预加载可能访问的页面 if (requestIdleCallback in window) { requestIdleCallback(() { import(/views/User.vue) }) }✅适合大概率会访问的页面如登录后的首页6️⃣ 懒加载 Loading 过渡!-- App.vue -- template router-view v-slot{ Component } Suspense template #default component :isComponent / /template template #fallback PageSkeleton / /template /Suspense /router-view /template✅路由切换时展示骨架屏体验丝滑二、滚动行为控制1️⃣ 基础配置const router createRouter({ history: createWebHistory(), routes, scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition // 浏览器后退/前进 } return { top: 0 } // 默认回到顶部 } })2️⃣ 不同路由不同滚动行为scrollBehavior(to) { if (to.hash) { return { el: to.hash, top: 100, // 偏移量考虑固定头部 behavior: smooth } } return { top: 0 } }锚点跳转 平滑滚动3️⃣ 异步滚动等待页面渲染完成scrollBehavior(to, from, savedPosition) { return new Promise((resolve) { setTimeout(() { resolve({ top: 0 }) }, 500) }) }✅适合需要等待数据加载完成的页面4️⃣ 列表页保持滚动位置实战// 在列表页组件中 import { onActivated } from vue let scrollTop 0 onDeactivated(() { scrollTop window.scrollY }) onActivated(() { window.scrollTo(0, scrollTop) })✅配合 KeepAlive 使用列表页返回时位置不变三、动态权限路由重点这是中后台系统的核心架构我会给出一个完整的可落地方案。1️⃣ 整体设计思路登录 → 获取用户信息含角色/权限 → 根据权限过滤路由表 → 动态添加路由 → 渲染菜单2️⃣ 定义路由表的两种类型// 所有路由不需要权限 export const constantRoutes [ { path: /login, component: () import(/views/Login.vue) }, { path: /403, component: () import(/views/403.vue) } ] // 需要权限的路由 export const asyncRoutes [ { path: /dashboard, component: () import(/layouts/DashboardLayout.vue), meta: { requiresAuth: true, roles: [admin, editor] }, children: [ { path: user, component: () import(/views/User.vue), meta: { roles: [admin] } }, { path: order, component: () import(/views/Order.vue), meta: { roles: [admin, editor] } } ] } ]3️⃣ 权限过滤函数// utils/filterRoutes.ts import { RouteRecordRaw } from vue-router export function filterRoutesByRoles( routes: RouteRecordRaw[], roles: string[] ): RouteRecordRaw[] { return routes.filter(route { // 没有 meta.roles 表示任何人都能访问 if (!route.meta?.roles) return true // 判断用户角色是否在允许列表中 const hasPermission route.meta.roles.some(role roles.includes(role)) if (hasPermission route.children) { route.children filterRoutesByRoles(route.children, roles) } return hasPermission }) }4️⃣ 在路由守卫中动态添加// router/index.ts import { useUserStore } from /stores/user let routesAdded false router.beforeEach(async (to, from) { const token localStorage.getItem(token) const userStore useUserStore() // 未登录跳转到登录页 if (!token to.path ! /login) { return /login } // 已登录访问登录页则跳转到首页 if (token to.path /login) { return /dashboard } // 已登录但未添加动态路由 if (token !routesAdded) { // 获取用户信息含角色 const roles await userStore.fetchUserInfo() // 根据角色过滤路由 const accessibleRoutes filterRoutesByRoles(asyncRoutes, roles) // 动态添加路由 accessibleRoutes.forEach(route { router.addRoute(route) }) // 添加 404必须在最后 router.addRoute({ path: /:pathMatch(.*)*, component: () import(/views/404.vue) }) routesAdded true // 重新触发导航重要 return { ...to, replace: true } } })return { ...to, replace: true }是关键动态添加路由后需要重新触发一次导航才能匹配到新路由。5️⃣ 菜单渲染script setup import { useRouter } from vue-router const router useRouter() const routes router.getRoutes() // 过滤出需要渲染的菜单项 const menuRoutes routes.filter(r r.meta?.title) /script template aside classsidebar nav v-forroute in menuRoutes :keyroute.path router-link :toroute.path {{ route.meta.title }} /router-link /nav /aside /template6️⃣ 退出登录时重置路由import { createRouter } from vue-router const router createRouter({ ... }) export function resetRouter() { const newRouter createRouter({ ... }) ;(router as any).matcher (newRouter as any).matcher }直接替换 matcher 是最干净的路由重置方式四、完整权限流程图┌─────────────┐ │ 访问页面 │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 有 Token │── 否 ──→ 跳转到 /login └──────┬──────┘ │ 是 ▼ ┌─────────────┐ │ 路由已添加 │── 是 ──→ 正常放行 └──────┬──────┘ │ 否 ▼ ┌─────────────┐ │ 获取用户信息 │ └──────┬──────┘ ▼ ┌─────────────┐ │ 过滤权限路由 │ └──────┬──────┘ ▼ ┌─────────────┐ │ 动态添加路由 │ └──────┬──────┘ ▼ ┌─────────────┐ │ 重新触发导航 │ └─────────────┘五、常见坑位与解决方案❌ 坑 1刷新页面后动态路由丢失原因动态添加的路由不在代码中持久化解决在beforeEach中判断每次刷新都重新添加❌ 坑 2404 路由匹配不到动态路由原因404 路由如果在静态路由中会优先匹配解决404 路由必须在动态路由添加之后注册❌ 坑 3addRoute 后路由不生效原因添加路由后没有重新触发导航解决return { ...to, replace: true }六、面试高频问答Q1动态路由和静态路由的区别静态路由在创建 router 时就注册动态路由在运行时通过addRoute添加。Q2为什么 404 路由要放在最后因为路由匹配是按注册顺序的放在前面会拦截所有未匹配的路由。Q3动态路由刷新后怎么保持在路由守卫中重新获取用户信息并再次addRoute。七、总结架构级路由懒加载是性能优化的第一步滚动行为控制是用户体验的细节动态权限路由是中后台系统的标配三者结合才能支撑企业级应用 下期预告第 19 篇HTTP 请求封装 —— Axios 在 Vue3 中的最佳实践