zoukankan      html  css  js  c++  java
  • vue-element-admin登录逻辑,以及动态添加路由,显示侧边栏

    这段时间在研究element-admin,感觉这个库有许多值得学习的地方,我学习这个库的方法是,先看它的路由,顺着路由,摸清它的逻辑,有点像顺藤摸瓜。

    这个库分的模块非常清晰,适合多人合作开发项目,但是如果一个人去用这个库开发后台,步骤显的有点繁琐,特别是调用后端接口,之前一个方法就调用到了,但是用这个库,需要先后分几步调用。

    比如说调用一个登陆接口:点击登陆按钮----》调用store中的方法----》调用api中对应登陆的方法---》request.js中封装的axios方法

    4步!!!!,让我看来确实是有点繁琐,这个问题到后面解决,通过自己封装的axios方法,直接调用后台接口,目前不知道会不会遇到其它问题。好了,接下来进入正题!!!

    接下来先介绍一下,element-admin的登录逻辑

    1、先看登录方法里写什么:

    handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid) {
              this.loading = true;
              //调用user模块红的login
              console.log("点击登陆按钮")
              this.$store.dispatch('user/login', this.loginForm).then(() => {
                console.log("登录成功");
                this.$router.push({ path: this.redirect || '/' });
                this.loading = false;
              }).catch(() => {
                this.loading = false;
              })
            } else {
              console.log('error submit!!');
              return false;
            }
          })
        }

    通过上面红色代码,可以看出,点击过登录按钮后,调用了$store里的一个方法,名叫login

    2、下面来看看$store里的这个login方法:

    import { login, logout, getInfo,self} from '@/api/user'


    const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo; return new Promise((resolve, reject) => { console.log("vuex中的请求") login({ username: username.trim(), password: password }).then(response => { console.log('vuex中') console.log(response); const { data } = response; commit('SET_TOKEN', data.token);//存在vueX中 setToken(data.token);//存在cookie中 resolve(); }).catch(error => { console.log(error); reject(error); }) }) },

    咿,怎么两个login,熟悉vuex的话应该知道,第一个login是$store中的方法,第二个login方法是,api里的login方法,用来调用接口的

    3、再来看看api中的login方法:

    import request from '@/utils/request'
    
    export function login(data) {
      return request({
        url: '/user/login',
        method: 'post',
        data
      })
    }

    上面是api中的login方法,它调用了request.js,request.js是封装了axios,是用来请求后台接口的,如果这个接口请求成功了,就回到了第一步中的.then()方法中

    代码是,路由跳转到首页,进入正式页面!!!

    重点来了!!!

    之所以是称之为权限,也就是必须满足一定的条件才能够访问到正常页面,那么如果不满足呢?如果我没有token,让不让我进正常页面呢??

    肯定是不让的,那我没有token,该去哪?答案是还待在登录页,哪都去不了,那么这些处理应该在哪写呢?答案是,permission.js模块

    这个js在main.js引入,其实就是个路由拦截器:来看看它的代码:

    import router from './router'
    import store from './store'
    import { Message } from 'element-ui'
    import NProgress from 'nprogress' // progress bar  一个进度条的插件
    import 'nprogress/nprogress.css' // progress bar style
    import { getToken } from '@/utils/auth' // get token from cookie
    import getPageTitle from '@/utils/get-page-title'
    
    NProgress.configure({ showSpinner: false }) // NProgress Configuration  是否有转圈效果
    
    const whiteList = ['/login'] // 没有重定向白名单
    
    router.beforeEach(async(to, from, next) => {
      // 开始进度条
      NProgress.start()
    
      // 设置页面标题
      document.title = getPageTitle(to.meta.title)
    
      // 确定用户是否已登录
      const hasToken = getToken()
    
      if (hasToken) {
        if (to.path === '/login') {
          // 如果已登录,则重定向到主页
          next({ path: '/' })
          NProgress.done()
        } else {
          const hasGetUserInfo = store.getters.name;
          console.log(hasGetUserInfo);
          if (hasGetUserInfo) {
            console.log("有用户信息");
            next();
          } else {
            console.log("无用户信息")
            try {
              // 获得用户信息
              await store.dispatch('user/getInfo');
              //实际是请求用户信息后返回,这里是模拟数据,直接从store中取
              const roles=store.getters.roles;
              store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
                router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
                router.options.routes=store.getters.routers;
                next({ ...to, replace: true });// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
              })
    
              // next()
            } catch (error) {
              // 删除token,进入登录页面重新登录
              await store.dispatch('user/resetToken');
              Message.error(error || 'Has Error');
              next(`/login?redirect=${to.path}`);
              NProgress.done();
            }
          }
        }
      } else {
        /* has no token*/
        if (whiteList.indexOf(to.path) !== -1) {
          // 在免费登录白名单,直接去
          next()
        } else {
          // 没有访问权限的其他页面被重定向到登录页面。
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    })
    
    router.afterEach(() => {
      // 完成进度条
      NProgress.done()
    })

    一看代码好多,不过不要怕,我来分析一下它的情况,不就是点if else嘛

    从上面代码看,每次路由跳转,都要从cookie中取token,

    那么可以分两种情况,有token和无token

    有token:再看看是不是去登录页的,登录页肯定不能拦截的,如果是登录页就直接放行。如果不是登录页,就要看看本地有没有用户信息,看看cookie中有没有用户信息(不一定是token,也可能是localstorage)。如果有用户信息,放行。如果没有用户信息,就调用接口去获取登录信息,然后后面还有一点代码,涉及到了动态添加路由(这里先说到这,后面具体说动态添加权限路由的事)。获取到用户信息后放行。如果在获取用户信息的过程中报错,则回到登录页

    无token:先看看用户要进入的页面是不是在白名单内,一般登录、注册、忘记密码都是在白名单内的,这些页面,在无token的情况下也是直接放行。如果不在白名单内,滚回登录页。

    以上就是element-admin的登录逻辑了。不知道能否帮助到你,但是写下来,让自己的思路更清晰,也是不错的。

    下面来说一下,element-admin的动态权限路由,显示侧边栏是什么逻辑

    首先要了解一下,侧边栏是如何渲染出来的,看看layout/components/slibar/index.vue有这样一段代码:

    <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />

    计算属性中有这样一段代码:

      routes() {
          return this.$router.options.routes
        },

    这个routes,是路由的元信息!!!是一个数组

    看到这就应该明白,侧边栏是如何渲染出来的,

    再来看看router.js里的代码:

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    /* Layout */
    import Layout from '@/layout'
    
    /**
     * 注意: 子菜单只在路由子菜单时出现。长度> = 1
     * 参考网址: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
     *
     * hidden: true                   如果设置为true,项目将不会显示在侧栏中(默认为false)
     * alwaysShow: true               如果设置为true,将始终显示根菜单
     *                                如果不设置alwaysShow, 当项目有多个子路由时,它将成为嵌套模式,否则不显示根菜单
     * redirect: noRedirect           如果设置noRedirect,则不会在面包屑中重定向
     * name:'router-name'             the name is used by <keep-alive> (must set!!!)
     * meta : {
        roles: ['admin','editor']    控制页面角色(可以设置多个角色)
        title: 'title'               名称显示在侧边栏和面包屑(推荐集)
        icon: 'svg-name'             图标显示在侧栏中
        breadcrumb: false            如果设置为false,则该项将隐藏在breadcrumb中(默认为true)
        activeMenu: '/example/list'  如果设置路径,侧栏将突出显示您设置的路径
      }
     */
    
    /**
     * constantRoutes
     * 没有权限要求的基本页
     * 所有角色都可以访问
     * 不需要动态判断权限的路由
     */
    export const constantRoutes = [
    
      {
        path: '/login',
        component: () => import('@/views/login/index'),
        hidden: true
      },
    
      {
        path: '/404',
        component: () => import('@/views/404'),
        hidden: true
      },
    
      {
        path: '/',
        component: Layout,
        redirect: '/self',
        children: [{
          path: 'self',
          name: 'Self',
          component: () => import('@/views/self/index'),
          meta: { title: '首页', icon: 'dashboard' }
        }]
      },
    
      {
        path: '/example',
        component: Layout,
        redirect: '/example/table',
        name: 'Example',
        meta: { title: 'Example', icon: 'example' },
        children: [
          {
            path: 'table',
            name: 'Table',
            component: () => import('@/views/table/index'),
            meta: { title: 'Table', icon: 'table'}
          },
          {
            path: 'tree',
            name: 'Tree',
            component: () => import('@/views/tree/index'),
            meta: { title: 'Tree', icon: 'tree',breadcrumb: true},
            hidden: false,//在侧边栏上显示  true为不显示  当父路由的字路由为1个时,不显示父路由,直接显示子路由
            alwaysShow:false,//默认是false   设置为true时会忽略设置的权限 一致显示在跟路由上
          }
        ]
      },
      
      {
        path: '/form',
        component: Layout,
        children: [
          {
            path: 'index',
            name: 'Form',
            component: () => import('@/views/form/index'),
            meta: { title: 'Form', icon: 'form' }
          }
        ]
      },
    
      {
        path: '/nested',
        component: Layout,
        redirect: '/nested/menu1',
        name: 'Nested',
        meta: {
          title: 'Nested',
          icon: 'nested'
        },
        children: [
          {
            path: 'menu1',
            component: () => import('@/views/nested/menu1/index'), // Parent router-view
            name: 'Menu1',
            meta: { title: 'Menu1' },
            children: [
              {
                path: 'menu1-1',
                component: () => import('@/views/nested/menu1/menu1-1'),
                name: 'Menu1-1',
                meta: { title: 'Menu1-1' }
              },
              {
                path: 'menu1-2',
                component: () => import('@/views/nested/menu1/menu1-2'),
                name: 'Menu1-2',
                meta: { title: 'Menu1-2' },
                children: [
                  {
                    path: 'menu1-2-1',
                    component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
                    name: 'Menu1-2-1',
                    meta: { title: 'Menu1-2-1' }
                  },
                  {
                    path: 'menu1-2-2',
                    component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
                    name: 'Menu1-2-2',
                    meta: { title: 'Menu1-2-2' }
                  }
                ]
              },
              {
                path: 'menu1-3',
                component: () => import('@/views/nested/menu1/menu1-3'),
                name: 'Menu1-3',
                meta: { title: 'Menu1-3' }
              }
            ]
          },
          
        ]
      },
      {
        path: 'external-link',
        component: Layout,
        children: [
          {
            path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
            meta: { title: 'External Link', icon: 'link' }
          }
        ]
      },
      {
        path: '/self',
        component: Layout,
        children: [
          {
            path: 'index',
            name: 'self',
            component: () => import('@/views/self'),
            meta: { title: 'self', icon: 'form' }
          }
        ]
      },
    
      // 404页面必须放在最后!!
      // { path: '*', redirect: '/404', hidden: true }
    ]
    
    // 创建路由
    const createRouter = () => new Router({
      // mode: 'history', // 需要服务支持
      scrollBehavior: () => ({ y: 0 }),
      routes: constantRoutes
    })
    
    var router = createRouter()
    
    // 重置路由
    // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
    export function resetRouter() {
      const newRouter = createRouter()
      router.matcher = newRouter.matcher // reset router
    }
    
    
    //异步挂载的路由
    //动态需要根据权限加载的路由表 
    export const asyncRouterMap = [
      {
        path: '/permission',
        component: Layout,
        name: 'permission',
        redirect: '/permission/index222',
        meta: {title:'permission', role: ['admin','super_editor'] }, //页面需要的权限
        children: [
          { 
            path: 'index222',
            component: () => import('@/views/self'),
            name: 'index222',
            meta: {title:'权限测试1',role: ['admin','super_editor'] }  //页面需要的权限
          },
          { 
            path: 'index333',
            component: () => import('@/views/self'),
            name: 'index333',
            meta: {title:'权限测试2',role: ['admin','super_editor'] }  //页面需要的权限
          }
        ]
      },
      { path: '*', redirect: '/404', hidden: true }
    ];
    
    
    
    export default router

    注意以上代码中红色的代码,这个routes中分两块路由配置,一块是固定的,无权限的路由配置,也就是不管是管理员身份,还是超级管理员身份,都会显示的路由配置。

    第二块是,带权限的路由配置,根据用户权限来显示侧边栏。注意,带权限的配置里的meta中都有role项,代表是权限

    首先,我们在获取用户信息时,会得到这个用户有哪些权限,是一个数组,假如是这样的

    commit('SET_ROLES',['admin','super_editor']);//自定义权限

    这个用户的权限有这些(admin、super_editor),然后再根据用户权限来筛选出符合的动态添加的路由,

    什么时候筛选呢?

    这就用到登录时的拦截器了,上面遇到过,就在哪执行,来看看那里都是写了一些什么代码:

    拿到这看看:

              // 获得用户信息
              await store.dispatch('user/getInfo');
              //实际是请求用户信息后返回,这里是模拟数据,直接从store中取
              const roles=store.getters.roles;
              store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
                router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
                router.options.routes=store.getters.routers;
                next({ ...to, replace: true });// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
              })

    routes其实就是上面的两个权限组成的数组,然后传入了GenerateRoutes方法内,(注意es6语法,看不懂的去了解一下es6),再看看GenerateRoutes中的代码:

    import { asyncRouterMap, constantRoutes } from '@/router';
    
    function hasPermission(roles, route) {
        if (route.meta && route.meta.role) {
            return roles.some(role => route.meta.role.indexOf(role) >= 0)
        } else {
            return true
        }
    }
      
    const permission = {
        namespaced: true,
        state: {
            routers: constantRoutes,
            addRouters: []
        },
        mutations: {
          SET_ROUTERS: (state, routers) => {
            state.addRouters = routers;
            state.routers = constantRoutes.concat(routers);
          }
        },
        actions: {
            GenerateRoutes({ commit }, data) {//roles是用户所带的权限
                return new Promise(resolve => {
                    const { roles } = data;
                    const accessedRouters = asyncRouterMap.filter(v => {
                        // if (roles.indexOf('admin') >= 0) {
                        //     return true;
                        // };
                        if (hasPermission(roles, v)) {
                            if (v.children && v.children.length > 0) {
                                v.children = v.children.filter(child => {
                                    if (hasPermission(roles, child)) {
                                        return child
                                    }
                                    return false;
                                });
                                return v
                            } else {
                                return v
                            }
                        }
                        return false;
                    });
                    commit('SET_ROUTERS', accessedRouters);
                    resolve();
                })
            }
        }
    };
      
    export default permission;
    GenerateRoutes方法办了一件事,就是把动态路由配置里符合用户权限的配置筛选出来,组成一个数组,然后,和固定路由合并到了一起,存到了vuex中,

    然后调用了这两句代码:

              router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
                router.options.routes=store.getters.routers;
    router.addRoutes()方法是,动态添加路由配置,参数是符合路由配置的数组,
    然后将路由元信息,变成合并后的路由元信息,因为渲染侧边栏需要用到,
    这两句代码,顺序不能变,缺一不可,缺了addRoutes,点击侧边栏的动态路由会无反应,缺了options,侧边栏显示不出来动态添加的路由!!!!

    以上就是element-admin的动态路由了,睡觉。。。。

    今天正式开始用这个模板开发了,想说一下,这个模板有许多模块,我用不到,比如说mock文件,当我们不用它本身的api去请求接口时,完全可以将它删除,我是今天把它原有的登录逻辑去掉了,登录时,就不会有mock的事了。
    于是,我一个一个文件去注释,对运行不影响的都删除了,test文件一下删除了,还有些不知道是干什么的js也去了,这样项目看着对我来说比较轻巧,也比较熟悉。另外,哪些utils。还有store里不用的方法都可以去掉。
    当我把登录改了之后,能去掉一大堆东西好不好。ok,有时间整理一个轻巧的模板,挂到码云上

    更新一个bug,目前页面上的路由拦截器的逻辑,并不是我现在用的逻辑,不过相差不远,项目上线后,发现刷新动态加载的路由页面,页面白屏,调试一下,发现是路由拦截器在重复跳转页面,这问题很奇怪,
    本地没有问题,然后琢磨了半天,既然是重复跳转,它有一个特征就是:to.path==from.path;那么可以利用这一特质,去拦截这种情况,
    if(to.path==from.path){
        next();
        return;
    }

    监听浏览器前进后退事件

    window.addEventListener("popstate", function () {
      backStatus=true;
      return;
    })

    补充一个递归过滤路由的方法(写在了vuex中):

    function hasPermission(roles, route) {
        //判断该条路由是否有权限
        if (route.meta && route.meta.authId) {
            return roles.includes(route.meta.authId);
        } else {
            return true;
        }
    }
    
    function filterFun(roles,parent){
        //递归筛选有权限的路由
        if(parent.children && parent.children.length > 0){
            parent.children = parent.children.filter(child => {
                if (hasPermission(roles, child)) {
                    return filterFun(roles,child)
                }
                return false;
            });
            return parent
        }else{
            return parent;
        }
    }
    
    const state = {
        routerPermissionIds:[],//能访问路由的 id列表
        routers:[],//所有的异步加载路由列表
        allRoutes:[],//经过权限筛选后,筛选出来的最终路由表
    }
    
    const mutations = {
        SET_ROUTER_PERMISSION_IDS(state,val){
            //设置 可访问路由的列表
            state.routerPermissionIds = val;
        },
        SET_ROUTES(state,val){
            //设置 所有的异步加载的路由列表
            state.routers = val;
        },
        SET_allRoutes(state,val){
            // 设置 allRouters
            state.allRoutes = val;
        }
    }
    
    const actions = {
        
        GenerateRoutes({commit},data){
            //生成路由表
            return new Promise( async resolve => {
                const {roles,asyncRouterArr} = data;
                console.log(roles,asyncRouterArr);
                const allowRoutes = asyncRouterArr.filter(v => {
                    if(hasPermission(roles,v)){
                        //递归筛选
                        return filterFun(roles,v)
                    }
                    return false;
                })
                resolve(allowRoutes);
            })
        }
    }
    
    export default {
    namespaced: true,
    state,
    mutations,
    actions
    }








  • 相关阅读:
    一个、说到所有的扩展指标
    时序图、流程图
    流程图总结
    UML活动图与流程图的区别
    类图与对象图
    app的描述-软件的描述
    UML的目标
    软件建模的用途
    Android中如何使用Intent在Activity之间传递对象[使用Serializable或者Parcelable]
    Serializable 和 Parcelable 区别
  • 原文地址:https://www.cnblogs.com/fqh123/p/11094296.html
Copyright © 2011-2022 走看看