使用 scrollBehavior 配置路由切换时的滚动位置:
const router = new VueRouter({
routes: [...],
scrollBehavior(to, from, savedPosition) {
// 返回滚动位置
}
})| 参数 | 类型 | 说明 |
|---|---|---|
to | Route | 目标路由对象 |
from | Route | 来源路由对象 |
savedPosition | Object | null | 浏览器前进/后退时的保存位置 |
const router = new VueRouter({
routes: [...],
scrollBehavior(to, from, savedPosition) {
// 1. 返回 { x, y } - 滚动到指定位置
return { x: 0, y: 0 }
// 2. 返回 savedPosition - 恢复保存的位置
if (savedPosition) {
return savedPosition
}
// 3. 返回 { selector } - 滚动到锚点
if (to.hash) {
return { selector: to.hash }
}
// 4. 返回 false - 不滚动
return false
}
})scrollBehavior(to, from, savedPosition) {
return { x: 0, y: 0 }
}scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash,
behavior: 'smooth' // 平滑滚动
}
}
}scrollBehavior(to, from, savedPosition) {
// 特定路由保持位置
if (to.meta.keepScroll) {
return savedPosition || false
}
// 默认滚动到顶部
return { x: 0, y: 0 }
}scrollBehavior(to, from, savedPosition) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: Home,
meta: { title: '首页' }
},
{
path: '/article/:id',
component: Article,
meta: { title: '文章详情', keepScroll: true }
}
],
scrollBehavior(to, from, savedPosition) {
// 前进/后退时恢复位置
if (savedPosition) {
return savedPosition
}
// 滚动到锚点
if (to.hash) {
return {
selector: to.hash,
behavior: 'smooth',
offset: { x: 0, y: 60 } // 偏移量(固定头部高度)
}
}
// 特定路由保持位置
if (to.meta.keepScroll) {
return false
}
// 默认滚动到顶部
return { x: 0, y: 0, behavior: 'smooth' }
}
})在路由配置中通过 meta 属性定义:
const routes = [
{
path: '/home',
component: Home,
meta: {
title: '首页',
requiresAuth: false
}
},
{
path: '/admin',
component: Admin,
meta: {
title: '管理后台',
requiresAuth: true,
roles: ['admin', 'super_admin']
}
}
]// 在路由守卫中
router.beforeEach((to, from, next) => {
// 获取标题
document.title = to.meta.title || '默认标题'
// 检查是否需要认证
if (to.meta.requiresAuth) {
// ...
}
next()
})Vue Router 3 中子路由不会自动继承父路由的 meta——每个路由记录的 meta 相互独立,to.meta 只是最深层匹配记录的 meta(meta 合并是 Vue Router 4 的行为)。要检查任意层级的 meta,需要遍历 to.matched,见下节。
const routes = [
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true },
children: [
{
path: 'user',
component: UserManage,
meta: { title: '用户管理' }
// 注意:to.meta 此时是 { title: '用户管理' },
// 父路由的 requiresAuth 需要通过 to.matched 逐层检查
}
]
}
]使用 to.matched 遍历所有路由记录:
router.beforeEach((to, from, next) => {
// 检查任意层级是否需要认证
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!isAuthenticated()) {
next('/login')
return
}
}
next()
})const routes = [
{
path: '/',
component: Layout,
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'Dashboard',
component: Dashboard,
meta: {
title: '仪表盘',
icon: 'dashboard',
breadcrumb: ['首页', '仪表盘']
}
},
{
path: 'user',
name: 'UserManage',
component: UserManage,
meta: {
title: '用户管理',
icon: 'user',
roles: ['admin'],
breadcrumb: ['首页', '用户管理']
}
},
{
path: 'user/:id',
name: 'UserDetail',
component: UserDetail,
meta: {
title: '用户详情',
hidden: true, // 不在菜单显示
activeMenu: '/user' // 高亮的菜单项
}
}
]
}
]
// 使用 meta 信息
router.beforeEach((to, from, next) => {
// 设置标题
const title = to.meta.title
document.title = title ? `${title} - 管理系统` : '管理系统'
// 权限检查
if (to.matched.some(record => record.meta.roles)) {
const roles = to.meta.roles
const userRole = store.state.user.role
if (!roles.includes(userRole)) {
next('/403')
return
}
}
// 认证检查
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
}
next()
})使用 <transition> 包裹 <router-view>:
<template>
<div id="app">
<transition name="fade" mode="out-in">
<router-view />
</transition>
</div>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>| 模式 | 说明 |
|---|---|
out-in | 先离开再进入 |
in-out | 先进入再离开 |
| 无 | 同时进行 |
根据路由元信息设置不同过渡:
<template>
<div id="app">
<transition :name="transitionName" mode="out-in">
<router-view :key="$route.fullPath" />
</transition>
</div>
</template>
<script>
export default {
data() {
return {
transitionName: 'fade'
}
},
watch: {
'$route'(to, from) {
// 根据路由层级决定过渡方向
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
// 或使用路由 meta 指定过渡
this.transitionName = to.meta.transition || 'fade'
}
}
}
</script>
<style>
/* 淡入淡出 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
/* 左右滑动 */
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: transform 0.3s ease;
position: absolute;
width: 100%;
}
.slide-left-enter {
transform: translateX(100%);
}
.slide-left-leave-to {
transform: translateX(-100%);
}
.slide-right-enter {
transform: translateX(-100%);
}
.slide-right-leave-to {
transform: translateX(100%);
}
</style>在路由配置中指定过渡:
const routes = [
{
path: '/home',
component: Home,
meta: { transition: 'slide-up' }
},
{
path: '/about',
component: About,
meta: { transition: 'fade' }
}
]<template>
<transition :name="$route.meta.transition || 'fade'" mode="out-in">
<router-view />
</transition>
</template>进入路由后获取数据,显示加载状态:
<template>
<div class="post">
<div v-if="loading" class="loading">加载中...</div>
<div v-else>
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
loading: false,
post: null
}
},
watch: {
'$route': 'fetchData'
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
this.loading = true
try {
const response = await fetch(`/api/post/${this.$route.params.id}`)
this.post = await response.json()
} catch (error) {
console.error('获取数据失败:', error)
} finally {
this.loading = false
}
}
}
}
</script>使用 beforeRouteEnter 在导航完成前获取数据:
<script>
export default {
data() {
return {
post: null
}
},
beforeRouteEnter(to, from, next) {
// 此时组件实例未创建,无法访问 this
fetch(`/api/post/${to.params.id}`)
.then(response => response.json())
.then(post => {
// 通过回调访问组件实例
next(vm => {
vm.post = post
})
})
.catch(() => {
next('/404')
})
},
beforeRouteUpdate(to, from, next) {
// 路由参数变化时
this.post = null
fetch(`/api/post/${to.params.id}`)
.then(response => response.json())
.then(post => {
this.post = post
next()
})
.catch(() => {
next('/404')
})
}
}
</script>// store/modules/post.js
export default {
state: {
post: null,
loading: false
},
mutations: {
SET_POST(state, post) {
state.post = post
},
SET_LOADING(state, loading) {
state.loading = loading
}
},
actions: {
async fetchPost({ commit }, id) {
commit('SET_LOADING', true)
try {
const response = await fetch(`/api/post/${id}`)
const post = await response.json()
commit('SET_POST', post)
} finally {
commit('SET_LOADING', false)
}
}
}
}// 路由配置
const routes = [
{
path: '/post/:id',
component: Post,
beforeEnter: (to, from, next) => {
store.dispatch('post/fetchPost', to.params.id)
.then(() => next())
.catch(() => next('/404'))
}
}
]Vue Router 3.4+ 提供导航失败检测:
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
// NavigationFailureType 枚举
NavigationFailureType.aborted // 导航被守卫中止
NavigationFailureType.cancelled // 新导航开始了
NavigationFailureType.duplicated // 目标位置与当前位置相同
NavigationFailureType.redirected // 在导航守卫中调用了 next(其他位置)router.push('/admin').catch(failure => {
if (isNavigationFailure(failure)) {
console.log('导航失败类型:', failure.type)
console.log('目标路由:', failure.to)
console.log('来源路由:', failure.from)
}
})// 全局处理
router.beforeEach((to, from, next) => {
// 某些条件下中止导航
if (shouldAbort) {
next(false) // 中止导航
return
}
next()
})
// 捕获导航失败
this.$router.push('/admin')
.catch(failure => {
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
console.log('导航被中止')
}
})// Vue Router 3.1+ 会抛出 NavigationDuplicated 错误
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => {
if (err.name !== 'NavigationDuplicated') {
return Promise.reject(err)
}
})
}// 添加单个路由
router.addRoute({
path: '/new-route',
component: NewRoute
})
// 添加嵌套路由
router.addRoute('parentRoute', {
path: 'child',
component: ChildRoute
})// 通过名称删除路由
router.removeRoute('routeName')// 获取所有路由记录
router.getRoutes()
// 检查路由是否存在
router.hasRoute('routeName')// 权限路由动态添加
export const asyncRoutes = [
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: { roles: ['admin'] }
}
]
// 根据用户角色添加路由
export function addDynamicRoutes(roles) {
const routes = asyncRoutes.filter(route => {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
}
return true
})
routes.forEach(route => {
router.addRoute(route)
})
}解析路由位置,返回路由信息和 URL:
const resolved = router.resolve({
name: 'UserDetail',
params: { id: 123 },
query: { tab: 'profile' }
})
console.log(resolved.href) // '/user/123?tab=profile'
console.log(resolved.route) // 路由对象
console.log(resolved.location) // 位置对象获取当前路由对象:
// 在组件内
this.$route
// 在组件外
router.currentRoute获取挂载的 Vue 根实例:
const app = router.approuter/
├── index.js # 主路由文件
├── modules/ # 路由模块
│ ├── user.js
│ ├── product.js
│ └── admin.js
└── guards.js # 路由守卫// constants/routes.js
export const ROUTE_NAMES = {
HOME: 'Home',
USER_LIST: 'UserList',
USER_DETAIL: 'UserDetail'
}
export const ROUTE_PATHS = {
HOME: '/',
USER: '/user',
ADMIN: '/admin'
}// 路由错误处理
router.onError(error => {
console.error('路由错误:', error)
// 跳转到错误页面
router.push('/error')
})
// 处理懒加载失败
const lazyLoad = (view) => {
return () => import(`@/views/${view}.vue`).catch(() => {
router.push('/error')
})
}<template>
<!-- Vue Router 3 中直接嵌套包裹:transition 在外、keep-alive 紧贴 router-view -->
<transition name="fade" mode="out-in">
<keep-alive :include="cachedViews">
<router-view />
</keep-alive>
</transition>
</template>const route = router.resolve({ name: 'UserDetail', params: { id: 123 } })
window.open(route.href, '_blank')// matched 包含所有嵌套的路由记录
const matched = this.$route.matched// 使用 router-link 的 active-class
<router-link to="/user" active-class="active">用户</router-link>
// 或使用 $route 判断
computed: {
isActive() {
return this.$route.path === '/user'
}
}watch: {
'$route.params.id': {
handler(newId, oldId) {
this.fetchData(newId)
},
immediate: true
}
}| 方法 | 说明 |
|---|---|
push(location) | 导航到指定路由 |
replace(location) | 替换当前路由 |
go(n) | 前进或后退 |
back() | 后退 |
forward() | 前进 |
addRoute(route) | 添加路由 |
removeRoute(name) | 删除路由 |
getRoutes() | 获取所有路由 |
hasRoute(name) | 检查路由是否存在 |
resolve(location) | 解析路由位置 |
| 属性 | 说明 |
|---|---|
path | 当前路径 |
params | 路由参数 |
query | 查询参数 |
hash | URL hash |
fullPath | 完整路径 |
name | 路由名称 |
matched | 匹配的路由记录 |
meta | 路由元信息 |
redirectedFrom | 重定向来源 |