第二十七节:进阶:路由权限控制 + 404 / 401 全局异常页面开发

第二十七节:进阶:路由权限控制 + 404 / 401 全局异常页面开发

🎯本节目标

  1. 开发 404 页面(页面不存在)、401 无权限页面
  2. 完善路由守卫,实现路由权限校验
  3. 区分:登录校验、角色 / 权限校验、不存在页面跳转逻辑
  4. 处理白名单路由(登录页不用登录也能访问)
  5. 模拟无权限访问页面,跳转 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')
    }
  }
})

✅测试验证清单

  1. 未登录状态 :直接访问 /dashboard/system/user,自动跳转到登录页面
  2. 访问不存在地址 :例如 http://localhost:5173/xxxabc,跳转到 404 页面
  3. 访问 /401:正常渲染无权限页面
  4. 登录成功 :动态路由/system/user被 addRoute 注册,侧边栏自动渲染系统管理菜单
  5. 已经登录后手动访问/login,自动跳转到工作台

关键知识点

  1. /:pathMatch(.*)* 404 路由必须放在路由数组最后;如果写在前面,所有页面都会命中 404。
  2. router.addRoute() 添加动态路由之后,next({...to,replace:true}) 必不可少,否则新增路由会报 404。
  3. 白名单路由,不需要 token 也能进入。
  4. 前端路由权限只是拦截跳转,接口层面依旧需要后端做权限校验

踩坑点

  1. @/permission 需要在 main.js 尽早导入,要在createApp(App)之前引入,守卫才生效。
  2. 刷新页面 pinia 状态丢失,搭配 pinia 持久化插件,token、用户信息刷新页面不丢失。
相关推荐
风骏时光牛马1 小时前
稳定性治理与疑难问题深度分析实践
前端
光影少年1 小时前
react navite 简单原生方法双向调用示例
javascript·react native·react.js
zzzzzz3101 小时前
react-bits:把“酷炫组件”放回 React 组件化的语境里看
javascript·react.js·动效
梦曦i10 小时前
@meng-xi/create-uni-app vs unibest:轻量脚手架与重型框架的定位之争
前端·uni-app
平头哥技术团队10 小时前
Day 10 | 工欲善其事:VS Code 配置与项目归档
android·开发语言·前端·javascript·html·交互
乌恩大侠10 小时前
GH200 新增 NVMe SSD 分区、格式化与挂载完整流程
运维·服务器·前端
yume_sibai11 小时前
Element Plus 数据展示组件完全指南(15个核心组件)
前端·javascript·vue.js
qiuhaipeng111 小时前
Claude code 升级后上下文长度变短问题
java·前端·数据库
zzz_236811 小时前
个人 AI 记忆如何跨工具复用:用 Markdown、索引和 Skill 搭一个可治理的记忆库
前端·人工智能·react.js·前端框架·agent·agent测评