Vite2+Vue3+TypeScript+Element-plus脚手架搭建系列

✅01-初始化 Vite 项目
✅02-配置 Vite2 环境变量
✅03-Vite2 配置及说明
✅04-Vue3 使用 SCSS
✅05-Vue3 路由配置
✅06-TypeScript 配置及说明
✅07-Vue3 使用 axios
✅08-Vue3 axios 对象封装
✅09-ESLint 配置及说明
✅10-ESLint 与 Prettier 集成配置及说明
✅11-Mock.js 模拟接口数据
✅12-Vite2 引入 Element-Plus 框架
✅13-渐变+透明样式实现清爽登录页
✅14-Element-Plus 实现后台管理系统布局
✅15-Pinia 实现 store 状态管理
✅16-Vue3 动态路由权限控制


源码地址:GitHub / 码云


Vue3 动态路由权限控制

🎯 目标

  • 实现登录用户与非登录用户权限控制:非登录用户只能访问登录页面 。
  • 实现管理员与普通用户的权限控制:管理员能访问用户管理和角色管理页面,普通用户只能用户管理页面。

😴 功课

权限控制相关流程

  • 登录成功流程
    在这里插入图片描述

  • 鉴权流程
    在这里插入图片描述

  • 登出流程
    在这里插入图片描述

路由导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。你可以使用 router.beforeEach 注册一个全局前置守卫:

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

每个守卫方法接收两个参数:
to:即将要进入的目标
from:当前导航正要离开的路由


你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
	// 后置处理逻辑
})

其它详情请查看 Vue Router 官方文档

🍸 准备

安装依赖

安装 cookie 操作依赖和进度条依赖。

npm install -S js-cookie @types/js-cookie nprogress @types/nprogress

调整文件&目录

添加 src/utils/cookie/index.ts 文件,存放 token 管理工具方法。
添加 src/utils/progress/index.ts 文件,存放进度条实例。

目录结构如下:

📁 src

----📁 utils

--------📁 token

------------📄 index.ts

--------📁 progress

------------📄 index.ts


🌈 Coding

💡 只粘贴了部分核心代码,完整代码可去 GitHub / 码云 获取

Token 管理

src/utils/token/index.ts Token 管理工具方法代码如下:

import Cookies from 'js-cookie'

// ↓cookie 中保存 token 的键
export const tokenKey = 'bee-token'

// ↓获取token
export const getToken = (): string | undefined => {
  return Cookies.get(tokenKey)
}

// ↓设置token
export const setToken = (token: string): string | undefined => {
  return Cookies.set(tokenKey, token)
}

// ↓删除token
export const removeToken = (): void => {
  return Cookies.remove(tokenKey)
}

// ↓判断token是否存在
export const existToken = (): boolean => {
  return getToken() !== undefined
}

实例化 NProgress

src\utils\progress\index.ts 实例化 NProgress 对象代码如下:

import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

NProgress.configure({
  easing: 'ease', // 动画方式
  speed: 500, // 递增进度条的速度
  showSpinner: false, // 是否显示加载ico
  trickleSpeed: 200, // 自动递增间隔
  minimum: 0.3, // 初始化时的最小百分比
})
export default NProgress

修改 Login Mock 数据

src/mock/login/index.ts

  • 将登录的 mock 数据改为随机生成的 token。且 admin 用户和普通用户返回 token 长度不一致
  • 将获取用户信息的 mock 数据改成 admin 用户和普通用户两种

💡 模拟后端解析 token 返回的权限数据,用 token 长度不一致来区分

......
'adminToken|1': [
  {
    token: '@STRING( "lower", 24 )',
  },
],
'zhangsanToken|1': [
  {
    token: '@STRING( "lower", 12 )',
  },
],
'adminInfo|1': [
  {
    id: '@INCREMENT()',
    username: 'admin',
    name: '超级管理员',
    avatar: '@IMAGE(100, "#ffc72d", "Code-Bee"),',
    menus: [
      {
        id: 1,
        name: '权限管理',
        icon: 'el-icon-menu',
        children: [
          { id: 2, name: '用户管理', path: '/sys/user', component: '/sys/User.vue' },
          { id: 3, name: '角色管理', path: '/sys/role', component: '/sys/Role.vue' },
          { id: 4, name: '菜单管理', path: '/sys/menu' },
        ],
      },
      {
        id: 5,
        name: '系统管理',
        icon: 'el-icon-setting',
        children: [
          { id: 6, name: '系统字典', path: '/sys/dict' },
          { id: 7, name: '参数配置', path: '/sys/config' },
          { id: 8, name: '通知公告', path: '/sys/notice' },
          { id: 9, name: '日志审计', path: '/sys/log' },
        ],
      },
    ],
  },
],
'zhangsanInfo|1': [
  {
    id: '@INCREMENT()',
    username: 'zhangsan',
    name: '张三',
    avatar: '@IMAGE(100, "#ffc72d", "张三"),',
    menus: [
      {
        id: 1,
        name: '权限管理',
        icon: 'el-icon-menu',
        children: [{ id: 2, name: '用户管理', routeName: 'sys-user', path: '/sys/user', component: '/sys/User.vue' }],
      },
    ],
  },
],
......

修改登录逻辑

修改 Login.vue 中登录逻辑,登录成功后将 token 保存到 cookie:

......
const signin = () => {
  if (!form.username) {
    errorMessage('用户名为空')
  } else if (!form.password) {
    errorMessage('密码为空')
  } else {
    loginApi.signin(form).then((res: any) => {
      // ↓保存token
      setToken(res.data.token)
      router.push('/')
    })
  }
}
......

修改登出逻辑

修改 src/components/layout/header/index.vue 中登出逻辑,登出时将 token 删除:

......
const signout = () => {
  // ↓将store重置为初始值
  useUserInfoStore().$reset()
  // ↓删除token
  removeToken()
  router.push('/login')
}
......

动态路由

src\store\user-info.ts保存用户信息到 store 时,动态添加路由:

......
actions: {
  setAll(userinfo: any) {
    const { id, username, name, avatar, menus } = userinfo
    this.id = id
    this.username = username
    this.name = name
    this.avatar = avatar
    this.menus = menus
    menus.forEach((menu: any) => {
      if (menu.children) {
        menu.children.forEach((sub: any) => {
          // ↓动态添加路由
          router.addRoute('root', {
            path: sub.path,
            component: modulesRoutes[`/src/views${sub.component}`],
          })
        })
      }
    })
  },
},
......

在路由中添加导航守卫

src/router/index.ts 将添加全局前置守卫(beforeEach)与全局后置钩子(afterEach):

......
// ↓全局前置守卫
router.beforeEach(async (to) => {
  NProgress.start()
  // ↓如果请求地址不是白名单
  if (whiteList.indexOf(to.path) === -1) {
    // ↓如果token存在检查store,否则跳转到登录页
    if (existToken()) {
      // ↓从store获取用户信息
      const userInfoStore = useUserInfoStore()
      // ↓如果没有用户信息,查询用户信息
      if (!userInfoStore.id) {
        // ↓查询成功保存用户信息且跳转到目标页
        try {
          await loginApi.userInfo().then((res: any) => {
            console.log(res)
            userInfoStore.setAll(res.data)
          })
        } catch (error) {
          // ↓移除token无效
          removeToken()
          return { name: 'login', query: { redirect: `${to.path}` } }
        }
      }
    } else {
      // ↓非白名单且token不存在
      return { name: 'login', query: { redirect: `${to.path}` } }
    }
  }
})

// ↓全局后置钩子
router.afterEach(() => {
  NProgress.done()
})
......

将静态路由用户管理 sys-user 注释,因为该路由已经 mock 了,会动态添加

🎭 结果

  • 未登录时访问首页会自动跳转到登录页
  • 用户登录后可访问首页
  • 如果登录 admin 用户,能看到多个菜单,且能访问用户管理和角色管理页面
  • 如果登录 zhangsan 用户,能看到用户管理菜单且能访问页面,但在浏览器地址栏输入http://127.0.0.1:3000/#/sys/role 访问角色管理页面,会显示404页面:
    在这里插入图片描述

本文为博主原创文章,任何个人、团体、机构转载和摘录,请注明出处。

Logo

前往低代码交流专区

更多推荐