4、路由架构:安装Vue Router,设计模块化路由配置,实现动态路由加载

路由,说白了就是单页应用的「导航地图」。没有它,你的页面就是一潭死水,点哪都跳不动。我个人习惯在项目搭建初期就把路由架构想清楚,不然后面改起来,那叫一个痛苦。

我记得刚带团队那会儿,有个同事把所有路由都塞在一个文件里,足足两千行。每次加个页面都得翻半天,还经常合并冲突。嗯,这就是典型的「能用就行」心态,但维护成本太高了。

4.1 安装与基础配置

先装包,这个没啥好说的:

npm install vue-router@4

装完之后,在 src 下建个 router 文件夹。我习惯把路由相关的逻辑都放这里,别散落在各处。

创建 src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
小提示:createWebHistory 而不是 hash 模式。虽然 hash 模式兼容性更好,但 URL 里带个 # 实在太丑了。生产环境配好 Nginx 就行。

然后在 main.js 里挂上:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

4.2 模块化路由设计

项目一大,路由肯定不能全写在一个文件里。我见过最夸张的项目,路由文件里还混着权限判断、页面标题、缓存配置……简直是个大杂烩。

我的做法是按业务模块拆分。比如一个后台管理系统,可以拆成这样:

src/router/
├── index.js          # 主入口,合并所有模块
├── modules/
│   ├── user.js       # 用户管理模块
│   ├── order.js      # 订单模块
│   ├── product.js    # 商品模块
│   └── dashboard.js  # 仪表盘模块
└── dynamic.js        # 动态路由逻辑

每个模块文件长这样:

// src/router/modules/user.js
export default [
  {
    path: '/user',
    name: 'User',
    component: () => import('@/views/user/UserList.vue'),
    meta: { title: '用户管理', icon: 'user' }
  },
  {
    path: '/user/:id',
    name: 'UserDetail',
    component: () => import('@/views/user/UserDetail.vue'),
    meta: { title: '用户详情', hidden: true }
  }
]

然后在 index.js 里合并:

import userRoutes from './modules/user'
import orderRoutes from './modules/order'
import productRoutes from './modules/product'
import dashboardRoutes from './modules/dashboard'

const routes = [
  ...dashboardRoutes,
  ...userRoutes,
  ...orderRoutes,
  ...productRoutes
]
核心思路:每个模块只关心自己的路由,互不干扰。新增一个业务模块,就新建一个路由文件,然后在 index.js 里引入。这样团队协作时,每个人只管自己的模块,冲突概率大大降低。

4.3 动态路由加载

动态路由,说白了就是根据用户权限来「组装」路由。比如管理员能看到「系统设置」,普通用户就看不到。

我曾经踩过一个坑:把动态路由和静态路由混在一起初始化,结果用户还没登录,路由表里就已经有了「系统设置」的路径。虽然前端做了权限拦截,但 URL 直接访问还是能跳过去……嗯,这就是典型的「前端防君子不防小人」。

正确的做法是:

  1. 初始化时只加载公共路由(登录页、404等)
  2. 用户登录后,根据角色获取可访问的路由配置
  3. router.addRoute() 动态添加

来看代码:

// src/router/dynamic.js
import store from '@/store'

// 根据角色获取路由
export function getAsyncRoutes(role) {
  // 这里可以从后端获取,也可以本地配置
  const allRoutes = {
    admin: [
      { path: '/settings', component: () => import('@/views/Settings.vue') },
      { path: '/logs', component: () => import('@/views/Logs.vue') }
    ],
    editor: [
      { path: '/editor', component: () => import('@/views/Editor.vue') }
    ]
  }
  return allRoutes[role] || []
}

// 动态添加路由
export function addDynamicRoutes(role) {
  const routes = getAsyncRoutes(role)
  routes.forEach(route => {
    router.addRoute(route)
  })
}

在登录成功后调用:

// 登录成功后的逻辑
async function handleLogin(role) {
  await store.dispatch('user/login')
  addDynamicRoutes(role)
  // 跳转到首页
  router.push('/')
}
注意:动态添加的路由,如果刷新页面就没了。所以需要在 App.vueonMounted 里重新加载。我一般会在路由守卫里做这件事,每次跳转前检查一下路由表是否完整。

4.4 路由守卫与权限控制

路由守卫,就是「门卫」。每次跳转前,它都会问一句:「你谁啊?要去哪?有票吗?」

我习惯在 router/index.js 里统一配置:

router.beforeEach(async (to, from, next) => {
  // 设置页面标题
  document.title = to.meta.title || '默认标题'

  // 判断是否需要登录
  if (to.meta.requiresAuth) {
    const token = localStorage.getItem('token')
    if (!token) {
      next({ name: 'Login', query: { redirect: to.fullPath } })
      return
    }
  }

  // 判断角色权限
  if (to.meta.roles) {
    const userRole = store.state.user.role
    if (!to.meta.roles.includes(userRole)) {
      next({ name: '403' })
      return
    }
  }

  next()
})
避坑指南:我曾经在路由守卫里写了异步请求,结果页面跳转卡了半秒。后来把所有异步操作都放到 beforeResolve 里,用户体验好多了。说白了,beforeEach 只做同步判断,异步操作往后放。

4.5 路由元信息的最佳实践

meta 字段是个好东西,但别乱用。我见过有人把接口地址、组件配置、甚至 CSS 样式都塞进去……那还不如直接写个配置文件。

我常用的 meta 字段:

字段 类型 说明
title String 页面标题,用于浏览器标签和面包屑
icon String 菜单图标,侧边栏用
hidden Boolean 是否在菜单中隐藏(比如详情页)
requiresAuth Boolean 是否需要登录
roles Array 允许访问的角色列表
keepAlive Boolean 是否缓存页面(配合 <keep-alive>

举个例子:

{
  path: '/order/detail/:id',
  name: 'OrderDetail',
  component: () => import('@/views/order/OrderDetail.vue'),
  meta: {
    title: '订单详情',
    hidden: true,        // 不在菜单显示
    requiresAuth: true,  // 需要登录
    roles: ['admin', 'editor'],  // 只有这两种角色能看
    keepAlive: false     // 每次进入都重新加载
  }
}
总结一下:路由架构这件事,说白了就是「分而治之」。模块化拆分让代码好维护,动态加载让权限可控,元信息让功能可配。你想想看,如果一开始就把这些想清楚,后面加功能、改权限,是不是轻松多了?

嗯,这一章就到这。下一章我们聊聊状态管理,Vuex 和 Pinia 到底怎么选,以及如何设计一个可扩展的 Store 架构。