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 动态路由权限控制
文章目录
- Vue3 动态路由权限控制
- 🎯 目标
- 😴 功课
- 权限控制相关流程
- 路由导航守卫
- 🍸 准备
- 安装依赖
- 调整文件&目录
- 🌈 Coding
- Token 管理
- 实例化 NProgress
- 修改 Login Mock 数据
- 修改登录逻辑
- 修改登出逻辑
- 动态路由
- 在路由中添加导航守卫
- 🎭 结果
源码地址: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, // 是否显示加载icotrickleSpeed: 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) => {// ↓保存tokensetToken(res.data.token)router.push('/')})}
}
......
修改登出逻辑
修改 src/components/layout/header/index.vue
中登出逻辑,登出时将 token 删除:
......
const signout = () => {// ↓将store重置为初始值useUserInfoStore().$reset()// ↓删除tokenremoveToken()router.push('/login')
}
......
动态路由
src\store\user-info.ts
保存用户信息到 store 时,动态添加路由:
......
actions: {setAll(userinfo: any) {const { id, username, name, avatar, menus } = userinfothis.id = idthis.username = usernamethis.name = namethis.avatar = avatarthis.menus = menusmenus.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页面:
本文为博主原创文章,任何个人、团体、机构转载和摘录,请注明出处。