Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Vue Router路由守卫教程,详解全局守卫、路由独享守卫、组件内守卫。包含完整代码示例,适合Vue.js开发者快速掌握路由权限控制。
核心关键词:Vue Router路由守卫2024、Vue路由权限、Vue导航守卫、beforeEach、beforeEnter、Vue路由拦截
长尾关键词:Vue路由守卫怎么用、Vue路由权限控制、Vue导航守卫配置、Vue路由拦截器、Vue Router守卫详解
通过本节Vue Router路由守卫详解,你将系统性掌握:
Vue Router路由守卫是控制路由导航的重要机制。路由守卫通过拦截路由跳转实现权限验证和导航控制,也是Vue应用安全性的重要保障。
💡 安全提醒:路由守卫只是前端权限控制,真正的安全验证必须在后端进行
beforeEach是最常用的路由守卫,在每次路由跳转前执行:
// 🎉 全局前置守卫示例
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore } from '@/stores/user';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
roles: ['admin']
}
}
]
});
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
console.log('导航开始:', from.path, '->', to.path);
const userStore = useUserStore();
// 1. 检查是否需要登录
if (to.meta.requiresAuth) {
if (!userStore.isLoggedIn) {
// 未登录,重定向到登录页
next({
name: 'Login',
query: { redirect: to.fullPath }
});
return;
}
// 2. 检查用户权限
if (to.meta.roles) {
const hasPermission = to.meta.roles.some(role =>
userStore.user.roles.includes(role)
);
if (!hasPermission) {
// 权限不足,显示错误页面
next({ name: 'Forbidden' });
return;
}
}
}
// 3. 设置页面标题
if (to.meta.title) {
document.title = to.meta.title;
}
// 4. 页面访问统计
if (typeof gtag !== 'undefined') {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: to.path
});
}
// 允许导航
next();
});beforeResolve在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用:
// 全局解析守卫
router.beforeResolve(async (to, from, next) => {
console.log('路由解析阶段');
// 预加载数据
if (to.meta.preload) {
try {
// 显示加载状态
showLoading();
// 预加载数据
await preloadData(to);
// 隐藏加载状态
hideLoading();
} catch (error) {
console.error('数据预加载失败:', error);
// 可以选择继续导航或中断
next(false);
return;
}
}
next();
});
// 数据预加载函数
async function preloadData(route) {
const promises = [];
if (route.meta.preload.includes('user')) {
promises.push(loadUserData());
}
if (route.meta.preload.includes('settings')) {
promises.push(loadSettings());
}
await Promise.all(promises);
}afterEach在导航完成后执行,不能改变导航:
// 全局后置钩子
router.afterEach((to, from, failure) => {
console.log('导航完成:', from.path, '->', to.path);
// 1. 隐藏加载状态
hideLoading();
// 2. 滚动到顶部
window.scrollTo(0, 0);
// 3. 记录页面访问
logPageView(to);
// 4. 处理导航失败
if (failure) {
console.error('导航失败:', failure);
showErrorMessage('页面加载失败,请重试');
}
});beforeEnter只在进入特定路由时触发:
// 🎉 路由独享守卫示例
const routes = [
{
path: '/admin',
component: AdminLayout,
beforeEnter: async (to, from, next) => {
console.log('进入管理后台');
const userStore = useUserStore();
// 检查管理员权限
if (!userStore.user.roles.includes('admin')) {
// 记录未授权访问尝试
logSecurityEvent('unauthorized_admin_access', {
user: userStore.user.id,
from: from.path,
to: to.path
});
next({ name: 'Forbidden' });
return;
}
// 检查IP白名单(示例)
const clientIP = await getClientIP();
if (!isIPWhitelisted(clientIP)) {
next({ name: 'IPBlocked' });
return;
}
next();
},
children: [
// 子路由配置
]
},
{
path: '/payment',
component: Payment,
beforeEnter: (to, from, next) => {
// 支付页面特殊验证
const orderStore = useOrderStore();
if (!orderStore.currentOrder) {
// 没有订单信息,重定向到购物车
next({ name: 'Cart' });
return;
}
// 验证订单状态
if (orderStore.currentOrder.status !== 'pending') {
next({ name: 'OrderStatus' });
return;
}
next();
}
}
];组件内守卫通过组件选项或Composition API实现组件级别的导航控制:
<!-- 🎉 Options API组件内守卫示例 -->
<template>
<div class="user-profile">
<h2>用户资料</h2>
<form @submit.prevent="saveProfile">
<input v-model="profile.name" placeholder="姓名">
<input v-model="profile.email" placeholder="邮箱">
<button type="submit">保存</button>
</form>
</div>
</template>
<script>
export default {
name: 'UserProfile',
data() {
return {
profile: {
name: '',
email: ''
},
hasUnsavedChanges: false
};
},
// 进入组件前
beforeRouteEnter(to, from, next) {
console.log('即将进入用户资料页');
// 检查用户是否有权限查看此资料
const userId = to.params.id;
const currentUserId = getCurrentUserId();
if (userId !== currentUserId && !isAdmin()) {
next({ name: 'Forbidden' });
return;
}
// 预加载用户数据
loadUserProfile(userId).then(profile => {
next(vm => {
vm.profile = profile;
});
}).catch(() => {
next({ name: 'NotFound' });
});
},
// 更新组件前
beforeRouteUpdate(to, from, next) {
console.log('用户资料页参数更新');
// 检查是否有未保存的更改
if (this.hasUnsavedChanges) {
const answer = window.confirm('有未保存的更改,确定要离开吗?');
if (!answer) {
next(false);
return;
}
}
// 加载新用户的数据
const userId = to.params.id;
this.loadProfile(userId);
next();
},
// 离开组件前
beforeRouteLeave(to, from, next) {
console.log('即将离开用户资料页');
// 检查是否有未保存的更改
if (this.hasUnsavedChanges) {
const answer = window.confirm('有未保存的更改,确定要离开吗?');
if (answer) {
next();
} else {
next(false);
}
} else {
next();
}
},
methods: {
async loadProfile(userId) {
try {
this.profile = await fetchUserProfile(userId);
} catch (error) {
console.error('加载用户资料失败:', error);
}
},
async saveProfile() {
try {
await updateUserProfile(this.profile);
this.hasUnsavedChanges = false;
this.$message.success('保存成功');
} catch (error) {
this.$message.error('保存失败');
}
}
},
watch: {
profile: {
handler() {
this.hasUnsavedChanges = true;
},
deep: true
}
}
};
</script><!-- Composition API组件内守卫示例 -->
<template>
<div class="article-editor">
<h2>文章编辑</h2>
<form @submit.prevent="saveArticle">
<input v-model="article.title" placeholder="标题">
<textarea v-model="article.content" placeholder="内容"></textarea>
<button type="submit">保存</button>
</form>
</div>
</template>
<script>
import { ref, watch } from 'vue';
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
export default {
setup() {
const article = ref({
title: '',
content: ''
});
const hasUnsavedChanges = ref(false);
// 监听文章内容变化
watch(article, () => {
hasUnsavedChanges.value = true;
}, { deep: true });
// 路由更新前
onBeforeRouteUpdate(async (to, from, next) => {
if (hasUnsavedChanges.value) {
const shouldLeave = await confirmLeave();
if (!shouldLeave) {
next(false);
return;
}
}
// 加载新文章
const articleId = to.params.id;
await loadArticle(articleId);
next();
});
// 离开路由前
onBeforeRouteLeave(async (to, from, next) => {
if (hasUnsavedChanges.value) {
const shouldLeave = await confirmLeave();
if (!shouldLeave) {
next(false);
return;
}
}
next();
});
const confirmLeave = () => {
return new Promise((resolve) => {
const modal = createConfirmModal({
title: '确认离开',
content: '有未保存的更改,确定要离开吗?',
onConfirm: () => resolve(true),
onCancel: () => resolve(false)
});
modal.show();
});
};
const loadArticle = async (id) => {
try {
article.value = await fetchArticle(id);
hasUnsavedChanges.value = false;
} catch (error) {
console.error('加载文章失败:', error);
}
};
const saveArticle = async () => {
try {
await updateArticle(article.value);
hasUnsavedChanges.value = false;
} catch (error) {
console.error('保存失败:', error);
}
};
return {
article,
saveArticle
};
}
};
</script>组件内守卫的核心优势:
💼 实际应用场景:表单编辑页面、数据录入界面、需要确认的操作页面
通过本节Vue Router路由守卫详解的学习,你已经掌握:
A: next()可以无参数调用(继续导航)、传入false(中断导航)、传入路由对象(重定向)、传入Error对象(导航失败)。
A: 可以使用async/await或Promise,确保在异步操作完成后调用next()函数。
A: 全局beforeEach → 路由beforeEnter → 组件beforeRouteEnter → 全局beforeResolve → 导航确认 → 全局afterEach → 组件beforeRouteEnter的next回调。
A: 可以直接导入store实例,或者在守卫中通过app实例访问全局状态。
A: 复杂的守卫逻辑可能影响导航性能,建议优化异步操作,避免在守卫中执行耗时操作。
"掌握Vue Router路由守卫是构建安全可靠Vue应用的重要技能。通过合理使用路由守卫,你将能够实现完善的权限控制系统,提升应用的安全性和用户体验。"