当前位置:   article > 正文

Vue 权限控制(路由验证)转载_component: () => import

component: () => import

这篇文章简直精辟啊

原文  Vue 权限控制(路由验证) - 掘金 (juejin.cn)

下面介绍两种权限控制的方法:

  • 路由元信息(meta)
  • 动态加载菜单和路由(addRoutes)

路由元信息(meta)

如果一个网站有不同的角色,比如管理员普通用户,要求不同的角色能访问的页面是不一样的

这个时候我们就可以把所有的页面都放在路由表里,只要在访问的时候判断一下角色权限。如果有权限就让访问,没有权限的话就拒绝访问,跳转到404页面

vue-router在构建路由时提供了元信息meta配置接口,我们可以在元信息中添加路由对应的权限,然后在路由守卫中检查相关权限,控制其路由跳转。

可以在每一个路由的 meta 属性里,将能访问该路由的角色添加到 roles 里。用户每次登陆后,将用户的角色返回。然后在访问页面时,把路由的 meta 属性和用户的角色进行对比,如果用户的角色在路由的 roles 里,那就是能访问,如果不在就拒绝访问。

代码示例1:

路由信息:

  1. routes: [
  2. {
  3. path: '/login',
  4. name: 'login',
  5. meta: {
  6. roles: ['admin', 'user']
  7. },
  8. component: () => import('../components/Login.vue')
  9. },
  10. {
  11. path: 'home',
  12. name: 'home',
  13. meta: {
  14. roles: ['admin']
  15. },
  16. component: () => import('../views/Home.vue')
  17. },
  18. ]

页面控制:

  1. //假设有两种角色:admin 和 user
  2. //从后台获取的用户角色
  3. const role = 'user'
  4. //当进入一个页面是会触发导航守卫 router.beforeEach 事件
  5. router.beforeEach((to,from,next)=>{
  6. if(to.meta.roles.includes(role)){
  7. next() //放行
  8. }esle{
  9. next({path:"/404"}) //跳到404页面
  10. }
  11. })

代码示例2

当然也可以用下面的一种方法:

  1. // router.js
  2. // 路由表元信息
  3. [
  4. {
  5. path: '',
  6. redirect: '/home'
  7. },
  8. {
  9. path: '/home',
  10. meta: {
  11. title: 'Home',
  12. icon: 'home'
  13. }
  14. },
  15. {
  16. path: '/userCenter',
  17. meta: {
  18. title: '个人中心',
  19. requireAuth: true // 在需要登录的路由的meta中添加响应的权限标识
  20. }
  21. }
  22. ]
  23. // 在守卫中访问元信息
  24. router.beforeEach (to, from, next) {
  25. let flag = to.matched.some(record=>record.meta.requireAuth);
  26. //console.log(flag); //可自己打印出来看一下
  27. }

可以在多个路由下面添加这个权限标识,达到控制的目的

只要一切换页面,就需要看有没有这个权限,所以可以在最大的路由下 main.js 中配置

存储信息

一般的,用户登录后会在本地存储用户的认证信息,可以用 tokencookie 等,这里我们用 token

将用户的token保存到localStorage里,而用户信息则存在内存store中。这样可以在vuex中存储一个标记用户登录状态的属性auth,方便权限控制。

代码示例

  1. // store.js
  2. {
  3. state: {
  4. token: window.localStorage.getItem('token'),
  5. auth: false,
  6. userInfo: {}
  7. },
  8. mutations: {
  9. setToken (state, token) {
  10. state.token = token
  11. window.localStorage.setItem('token', token)
  12. },
  13. clearToken (state) {
  14. state.token = ''
  15. window.localStorage.setItem('token', '')
  16. },
  17. setUserInfo (state, userInfo) {
  18. state.userInfo = userInfo
  19. state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
  20. }
  21. },
  22. actions: {
  23. async getUserInfo (ctx, token) {
  24. return fetchUserInfo(token).then(response => {
  25. if (response.code === 200) {
  26. ctx.commit('setUserInfo', response.data)
  27. }
  28. return response
  29. })
  30. },
  31. async login (ctx, account) {
  32. return login(account).then(response => {
  33. if (response.code === 200) {
  34. ctx.commit('setUserInfo', response.data.userInfo)
  35. ctx.commit('setToken', response.data.token)
  36. }
  37. })
  38. }
  39. }
  40. }

写好路由表和vuex之后,给所有路由设置一个全局守卫,在进入路由之前进行权限检查,并导航到对应的路由。

  1. // router.js
  2. router.beforeEach(async (to, from, next) => {
  3. if (to.matched.some(record => record.meta.requireAuth)) { // 检查是否需要登录权限
  4. if (!store.state.auth) { // 检查是否已登录
  5. if (store.state.token) { // 未登录,但是有token,获取用户信息
  6. try {
  7. const data = await store.dispatch('getUserInfo', store.state.token)
  8. if (data.code === 200) {
  9. next()
  10. } else {
  11. window.alert('请登录')
  12. store.commit('clearToken')
  13. next({ name: 'Login' })
  14. }
  15. } catch (err) {
  16. window.alert('请登录')
  17. store.commit('clearToken')
  18. next({ name: 'Login' })
  19. }
  20. } else {
  21. window.alert('请登录')
  22. next({ name: 'Login' })
  23. }
  24. } else {
  25. next()
  26. }
  27. } else {
  28. next()
  29. }
  30. })

上述的方法是基于jwt认证方式,本地不持久化用户信息,只保存token,当用户刷新或者重新打开网页时,进入需要登录的页面都会尝试去请求用户信息,该操作在整个访问过程中只进行一次,直到刷新或者重新打开,对于应用后期的开发维护和扩展支持都很好。

动态加载菜单和路由(addRoutes)

有时候为了安全,我们需要根据用户权限或者是用户属性去动态的添加菜单和路由表,可以实现对用户的功能进行定制。vue-router提供了addRoutes()方法,可以动态注册路由,需要注意的是,动态添加路由是在路由表中push路由,由于路由是按顺序匹配的,因此需要将诸如404页面这样的路由放在动态添加的最后。

代码示例

  1. // store.js
  2. // 将需要动态注册的路由提取到vuex中
  3. const dynamicRoutes = [
  4. {
  5. path: '/manage',
  6. name: 'Manage',
  7. meta: {
  8. requireAuth: true
  9. },
  10. component: () => import('./views/Manage')
  11. },
  12. {
  13. path: '/userCenter',
  14. name: 'UserCenter',
  15. meta: {
  16. requireAuth: true
  17. },
  18. component: () => import('./views/UserCenter')
  19. }
  20. ]

vuex中添加userRoutes数组用于存储用户的定制菜单。在setUserInfo中根据后端返回的菜单生成用户的路由表。

  1. // store.js
  2. setUserInfo (state, userInfo) {
  3. state.userInfo = userInfo
  4. state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
  5. // 生成用户路由表
  6. state.userRoutes = dynamicRoutes.filter(route => {
  7. return userInfo.menus.some(menu => menu.name === route.name)
  8. })
  9. router.addRoutes(state.userRoutes) // 注册路由
  10. }

修改菜单渲染

  1. // App.vue
  2. <div id="nav">
  3. <router-link to="/">主页</router-link>
  4. <router-link to="/login">登录</router-link>
  5. <template v-for="(menu, index) of $store.state.userInfo.menus">
  6. <router-link :to="{ name: menu.name }" :key="index">{{menu.title}}</router-link>
  7. </template>
  8. </div>

完整的 动态加载菜单和路由 案例

如果前面的看完了,觉得自己了解的可以了,其实就不需要往下面看了,当然,你也可以看一下这个案例,尝试自己写一下。

友情提示:这个案例可能会有点难,有些逻辑比较不好理解,最好亲自上手写一下,下去自己慢慢慢慢消化


那么就让我们开始吧:

新建一个项目,删去不需要的文件和代码

后端准备数据

在src同级目录下,新建一个server.js文件,如下:

代码:
其中涉及到跨域问题,具体参考我的文章《常见的跨域解决方案(全)》

  1. //Server.js
  2. let express  = require('express');
  3. let app = express();
  4. //在后端配置,让所有人都可以访问api接口 其中设计到跨域问题,具体参考我的文章《常见的跨域解决方案(全)》
  5. app.use('*', function (req, res, next) {
  6.     res.header('Access-Control-Allow-Origin', '*');
  7.     //Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行
  8.     res.header('Access-Control-Allow-Headers', 'Content-Type');
  9.     res.header('Access-Control-Allow-Methods', '*');
  10.     res.header('Content-Type', 'application/json;charset=utf-8');
  11.     next();
  12. });
  13. app.get('/role',(req,res)=>{
  14.     res.json({
  15. //假如这是后端给我们的JSON数据
  16.         menuList:[
  17.             {pid:-1,path:'/cart',name:'购物车',id:1,auth:'cart'},
  18.             {pid:1,path:'/cart/cart-list',name:'购物车列表',id:4,auth:'cart-list'},
  19.             {pid:4,path:'/cart/cart-list/lottery',auth:'lottery',id:5,name:'彩票'},
  20.             {pid:4,path:'/cart/cart-list/product',auth:'product',id:6,name:'商品'},
  21.             {pid:-1,path:'/shop',name:'商店',id:2,auth:'shop'},
  22.             {pid:-1,path:'/profile',name:'个人中心',id:3,auth:'profile'},
  23.         ],
  24.         buttonAuth:{
  25.             edit:true, // 可编辑
  26.         }
  27.     })
  28. })
  29. //监听3000端口
  30. app.listen(3000);

然后测试端口数据能不能获得(使用node指令):

使用浏览器访问3000端口下的/role
如果可以获得数据,则说明端口没问题

然后把下面这些对应的页面用vue的基本骨架写出来

router.js中配置路由

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. //引入组件
  4. import Home from "./views/Home.vue"
  5. Vue.use(Router)
  6. export default new Router({
  7. mode: 'history',
  8. base: process.env.BASE_URL,
  9. //配置路由
  10. routes: [
  11. {
  12. //访问'/'时,重定向到home页面
  13. path:'/',
  14. redirect:'/home'
  15. },
  16. {
  17. path:'/home',
  18. name:'home',
  19. component:Home
  20. },
  21. {
  22. path:'/cart',
  23. name:'cart',
  24. //使用懒加载,当使用这个组件的时候再加载资源,当组件资源较大时,不建议使用,可能会出现白屏现象
  25. //而且最好使用绝对路径,@是绝对路径的意思,相当于src下
  26. component:()=>import('@/components/menu/cart.vue'),
  27. //配置子路由
  28. children:[
  29. {
  30. //当配置子路由时,最好不要在前面加'/',比如:'/cart-list'
  31. path:'cart-list',
  32. name:'cart-list',
  33. component:()=>import('@/components/menu/cart-list.vue'),
  34. //配置子路由
  35. children:[
  36. {
  37. path:'lottery',
  38. name:'lottery',
  39. component:()=>import('@/components/menu/lottery.vue')
  40. },
  41. {
  42. path:'product',
  43. name:'product',
  44. component:()=>import('@/components/menu/product.vue')
  45. }
  46. ]
  47. }
  48. ]
  49. },
  50. {
  51. path:'/profile',
  52. name:'profile',
  53. component:()=>import('@/components/menu/profile.vue')
  54. },
  55. {
  56. path:'/shop',
  57. name:'shop',
  58. component:()=>import('@/components/menu/shop.vue')
  59. },
  60. {
  61. path:'*',
  62. component:()=>import('@/views/404.vue')
  63. }
  64. ]
  65. })

在浏览器地址栏中输入路由,测试,肯定是ok的,这里就不贴图了

路由配置完毕后,后端给我们返回的数据如图所示:

我们需要把上面的数据转换成我们需要的数据格式,并保存在vuex中,(其中涉及到异步问题,具体参考我的文章《JS中的异步解决方案》),如下:

  1. //store.js
  2. //重难点代码 很难理解 需要多看几遍慢慢理解
  3. let formatMenuList = (menuList) => {
  4. function r(pid) {
  5. //filter过滤数组,返回一个满足要求的数组
  6. return menuList.filter(menu => {
  7. //格式化菜单变成我们需要的结果
  8. if (menu.pid === pid) {
  9. let children = r(menu.id);
  10. menu.children = children.length ? children : null;
  11. return true;
  12. }
  13. })
  14. }
  15. return r(-1);
  16. }
  17. actions: {
  18. //异步 async await 参考我的文章<异步解决方案>
  19. async getMenuList({ commit }) {
  20. //去server.js(后端api)获取数据
  21. let { data } = await axios.get('http://localhost:3000/role');
  22. let menuList = data.menuList
  23. //把获取的数据传入上面写的方法里,转换格式
  24. menuList = formatMenuList(menuList)
  25. //配置完全局路由(main.js)后,可自己打印出来看一下
  26. // console.log(menuList);
  27. },
  28. }

需要保存成的数据格式,拿我之前写过的一个树形数据来让大家看看,只看结构就好,不用看数据:

  1. treeData: {
  2. title: "Web全栈架构师",
  3. children: [
  4. {
  5. title: "Java架构师"
  6. },
  7. {
  8. title: "JS高级",
  9. children: [
  10. {
  11. title: "ES6"
  12. },
  13. {
  14. title: "动效"
  15. }
  16. ]
  17. },
  18. {
  19. title: "Web全栈",
  20. children: [
  21. {
  22. title: "Vue训练营",
  23. expand: true,
  24. children: [
  25. {
  26. title: "组件化"
  27. },
  28. {
  29. title: "源码"
  30. },
  31. {
  32. title: "docker部署"
  33. }
  34. ]
  35. },
  36. {
  37. title: "React",
  38. children: [
  39. {
  40. title: "JSX"
  41. },
  42. {
  43. title: "虚拟DOM"
  44. }
  45. ]
  46. },
  47. {
  48. title: "Node"
  49. }
  50. ]
  51. }
  52. ]
  53. }

然后在main.js中添加路由 (涉及到导航守卫问题,请参考我的文章Vue 导航守卫(路由的生命周期)) 如下:

  1. //main.js
  2. //只要页面切换就执行的钩子
  3. //根据权限动态添加路由(我们的路由要分成两部分:一部分是有权限的,另一部分是没有权限的)
  4. router.beforeEach(async (to,from,next)=>{
  5. //判断当前有没有获取过权限,如果获取过了,就不要再获取了
  6. if(!store.state.hasRules){
  7. //获取权限,调用获取权限的接口,去action中获取数据
  8. await store.dispatch('getMenuList')
  9. }else{
  10. //如果已经获取了权限就可以访问页面了
  11. next()
  12. }
  13. })

然后访问主页面,效果如下:

在上面的数据有个auth项,代表有没有这个权限。如果没有这个权限,到时候会把这个菜单从路由规则中删除掉。把所有的auto都过滤出来,如下:

然后在主页面测试如下(记的把上面的console.log代码的注释解开):

如果把后端数据(menuList)改变了,那么重新刷新一下页面,那么就会动态的改变数据,这里就不演示了,自己可以注释一个看一下

接下来渲染菜单数据,
main.js使用element-ui:

  1. //main.js
  2. //安装 element-ui 并使用
  3. import ElementUI from "element-ui"
  4. //引入element-ui里的样式
  5. import 'element-ui/lib/theme-chalk/index.css'
  6. Vue.use(ElementUI)

把我们刚才得到的数据渲染到Home上面:

在 Home 中使用mapState方法:

当在我们渲染的时候,我们需要一个递归组件来动态的获取传递过来的数据,动态的进行渲染,所以肯定是不能写死的,递归组件的使用请参考我的文章《Vue 递归组件(构建树形菜单)》,这里不做过多介绍,直接拿来用

在 views 下面添加一个 ReSubMenu 组件,代码如下:

  1. <template>
  2. <el-submenu :index="data.path">
  3. <template slot="title">
  4. <router-link :to="data.path">{{data.name}}</router-link>
  5. </template>
  6. <template v-for="c in data.children">
  7. <ReSubMenu :key="c.auth" v-if="data.children" :data="c">
  8. </ReSubMenu>
  9. <el-menu-item :key="c.auth" v-else :index="c.path">
  10. {{c.name}}
  11. </el-menu-item>
  12. </template>
  13. </el-submenu>
  14. </template>
  15. <script>
  16. export default {
  17. name:"ReSubMenu",
  18. props:{
  19. data:{
  20. type:Object,
  21. default:()=>({})
  22. }
  23. }
  24. }
  25. </script>

然后再 Home 中使用这个组件

template 中的代码如下:

  1. <template>
  2. <div class='home'>
  3. <!-- <router-view></router-view> -->
  4. <el-menu default-active="2" class="el-menu-vertical-demo" :router="true">
  5. <template v-for="m in menuList">
  6. <!-- 渲染肯定是不能写死的,所以我使用之前封装过的递归组件,具体内容参考我的文章<Vue 递归组件(构建树形菜单)> -->
  7. <ReSubMenu :data="m" :key="m.auth" v-if="m.children"></ReSubMenu>
  8. <el-menu-item v-else :key="m.auth" :index="m.path">{{m.name}}</el-menu-item>
  9. </template>
  10. </el-menu>
  11. <!-- <router-view></router-view> -->
  12. </div>
  13. </template>

效果图:

这样就能根据传递过来的数据动态的生成我们需要的结构

但是如果后端没有给我们返回profile,也是拦不住的,如下:

这样也是可以访问到 profile 的,如下:

这样是不行的,因为后端并没有给我们返回 profile

如何解决?

答:根据权限动态添加路由 (我们的路由要分成两部分 一部分是有权限 另一部分是没权限的)

修改 router.js 中的代码如下:

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. //引入组件
  4. import Home from "./views/Home.vue"
  5. Vue.use(Router)
  6. let defaultRoutes = [
  7. {
  8. //访问'/'时,重定向到home页面
  9. path:'/',
  10. redirect:'/home'
  11. },
  12. {
  13. path:'/home',
  14. name:'home',
  15. component:Home
  16. },
  17. {
  18. path:'*',
  19. component:()=>import('@/views/404.vue')
  20. }
  21. ]
  22. export let authRoutes = [
  23. {
  24. path:'/cart',
  25. name:'cart',
  26. //使用懒加载,当使用这个组件的时候再加载资源,当组件资源较大时,不建议使用,可能会出现白屏现象
  27. //而且最好使用绝对路径,@是绝对路径的意思,相当于src下
  28. component:()=>import('@/components/menu/cart.vue'),
  29. //配置子路由
  30. children:[
  31. {
  32. //当配置子路由时,最好不要在前面加'/',比如:'/cart-list'
  33. path:'cart-list',
  34. name:'cart-list',
  35. component:()=>import('@/components/menu/cart-list.vue'),
  36. //配置子路由
  37. children:[
  38. {
  39. path:'lottery',
  40. name:'lottery',
  41. component:()=>import('@/components/menu/lottery.vue')
  42. },
  43. {
  44. path:'product',
  45. name:'product',
  46. component:()=>import('@/components/menu/product.vue')
  47. }
  48. ]
  49. }
  50. ]
  51. },
  52. {
  53. path:'/profile',
  54. name:'profile',
  55. component:()=>import('@/components/menu/profile.vue')
  56. },
  57. {
  58. path:'/shop',
  59. name:'shop',
  60. component:()=>import('@/components/menu/shop.vue')
  61. }
  62. ]
  63. //需要查看用户权限来动态添加路由
  64. export default new Router({
  65. mode: 'history',
  66. base: process.env.BASE_URL,
  67. //默认可以访问的路由
  68. routes: defaultRoutes
  69. })

stroe.js中导入:

store.js中添加代码:

修改main.js中的代码(重点):

测试ok:

当然你也可以再注释一个测试一下

所以这个路由是由后端返回的,如果没有权限


作者:YXi
链接:https://juejin.cn/post/6844903916870565901
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/58852
推荐阅读
相关标签
  

闽ICP备14008679号