Vue 路由跳转完全指南:8种跳转方式深度解析
Vue Router 提供了丰富灵活的路由跳转方式,从最简单的链接到最复杂的编程式导航。本文将全面解析所有跳转方式,并给出最佳实践建议。
一、快速概览:8种跳转方式对比
| 方式 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
1. <router-link> |
声明式 | 最简单,语义化 | 菜单、导航链接 |
2. router.push() |
编程式 | 灵活,可带参数 | 按钮点击、条件跳转 |
3. router.replace() |
编程式 | 替换历史记录 | 登录后跳转、表单提交 |
4. router.go() |
编程式 | 历史记录导航 | 前进后退、面包屑 |
| 5. 命名路由 | 声明式/编程式 | 解耦路径 | 大型项目、重构友好 |
| 6. 路由别名 | 声明式 | 多个路径指向同一路由 | 兼容旧URL、SEO优化 |
| 7. 重定向 | 配置式 | 自动跳转 | 默认路由、权限控制 |
| 8. 导航守卫 | 拦截式 | 控制跳转流程 | 权限验证、数据预取 |
二、声明式导航:<router-link>
2.1 基础用法
vue
<template>
<div class="navigation">
<!-- 1. 基础路径跳转 -->
<router-link to="/home">首页</router-link>
<!-- 2. 带查询参数 -->
<router-link to="/user?tab=profile&page=2">
用户(第2页)
</router-link>
<!-- 3. 带哈希 -->
<router-link to="/about#team">关于我们(团队)</router-link>
<!-- 4. 动态路径 -->
<router-link :to="`/product/${productId}`">
产品详情
</router-link>
<!-- 5. 自定义激活样式 -->
<router-link
to="/dashboard"
active-class="active-link"
exact-active-class="exact-active"
>
控制面板
</router-link>
<!-- 6. 替换历史记录 -->
<router-link to="/login" replace>
登录(无返回)
</router-link>
<!-- 7. 自定义标签 -->
<router-link to="/help" custom v-slot="{ navigate, isActive }">
<button
@click="navigate"
:class="{ active: isActive }"
class="custom-button"
>
帮助中心
</button>
</router-link>
</div>
</template>
<script>
export default {
data() {
return {
productId: 123
}
}
}
</script>
<style scoped>
.active-link {
color: #1890ff;
font-weight: bold;
}
.exact-active {
border-bottom: 2px solid #1890ff;
}
.custom-button {
padding: 8px 16px;
background: #f5f5f5;
border: none;
border-radius: 4px;
cursor: pointer;
}
.custom-button.active {
background: #1890ff;
color: white;
}
</style>
2.2 高级特性
vue
<template>
<!-- 1. 事件监听 -->
<router-link
to="/cart"
@click="handleClick"
@mouseenter="handleHover"
>
购物车
</router-link>
<!-- 2. 禁止跳转 -->
<router-link
to="/restricted"
:event="hasPermission ? 'click' : ''"
:class="{ disabled: !hasPermission }"
>
管理员入口
</router-link>
<!-- 3. 组合式API使用 -->
<router-link
v-for="nav in navList"
:key="nav.path"
:to="nav.path"
:class="getNavClass(nav)"
>
{{ nav.name }}
<span v-if="nav.badge" class="badge">{{ nav.badge }}</span>
</router-link>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const hasPermission = computed(() => true) // 权限逻辑
const navList = [
{ path: '/', name: '首页', exact: true },
{ path: '/products', name: '产品', badge: 'New' },
{ path: '/about', name: '关于' }
]
const getNavClass = (nav) => {
const isActive = nav.exact
? route.path === nav.path
: route.path.startsWith(nav.path)
return {
'nav-item': true,
'nav-active': isActive,
'has-badge': !!nav.badge
}
}
</script>
三、编程式导航
3.1 router.push() - 最常用的跳转
javascript
// 方法1:路径字符串
router.push('/home')
router.push('/user/123')
router.push('/search?q=vue')
router.push('/about#contact')
// 方法2:路由对象(推荐)
router.push({
path: '/user/123'
})
// 方法3:命名路由(最佳实践)
router.push({
name: 'UserProfile',
params: { id: 123 }
})
// 方法4:带查询参数
router.push({
path: '/search',
query: {
q: 'vue router',
page: 2,
sort: 'desc'
}
})
// 方法5:带哈希
router.push({
path: '/document',
hash: '#installation'
})
// 方法6:带状态(不显示在URL中)
router.push({
name: 'Checkout',
state: {
cartItems: ['item1', 'item2'],
discountCode: 'SAVE10'
}
})
// 方法7:动态路径
const userId = 456
const userType = 'vip'
router.push({
path: `/user/${userId}`,
query: { type: userType }
})
// 方法8:条件跳转
function navigateTo(target) {
if (userStore.isLoggedIn) {
router.push(target)
} else {
router.push({
path: '/login',
query: { redirect: target.path || target }
})
}
}
3.2 router.replace() - 替换当前历史记录
javascript
// 场景1:登录后跳转(不让用户返回登录页)
function handleLogin() {
login().then(() => {
router.replace('/dashboard') // 替换登录页记录
})
}
// 场景2:表单提交后
function submitForm() {
submit().then(() => {
// 提交成功后,替换当前页
router.replace({
name: 'Success',
query: { formId: this.formId }
})
})
}
// 场景3:重定向中间页
// 访问 /redirect?target=/dashboard
router.beforeEach((to, from, next) => {
if (to.path === '/redirect') {
const target = to.query.target
router.replace(target || '/')
return
}
next()
})
// 场景4:错误页面处理
function loadProduct(id) {
fetchProduct(id).catch(error => {
// 错误时替换到错误页
router.replace({
name: 'Error',
params: { message: '产品加载失败' }
})
})
}
3.3 router.go() - 历史记录导航
javascript
// 前进后退
router.go(1) // 前进1步
router.go(-1) // 后退1步
router.go(-3) // 后退3步
router.go(0) // 刷新当前页
// 快捷方法
router.back() // 后退 = router.go(-1)
router.forward() // 前进 = router.go(1)
// 实际应用
const navigationHistory = []
// 记录导航历史
router.afterEach((to, from) => {
navigationHistory.push({
from: from.fullPath,
to: to.fullPath,
timestamp: Date.now()
})
})
// 返回指定步骤
function goBackSteps(steps) {
if (router.currentRoute.value.meta.preventBack) {
alert('当前页面禁止返回')
return
}
router.go(-steps)
}
// 返回首页
function goHome() {
const currentDepth = navigationHistory.length
router.go(-currentDepth + 1) // 保留首页
}
// 面包屑导航
const breadcrumbs = computed(() => {
const paths = []
let current = router.currentRoute.value
while (current) {
paths.unshift(current)
// 根据meta中的parent字段查找父路由
current = routes.find(r => r.name === current.meta?.parent)
}
return paths
})
3.4 编程式导航最佳实践
javascript
// 1. 封装导航工具函数
export const nav = {
// 带权限检查的跳转
pushWithAuth(to, requiredRole = null) {
if (!authStore.isLoggedIn) {
return router.push({
path: '/login',
query: { redirect: typeof to === 'string' ? to : to.path }
})
}
if (requiredRole && !authStore.hasRole(requiredRole)) {
return router.push('/unauthorized')
}
return router.push(to)
},
// 带确认的跳转
pushWithConfirm(to, message = '确定离开当前页面?') {
return new Promise((resolve) => {
if (confirm(message)) {
router.push(to).then(resolve)
}
})
},
// 新标签页打开
openInNewTab(to) {
const route = router.resolve(to)
window.open(route.href, '_blank')
},
// 带Loading的跳转
pushWithLoading(to) {
loadingStore.show()
return router.push(to).finally(() => {
loadingStore.hide()
})
}
}
// 2. 使用示例
// 组件中使用
methods: {
viewProductDetail(product) {
nav.pushWithAuth({
name: 'ProductDetail',
params: { id: product.id }
}, 'user')
},
editProduct(product) {
nav.pushWithConfirm(
{ name: 'ProductEdit', params: { id: product.id } },
'有未保存的更改,确定要编辑吗?'
)
}
}
四、命名路由跳转
4.1 配置和使用
javascript
// router/index.js
const routes = [
{
path: '/',
name: 'Home', // 命名路由
component: Home
},
{
path: '/user/:userId',
name: 'UserProfile', // 命名路由
component: UserProfile,
props: true
},
{
path: '/product/:category/:id',
name: 'ProductDetail', // 命名路由
component: ProductDetail
},
{
path: '/search',
name: 'Search',
component: Search,
props: route => ({ query: route.query.q })
}
]
// 组件中使用命名路由
// 声明式
<router-link :to="{ name: 'UserProfile', params: { userId: 123 } }">
用户资料
</router-link>
// 编程式
router.push({
name: 'ProductDetail',
params: {
category: 'electronics',
id: 456
}
})
// 带查询参数
router.push({
name: 'Search',
query: {
q: 'vue router',
sort: 'price'
}
})
4.2 命名路由的优势
javascript
// 优势1:路径解耦,重构方便
// 旧路径:/user/:id
// 新路径:/profile/:id
// 只需修改路由配置,无需修改跳转代码
// 优势2:清晰的参数传递
router.push({
name: 'OrderCheckout',
params: {
orderId: 'ORD-2024-001',
step: 'payment' // 参数名清晰
},
query: {
coupon: 'SAVE20',
source: 'cart'
}
})
// 优势3:嵌套路由跳转
const routes = [
{
path: '/admin',
name: 'Admin',
component: AdminLayout,
children: [
{
path: 'users',
name: 'AdminUsers', // 全名:AdminUsers
component: AdminUsers
},
{
path: 'settings',
name: 'AdminSettings',
component: AdminSettings
}
]
}
]
// 跳转到嵌套路由
router.push({ name: 'AdminUsers' }) // 自动找到完整路径
五、路由别名和重定向
5.1 路由别名
javascript
// 多个路径指向同一组件
const routes = [
{
path: '/home',
alias: ['/index', '/main', '/'], // 多个别名
component: Home,
meta: { title: '首页' }
},
{
path: '/about-us',
alias: '/company', // 单个别名
component: About
},
{
path: '/products/:id',
alias: '/items/:id', // 带参数的别名
component: ProductDetail
}
]
// 实际应用场景
const routes = [
// 场景1:SEO优化 - 多个关键词
{
path: '/vue-tutorial',
alias: ['/vue-教程', '/vue-入门', '/vue-guide'],
component: Tutorial
},
// 场景2:兼容旧URL
{
path: '/new-url',
alias: ['/old-url', '/legacy-url', '/deprecated-path'],
component: NewComponent,
meta: {
canonical: '/new-url', // 告诉搜索引擎主URL
redirect301: true
}
},
// 场景3:多语言路径
{
path: '/en/about',
alias: ['/zh/about', '/ja/about', '/ko/about'],
component: About,
beforeEnter(to, from, next) {
// 根据路径设置语言
const lang = to.path.split('/')[1]
i18n.locale = lang
next()
}
}
]
5.2 路由重定向
javascript
// 1. 简单重定向
const routes = [
{
path: '/home',
redirect: '/dashboard' // 访问/home跳转到/dashboard
},
{
path: '/',
redirect: '/home' // 根路径重定向
}
]
// 2. 命名路由重定向
const routes = [
{
path: '/user',
redirect: { name: 'UserList' } // 重定向到命名路由
}
]
// 3. 函数式重定向(动态)
const routes = [
{
path: '/user/:id',
redirect: to => {
// 根据参数动态重定向
const userType = getUserType(to.params.id)
if (userType === 'admin') {
return { name: 'AdminProfile', params: { id: to.params.id } }
} else {
return { name: 'UserProfile', params: { id: to.params.id } }
}
}
}
]
// 4. 实际应用场景
const routes = [
// 场景1:版本升级重定向
{
path: '/v1/products/:id',
redirect: to => `/products/${to.params.id}?version=v1`
},
// 场景2:权限重定向
{
path: '/admin',
redirect: to => {
if (authStore.isAdmin) {
return '/admin/dashboard'
} else {
return '/unauthorized'
}
}
},
// 场景3:临时重定向(维护页面)
{
path: '/under-maintenance',
component: Maintenance,
meta: { maintenance: true }
},
{
path: '/',
redirect: () => {
if (isMaintenanceMode) {
return '/under-maintenance'
}
return '/home'
}
},
// 场景4:404页面捕获
{
path: '/:pathMatch(.*)*', // 捕获所有未匹配路径
name: 'NotFound',
component: NotFound,
beforeEnter(to, from, next) {
// 记录404访问
log404(to.fullPath)
next()
}
}
]
六、导航守卫控制跳转
6.1 完整的守卫流程
javascript
// 完整的导航解析流程
const router = createRouter({
routes,
// 全局配置
})
// 1. 导航被触发
// 2. 在失活的组件里调用 beforeRouteLeave 守卫
// 3. 调用全局的 beforeEach 守卫
// 4. 在重用的组件里调用 beforeRouteUpdate 守卫
// 5. 在路由配置里调用 beforeEnter 守卫
// 6. 解析异步路由组件
// 7. 在被激活的组件里调用 beforeRouteEnter 守卫
// 8. 调用全局的 beforeResolve 守卫
// 9. 导航被确认
// 10. 调用全局的 afterEach 守卫
// 11. 触发 DOM 更新
// 12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数
// 实际应用:权限控制流程
const routes = [
{
path: '/admin',
component: AdminLayout,
meta: { requiresAuth: true, requiresAdmin: true },
beforeEnter: (to, from, next) => {
// 路由独享守卫
if (!authStore.isAdmin) {
next('/unauthorized')
} else {
next()
}
},
children: [
{
path: 'dashboard',
component: AdminDashboard,
meta: { requiresSuperAdmin: true }
}
]
}
]
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 1. 页面标题
document.title = to.meta.title || '默认标题'
// 2. 权限验证
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
// 3. 管理员权限
if (to.meta.requiresAdmin && !authStore.isAdmin) {
next('/unauthorized')
return
}
// 4. 维护模式检查
if (to.meta.maintenance && !isMaintenanceMode) {
next(from.path || '/')
return
}
// 5. 滚动行为重置
if (to.meta.resetScroll) {
window.scrollTo(0, 0)
}
next()
})
// 全局解析守卫(适合获取数据)
router.beforeResolve(async (to, from, next) => {
// 预取数据
if (to.meta.requiresData) {
try {
await store.dispatch('fetchRequiredData', to.params)
next()
} catch (error) {
next('/error')
}
} else {
next()
}
})
// 全局后置守卫
router.afterEach((to, from) => {
// 1. 页面访问统计
analytics.trackPageView(to.fullPath)
// 2. 关闭加载动画
hideLoading()
// 3. 保存导航历史
saveNavigationHistory(to, from)
// 4. 更新面包屑
updateBreadcrumb(to)
})
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 不能访问 this,因为组件还没创建
// 但可以通过回调访问
next(vm => {
// 通过 vm 访问组件实例
vm.loadData(to.params.id)
})
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 可以访问组件实例 this
this.productId = to.params.id
this.fetchProductData()
next()
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 this
if (this.hasUnsavedChanges) {
const answer = confirm('有未保存的更改,确定离开吗?')
if (!answer) {
next(false) // 取消导航
return
}
}
next()
}
}
6.2 守卫组合实践
javascript
// 封装守卫函数
const guard = {
// 认证守卫
auth: (to, from, next) => {
if (!authStore.isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
},
// 权限守卫
role: (requiredRole) => (to, from, next) => {
if (!authStore.hasRole(requiredRole)) {
next('/forbidden')
} else {
next()
}
},
// 功能开关守卫
feature: (featureName) => (to, from, next) => {
if (!featureToggle.isEnabled(featureName)) {
next('/feature-disabled')
} else {
next()
}
},
// 数据预取守卫
prefetch: (dataKey) => async (to, from, next) => {
try {
await store.dispatch(`fetch${dataKey}`, to.params)
next()
} catch (error) {
next('/error')
}
}
}
// 在路由中使用
const routes = [
{
path: '/admin',
component: AdminLayout,
beforeEnter: [guard.auth, guard.role('admin')],
children: [
{
path: 'analytics',
component: Analytics,
beforeEnter: guard.feature('analytics')
}
]
},
{
path: '/product/:id',
component: ProductDetail,
beforeEnter: guard.prefetch('Product')
}
]
七、高级跳转技巧
7.1 路由传参的多种方式
javascript
// 方式1:params(路径参数)
// 路由配置:/user/:id
router.push({ path: '/user/123' })
// 或
router.push({ name: 'User', params: { id: 123 } })
// 方式2:query(查询参数)
router.push({ path: '/search', query: { q: 'vue', page: 2 } })
// 方式3:props(推荐方式)
const routes = [
{
path: '/user/:id',
name: 'User',
component: User,
props: true // params 转为 props
},
{
path: '/product/:id',
name: 'Product',
component: Product,
props: route => ({
id: Number(route.params.id),
preview: route.query.preview === 'true'
})
}
]
// 方式4:state(不显示在URL中)
router.push({
name: 'Checkout',
state: {
cartItems: [...],
discount: 'SAVE10',
source: 'promotion'
}
})
// 接收state
const route = useRoute()
const cartItems = route.state?.cartItems || []
// 方式5:meta(路由元信息)
const routes = [
{
path: '/premium',
component: Premium,
meta: {
requiresSubscription: true,
subscriptionLevel: 'gold'
}
}
]
// 方式6:动态props传递
function navigateWithProps(target, props) {
// 临时存储props
const propKey = `temp_props_${Date.now()}`
sessionStorage.setItem(propKey, JSON.stringify(props))
router.push({
path: target,
query: { _props: propKey }
})
}
// 在目标组件中读取
const route = useRoute()
const propsData = computed(() => {
const propKey = route.query._props
if (propKey) {
const data = JSON.parse(sessionStorage.getItem(propKey) || '{}')
sessionStorage.removeItem(propKey)
return data
}
return {}
})
7.2 跳转动画和过渡
vue
<template>
<!-- 路由过渡动画 -->
<router-view v-slot="{ Component, route }">
<transition
:name="route.meta.transition || 'fade'"
mode="out-in"
@before-enter="beforeEnter"
@after-enter="afterEnter"
>
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</template>
<script>
export default {
methods: {
beforeEnter() {
// 动画开始前
document.body.classList.add('page-transition')
},
afterEnter() {
// 动画结束后
document.body.classList.remove('page-transition')
}
}
}
</script>
<style>
/* 淡入淡出 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 滑动效果 */
.slide-left-enter-active,
.slide-left-leave-active {
transition: transform 0.3s ease;
}
.slide-left-enter-from {
transform: translateX(100%);
}
.slide-left-leave-to {
transform: translateX(-100%);
}
/* 缩放效果 */
.zoom-enter-active,
.zoom-leave-active {
transition: all 0.3s ease;
}
.zoom-enter-from {
opacity: 0;
transform: scale(0.9);
}
.zoom-leave-to {
opacity: 0;
transform: scale(1.1);
}
</style>
7.3 滚动行为控制
javascript
const router = createRouter({
history: createWebHistory(),
routes,
// 滚动行为控制
scrollBehavior(to, from, savedPosition) {
// 1. 返回按钮保持位置
if (savedPosition) {
return savedPosition
}
// 2. 哈希导航
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth' // 平滑滚动
}
}
// 3. 特定路由滚动到顶部
if (to.meta.scrollToTop !== false) {
return { top: 0, behavior: 'smooth' }
}
// 4. 保持当前位置
if (to.meta.keepScroll) {
return false
}
// 5. 滚动到指定元素
if (to.meta.scrollTo) {
return {
el: to.meta.scrollTo,
offset: { x: 0, y: 20 } // 偏移量
}
}
// 默认行为
return { left: 0, top: 0 }
}
})
八、实际项目应用
8.1 电商网站路由跳转示例
javascript
// router/index.js - 电商路由配置
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { title: '首页 - 电商平台' }
},
{
path: '/products',
name: 'ProductList',
component: ProductList,
meta: {
title: '商品列表',
keepAlive: true // 保持组件状态
},
props: route => ({
category: route.query.category,
sort: route.query.sort || 'default',
page: parseInt(route.query.page) || 1
})
},
{
path: '/product/:id(\\d+)', // 只匹配数字ID
name: 'ProductDetail',
component: ProductDetail,
meta: {
title: '商品详情',
requiresAuth: false
},
beforeEnter: async (to, from, next) => {
// 验证商品是否存在
try {
await productStore.fetchProduct(to.params.id)
next()
} catch (error) {
next('/404')
}
}
},
{
path: '/cart',
name: 'ShoppingCart',
component: ShoppingCart,
meta: {
title: '购物车',
requiresAuth: true
}
},
{
path: '/checkout',
name: 'Checkout',
component: Checkout,
meta: {
title: '结算',
requiresAuth: true,
requiresCart: true // 需要购物车有商品
},
beforeEnter: (to, from, next) => {
if (cartStore.isEmpty) {
next({ name: 'ShoppingCart' })
} else {
next()
}
}
},
{
path: '/order/:orderId',
name: 'OrderDetail',
component: OrderDetail,
meta: {
title: '订单详情',
requiresAuth: true,
scrollToTop: true
}
},
// ... 其他路由
]
// 组件中使用
export default {
methods: {
// 查看商品
viewProduct(product) {
this.$router.push({
name: 'ProductDetail',
params: { id: product.id },
query: {
source: 'list',
ref: this.$route.fullPath
}
})
},
// 加入购物车
addToCart(product) {
cartStore.add(product).then(() => {
// 显示成功提示后跳转
this.$message.success('加入购物车成功')
this.$router.push({
name: 'ShoppingCart',
query: { added: product.id }
})
})
},
// 立即购买
buyNow(product) {
cartStore.add(product).then(() => {
this.$router.replace({
name: 'Checkout',
query: { quick: 'true' }
})
})
},
// 继续购物
continueShopping() {
// 返回之前的商品列表,保持筛选状态
const returnTo = this.$route.query.ref || '/products'
this.$router.push(returnTo)
}
}
}
8.2 后台管理系统路由示例
javascript
// 动态路由加载
let dynamicRoutesLoaded = false
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { guest: true }
},
{
path: '/',
component: Layout,
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '仪表板', icon: 'dashboard' }
}
]
}
]
})
// 动态加载路由
async function loadDynamicRoutes() {
if (dynamicRoutesLoaded) return
try {
const userInfo = await authStore.getUserInfo()
const menus = await menuStore.fetchUserMenus(userInfo.role)
// 转换菜单为路由
const routes = transformMenusToRoutes(menus)
// 动态添加路由
routes.forEach(route => {
router.addRoute('Layout', route)
})
dynamicRoutesLoaded = true
// 如果当前路由不存在,重定向到首页
if (!router.hasRoute(router.currentRoute.value.name)) {
router.replace('/')
}
} catch (error) {
console.error('加载动态路由失败:', error)
router.push('/error')
}
}
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 显示加载中
loadingBar.start()
// 登录检查
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
next({
name: 'Login',
query: { redirect: to.fullPath }
})
return
}
// 游客页面检查(已登录用户不能访问登录页)
if (to.meta.guest && authStore.isLoggedIn) {
next('/')
return
}
// 加载动态路由
if (!dynamicRoutesLoaded && authStore.isLoggedIn) {
await loadDynamicRoutes()
// 动态路由加载后重新跳转
next(to.fullPath)
return
}
// 权限检查
if (to.meta.permissions) {
const hasPermission = checkPermission(to.meta.permissions)
if (!hasPermission) {
next('/403')
return
}
}
next()
})
router.afterEach((to) => {
// 设置页面标题
document.title = to.meta.title ? `${to.meta.title} - 后台管理` : '后台管理'
// 关闭加载
loadingBar.finish()
// 记录访问日志
logAccess(to)
})
九、常见问题与解决方案
9.1 路由跳转常见错误
javascript
// 错误1:重复跳转相同路由
// ❌ 会报错:NavigationDuplicated
router.push('/current-path')
// ✅ 解决方案:检查当前路由
function safePush(to) {
if (router.currentRoute.value.path !== to) {
router.push(to)
}
}
// 错误2:params 和 path 同时使用
// ❌ params 会被忽略
router.push({
path: '/user/123',
params: { id: 456 } // 这个被忽略!
})
// ✅ 正确:使用命名路由
router.push({
name: 'User',
params: { id: 456 }
})
// 错误3:路由未找到
// ❌ 跳转到不存在的路由
router.push('/non-existent')
// ✅ 解决方案:检查路由是否存在
function safeNavigate(to) {
const resolved = router.resolve(to)
if (resolved.matched.length > 0) {
router.push(to)
} else {
router.push('/404')
}
}
// 错误4:组件未加载
// ❌ 异步组件加载失败
router.push({ name: 'AsyncComponent' })
// ✅ 解决方案:添加错误处理
router.push({ name: 'AsyncComponent' }).catch(error => {
if (error.name === 'NavigationDuplicated') {
// 忽略重复导航错误
return
}
// 其他错误处理
console.error('导航失败:', error)
router.push('/error')
})
9.2 性能优化建议
javascript
// 1. 路由懒加载
const routes = [
{
path: '/heavy-page',
component: () => import(/* webpackChunkName: "heavy" */ '@/views/HeavyPage.vue')
}
]
// 2. 组件预加载
// 在适当的时候预加载路由组件
function prefetchRoute(routeName) {
const route = router.getRoutes().find(r => r.name === routeName)
if (route && typeof route.components?.default === 'function') {
route.components.default()
}
}
// 在鼠标悬停时预加载
<router-link
:to="{ name: 'HeavyPage' }"
@mouseenter="prefetchRoute('HeavyPage')"
>
重量级页面
</router-link>
// 3. 路由缓存
// 使用 keep-alive 缓存常用页面
<router-view v-slot="{ Component, route }">
<keep-alive :include="cachedRoutes">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</router-view>
// 4. 滚动位置缓存
const scrollPositions = new Map()
router.beforeEach((to, from) => {
// 保存离开时的滚动位置
if (from.meta.keepScroll) {
scrollPositions.set(from.fullPath, {
x: window.scrollX,
y: window.scrollY
})
}
})
router.afterEach((to, from) => {
// 恢复滚动位置
if (to.meta.keepScroll && from.meta.keepScroll) {
const position = scrollPositions.get(to.fullPath)
if (position) {
window.scrollTo(position.x, position.y)
}
}
})
十、总结
路由跳转选择指南
javascript
// 根据场景选择跳转方式
const navigationGuide = {
// 场景:普通链接
普通链接: '使用 <router-link>',
// 场景:按钮点击跳转
按钮点击: '使用 router.push()',
// 场景:表单提交后
表单提交: '使用 router.replace() 避免重复提交',
// 场景:返回上一步
返回操作: '使用 router.back() 或 router.go(-1)',
// 场景:权限验证后跳转
权限跳转: '在导航守卫中控制',
// 场景:动态路由
动态路由: '使用 router.addRoute() 动态添加',
// 场景:404处理
未找到页面: '配置 catch-all 路由',
// 场景:平滑过渡
页面过渡: '使用 <transition> 包裹 <router-view>'
}
// 最佳实践总结
const bestPractices = `
1. 尽量使用命名路由,提高代码可维护性
2. 复杂参数传递使用 props 而不是直接操作 $route
3. 重要跳转添加 loading 状态和错误处理
4. 合理使用导航守卫进行权限控制
5. 移动端考虑滑动返回等交互
6. SEO 重要页面使用静态路径
7. 适当使用路由缓存提升性能
8. 监控路由跳转错误和异常
`
Vue Router 提供了强大而灵活的路由跳转机制,掌握各种跳转方式并根据场景合理选择,可以显著提升应用的用户体验和开发效率。记住:简单的用声明式,复杂的用编程式,全局的控制用守卫。