第9课 Vue Router 路由
- 为 Vue 应用配置多页面路由,包括嵌套路由
- 掌握声明式导航(
<router-link>)与编程式导航(useRouter) - 掌握路由传参(query 和 params)
- 使用导航守卫(
beforeEach)实现登录拦截
1. 路由概念与安装
作用:根据浏览器 URL 显示不同页面组件,实现单页应用(SPA)内的多页面切换。
安装(需在项目根目录执行):
bash
npm install vue-router@4
核心三件套:
| 动作 | 文件 | 关键代码 |
|---|---|---|
| 创建路由表 + 路由器 | src/router/index.js |
createRouter({ history, routes }) |
| 注册路由 | src/main.js |
app.use(router) |
| 路由出口 | src/App.vue |
<router-view /> |
2. 基本配置
2.1 路由表 (src/router/index.js)
js
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '../views/LoginView.vue'
const router = createRouter({
history: createWebHistory(), // History 模式,无 #
routes: [
{ path: '/', redirect: '/dashboard' },
{ path: '/login', name: 'login', component: LoginView },
{ path: '/dashboard', name: 'dashboard', component: () => import('../views/DashboardView.vue') }
]
})
export default router
路由记录字段:
path:URL 路径("门牌号")component:对应组件("房间")name:命名路由("昵称"),用于编程式跳转redirect:访问path时自动重定向
2.2 注册路由 (main.js)
js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
2.3 路由出口 (App.vue)
vue
<template>
<router-view />
</template>
3. 嵌套路由(主布局 + 子页面)
场景:后台系统有公共的顶部栏、侧边菜单,页面切换只改变内容区。
实现:
- 创建主布局组件
MainLayout.vue,内部也放置<router-view /> - 路由表配置
children,子页面的path不能以/开头
js
// router/index.js
import MainLayout from '../layouts/MainLayout.vue'
routes: [
{ path: '/login', name: 'login', component: LoginView }, // 独立,不套主布局
{
path: '/',
component: MainLayout,
redirect: '/dashboard',
children: [
{ path: 'dashboard', name: 'dashboard', component: () => import('../views/DashboardView.vue') },
{ path: 'users', name: 'users', component: () => import('../views/UserListView.vue') },
{ path: 'users/:id', name: 'user-detail', component: () => import('../views/UserDetailView.vue') }
]
}
]
:id是动态路由参数(见路由传参)- 子路由使用懒加载 :
() => import(...)可加快首屏加载
4. 声明式导航(<router-link>)
用于模板中用户点击切换页面的链接。
html
<router-link to="/dashboard" class="menu-item">首页</router-link>
特点:
- 不会触发整页刷新
- 当前路由对应的链接会自动添加
router-link-active类,可用于高亮样式:
css
.menu-item.router-link-active {
background: #4f46e5;
color: #fff;
}
对象写法 (推荐用 name):
html
<router-link :to="{ name: 'dashboard' }">首页</router-link>
5. 编程式导航(useRouter)
在脚本中控制跳转,如登录成功后、按钮点击后。
js
import { useRouter } from 'vue-router'
const router = useRouter()
// 跳转(添历史记录)
router.push('/dashboard')
router.push({ name: 'user-detail', params: { id: 3 } })
// 替换(不留历史记录,常用于登录成功)
router.replace({ name: 'login' })
// 前进/后退
router.back()
router.forward()
push vs replace :push 可后退,replace 不可后退。
6. 路由传参
6.1 路径参数 (params) ------ 动态路由
定义 :path: 'users/:id'
跳转 (必须用 name):
js
router.push({ name: 'user-detail', params: { id: user.id } })
读取:
js
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // 注意:值是字符串
6.2 查询参数 (query)
跳转:
js
router.push({ path: '/users', query: { keyword: '张', status: '在职' } })
// URL: /users?keyword=张&status=在职
读取:
js
route.query.keyword // '张'
对比:
| params | query | |
|---|---|---|
| URL形式 | /users/3 |
/users?id=3 |
| 定义 | 路由表 path 中用 :id |
无需额外定义 |
| 跳转语法 | { name, params } |
{ path/name, query } |
| 刷新后丢失? | 否(在 URL 里) | 否 |
params必须搭配name使用,不能直接用path传参。
7. 导航守卫(登录拦截)
全局前置守卫 beforeEach:每次路由跳转前检查,控制访问权限。
实现 (在 router/index.js 中):
js
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('isLogin') === 'true'
if (to.path !== '/login' && !isLogin) {
next({ name: 'login' }) // 未登录,重定向到登录页
} else {
next() // 放行
}
})
三个参数:
to:目标路由对象from:来源路由对象next():放行 / 重定向
关键点:
- 登录页必须放行(
to.path !== '/login'),否则会导致死循环 - 登录成功时写入
localStorage.setItem('isLogin', 'true') - 退出登录时清除
localStorage.removeItem('isLogin')(并跳转)
8. 项目文件结构速查
src/
├── main.js # 注册路由
├── App.vue # 一级出口
├── router/
│ └── index.js # 路由表 + 守卫
├── layouts/
│ └── MainLayout.vue # 主布局(公共外壳,包含二级出口)
└── views/
├── LoginView.vue
├── DashboardView.vue
├── UserListView.vue
└── UserDetailView.vue
9. 常见错误排查
| 现象 | 原因 | 解决 |
|---|---|---|
页面空白,终端报错 Failed to resolve component: RouterView |
未安装或未注册路由 | npm install vue-router@4 并 app.use(router) |
| 子页面出现但无主布局(顶栏/侧栏消失) | 子路由 path 以 / 开头 |
去掉开头的 / |
| params 传参后地址栏没有参数 | 使用 path 而非 name |
改为 { name, params } |
| 菜单点击不高亮 | 未正确使用 router-link-active 类 |
确保 CSS 选择器为 .menu-item.router-link-active |
| 未登录也能访问后台 | 守卫未配置或 localStorage 残留旧值 | 添加 beforeEach,测试前清空 localStorage |
| 守卫导致无法登录(无限重定向) | 登录页也被拦截 | 条件中增加 to.path !== '/login' |
| 登录后按后退回到登录页 | 登录跳转用了 push |
改用 router.replace |
| 详情页拿到的 id 参与计算出错 | route.params.id 是字符串 |
使用 Number(route.params.id) 转换 |
核心总结
- 路由 = 前台:根据 URL 映射组件
- 嵌套路由 = 公共布局 + 子页面(
children) - 导航 :
<router-link>(声明式)/useRouter(编程式) - 传参 :
query(?key=val)适合筛选条件;params(/users/:id)适合资源标识 - 守卫 :
beforeEach做登录拦截,记住放行登录页 - 登录成功用
replace,退出时清除登录态并跳转