第二十七节:进阶:路由权限控制 + 404 / 401 全局异常页面开发
🎯本节目标
- 开发 404 页面(页面不存在)、401 无权限页面
- 完善路由守卫,实现路由权限校验
- 区分:登录校验、角色 / 权限校验、不存在页面跳转逻辑
- 处理白名单路由(登录页不用登录也能访问)
- 模拟无权限访问页面,跳转 401;访问不存在路由跳转 404
前面
v‑perm控制按钮显示隐藏 ;路由权限控制整个页面能不能进。
步骤 1:新建两个异常页面
① src/views/error/404.vue
<template>
<div class="error-page">
<div class="code">404</div>
<div class="desc">抱歉,你访问的页面不存在</div>
<el-button type="primary" @click="$router.push('/dashboard')">返回工作台</el-button>
</div>
</template>
<script setup></script>
<style scoped>
.error-page{
height: 100%;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
}
.code{
font-size:80px;
font-weight:bold;
color:#909399;
}
.desc{
font-size:16px;
color:#666;
margin:16px 0 24px;
}
</style>
② src/views/error/401.vue
<template>
<div class="error-page">
<div class="code">401</div>
<div class="desc">抱歉,你没有权限访问该页面</div>
<el-button type="primary" @click="$router.push('/dashboard')">返回工作台</el-button>
</div>
</template>
<script setup></script>
<style scoped>
.error-page{
height: 100%;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
}
.code{
font-size:80px;
font-weight:bold;
color:#E6A23C;
}
.desc{
font-size:16px;
color:#666;
margin:16px 0 24px;
}
</style>
步骤 2:配置路由 src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout/index.vue'
// 常量路由:所有用户都可以访问,不需要权限
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录' }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: '/dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: { title: '工作台', icon: 'DataBoard' }
}
]
},
// 无权限页面
{
path: '/401',
component: () => import('@/views/error/401.vue')
},
// 404页面,必须放在路由数组最后!
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/error/404.vue')
}
]
// 需要权限的业务路由(后续动态追加)
export const asyncRoutes = [
{
path: '/system',
component: Layout,
meta: { title: '系统管理', icon: 'Setting' },
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', icon: 'User' }
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes
})
export default router
步骤 3:编写路由守卫 src/permission.js
新建 src/permission.js,全局路由拦截逻辑
import router from '@/router'
import { useUserStore } from '@/stores/user'
// 白名单:不需要登录就可以访问的页面
const whiteList = ['/login','/401','/404']
router.beforeEach(async(to, from, next) => {
const userStore = useUserStore()
const token = userStore.token
// 1、有token,已经登录
if(token){
if(to.path === '/login'){
// 已经登录,访问登录页,直接跳工作台
next('/dashboard')
}else{
// 判断是否已经获取用户信息
if(!userStore.username){
try {
// 这里可以调用接口获取用户信息,存入pinia
// await getUserInfoApi()
// 把动态权限路由追加到路由表
// router.addRoute(asyncRoutes)
next({ ...to, replace:true })
}catch(err){
// token失效,清空状态回到登录
userStore.logout()
next('/login')
}
}else{
// 已拿到用户信息,放行
next()
}
}
}else{
// 2、没有token,未登录
if(whiteList.includes(to.path)){
// 在白名单,直接放行
next()
}else{
// 不在白名单,跳登录页
next('/login')
}
}
})
步骤 4:在 main.js 引入 permission,让守卫生效
import { createApp } from 'vue'
import App from './App.vue'
import '@/permission' // ✅导入路由守卫,必须写在createApp之前
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { perm } from '@/directive/perm'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.directive('perm', perm)
app.use(ElementPlus)
app.mount('#app')
步骤 5:动态路由追加(重点:根据权限动态挂载路由)
现在所有路由全部写死,真正后台项目:登录拿到用户权限,再调用
router.addRoute把有权限的路由注册进去。
修改 src/permission.js,演示动态路由逻辑
import router from '@/router'
import { useUserStore } from '@/stores/user'
import { asyncRoutes } from '@/router'
const whiteList = ['/login','/401','/404']
// 模拟过滤路由:根据用户权限过滤出可以访问的路由
function filterAsyncRoutes(routes, permissions){
const res = []
routes.forEach(route => {
const temp = { ...route }
// 简单演示:如果是admin直接全部放行
res.push(temp)
})
return res
}
router.beforeEach(async(to, from, next) => {
const userStore = useUserStore()
const token = userStore.token
if(token){
if(to.path === '/login'){
next('/dashboard')
}else{
if(!userStore.username){
try {
// 模拟登录后获取用户信息,这里是mock已经存好
// 根据权限过滤动态路由
const accessRoutes = filterAsyncRoutes(asyncRoutes, userStore.permissions)
// 将过滤后的路由添加进路由实例
accessRoutes.forEach(item=>{
router.addRoute(item)
})
// addRoute之后必须带上replace:true,防止路由404
next({ ...to, replace:true })
}catch(err){
userStore.logout()
next('/login')
}
}else{
next()
}
}
}else{
if(whiteList.includes(to.path)){
next()
}else{
next('/login')
}
}
})
✅测试验证清单
- 未登录状态 :直接访问
/dashboard、/system/user,自动跳转到登录页面 - 访问不存在地址 :例如
http://localhost:5173/xxxabc,跳转到 404 页面 - 访问 /401:正常渲染无权限页面
- 登录成功 :动态路由
/system/user被 addRoute 注册,侧边栏自动渲染系统管理菜单 - 已经登录后手动访问
/login,自动跳转到工作台
关键知识点
/:pathMatch(.*)*404 路由必须放在路由数组最后;如果写在前面,所有页面都会命中 404。router.addRoute()添加动态路由之后,next({...to,replace:true})必不可少,否则新增路由会报 404。- 白名单路由,不需要 token 也能进入。
- 前端路由权限只是拦截跳转,接口层面依旧需要后端做权限校验。
踩坑点
@/permission需要在 main.js 尽早导入,要在createApp(App)之前引入,守卫才生效。- 刷新页面 pinia 状态丢失,搭配 pinia 持久化插件,token、用户信息刷新页面不丢失。