第二十七节:进阶:路由权限控制 + 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、用户信息刷新页面不丢失。