• 记录一次vue-admin-template后台框架使用(动态路由权限)


    最近公司项目不是很忙,可以写个博客总结一下,最近公司项目的后台管理系统使用了vue-admin-template作为主要框架,这里可以安利以下真的很不错,封装了很多主要功能

    地址:

    https://panjiachen.github.io/vue-element-admin-site/zh/

    相应配套教程:

    https://juejin.cn/post/6844903476661583880

    项目中权限控制和公司实际业务不一样的是,后台管理系统中可以配置多个角色,每个角色所配置的权限都是不同的,可以动态调整的,因此并不能按照官方教程中的把每个页面路由所需要的role直接写在路由表里,然后用户登陆后再从用户拥有的role去递归遍历出可以访问的路由

    和公司后台人员商量后,决定后台直接返回用户所拥有的路由权限,前端根据path去匹配,后台返回的数据长这样

     1.首先是用户登录,在项目根目录store文件夹下有modules,集合管理了所有vuex模块,其中有个user,改写其中的actions,

      login({ commit }, userInfo) {
        let { account, pwd, rsa } = userInfo;
        var encryptor = new JSEncrypt();
        encryptor.setPublicKey(rsa)//设置公钥
        account = account.trim();
        pwd = md5(pwd.trim()).toUpperCase();
        let rsaPassWord = encryptor.encrypt(pwd);
        let rsaAccount = encryptor.encrypt(account);
        return new Promise((resolve, reject) => {
          pcLogin({ account: rsaAccount, pwd: rsaPassWord }).then(response => {
            commit('SET_TOKEN', response);
            setToken(response)
            resolve();
          }).catch(error => {
            reject(error)
          })
        })
      },
    

    2.login页面下进行表单验证后直接调用action即可,因为在每个vuex子文件下加了namespaced: true,所以在action前要加上目录名user

        handleLogin() {
          this.$refs.loginForm.validate((valid) => {
            if (valid) {
              if (!this.loginForm.rsa) {
                this.$message.error("登录失败,获取私钥失败!");
                return false;
              }
              this.loading = true;
              this.$store
                .dispatch("user/login", this.loginForm)
                .then((res) => {
                  this.$message.success("登录成功!");
                  this.getAssetUserMenuTree();
                  this.loading = false;
                  this.getUserInfo();
                  this.getRand();
                })
                .catch(() => {
                  this.loading = false;
                  this.getRand();
                });
            } else {
              return false;
              this.getRand();
            }
          });
        }, 

    3.在获取用户token以后可以调获取用户路由权限的接口getAssetUserMenuTree,再获取可以访问的路由的时候判断当前登录页的url上的是否有需要有重定向回去的页面,如果没有就直接跳转第一个路由路径

        getAssetUserMenuTree() {
          // 新方法
          this.$store.dispatch("permission/generateRoutes").then((res) => {
            this.$router.addRoutes(res);
            this.$router.push({
              path: this.redirect ? this.redirect : res[0].path,
            });
          });
    
          //旧方法需要依赖session
          // const res = await getAssetUserMenuTree();
          // let router = this.recursionRouter(res, Main);
          // if (router.length > 0) {
          //   window.sessionStorage.removeItem("asyncRouter");
          //   window.sessionStorage.setItem("asyncRouter", JSON.stringify(router));
          //   //实时挂载路由到侧边方法一
          //   this.$router.options.routes = router;
          //   this.$router.addRoutes(router);
          //   this.$router.push({ path: router[0].path, replace: true });
          // }
        }, 

    4.在store目录下的permission.js改写其中的方法,这个方法使用了一个递归路由函数,比较接口返回的路由和全部路由,匹配出符合条件的路由

    const actions = {
      generateRoutes({ commit }) {
        return new Promise(resolve => {
          getAssetUserMenuTree().then(res => {
            let accessedRoutes = []
            accessedRoutes = recursionRouter(res, allRouter)
            console.log(accessedRoutes);
            commit('SET_ROUTES', accessedRoutes)
            resolve(accessedRoutes)
          })
        })
      }
    }
    

    5.allRouter就是router文件夹中index中的asyncRoutes(全部所需权限的路由),引入即可

    function recursionRouter(userRouter = [], allRouter = []) {
      var realRoutes = [];
      allRouter.forEach((v, i) => {
        // activeMenu 用于关联没有显示但是需要的路由
        let activeMenu = "";
        if (v.meta && v.meta.activeMenu) {
          activeMenu = v.meta.activeMenu || "";
        }
        userRouter.forEach((item, index) => {
          if (item.path === v.path || item.path === activeMenu) {
            if (item.children && item.children.length > 0) {
              v.children = recursionRouter(item.children, v.children);
            }
            realRoutes.push(v);
          }
        });
      });
      return realRoutes;
    } 

    成功渲染侧边路由

     6.现在还有一个问题因为是动态添加的路由,所以在页面刷新的时候会丢失,所以在permission.js中改写路由的导航守卫钩子,每次路由跳转的时候用户是否登录,如果是登录状态且没有动态路由表(permission_routes)则重新调获取用户路由表的接口

    import router from './router'
    import store from './store'
    import { Message } from 'element-ui'
    import NProgress from 'nprogress'
    import 'nprogress/nprogress.css'
    import { getToken } from '@/utils/auth' //从cookie获取token的方法
    import getPageTitle from '@/utils/get-page-title'
    NProgress.configure({ showSpinner: true }) // 每个页面头部进度条配置
    const whiteList = ['/login'] // 不需要token的白名单
    router.beforeEach(async (to, from, next) => {
      NProgress.start()
      document.title = getPageTitle(to.meta.title)
      const hasToken = getToken()
      if (store.getters.permission_routes.length > 0) {
        next()
      } else {
        if (hasToken) {
          if (to.path === '/login') {
            next()
          } else {
            store.dispatch('permission/generateRoutes').then((res) => { // 生成可访问的路由表
              router.addRoutes(res) // 动态添加可访问路由表
              next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,设置replace为true,不会再页面历史记录下留下记录
            })
            console.log('刷新页面--->重新获取用户权限')
          }
        } else {
          //没有token的情况下
          if (whiteList.indexOf(to.path) !== -1) {
            // 如果在白名单下有此路由路径,则直接跳转(不需要token)
            next()
          } else {
            // 否则直接重定向到登录页
            next(`/login?redirect=${to.path}`)
            NProgress.done()
          }
        }
      }
    })
    
    router.afterEach(() => {
      NProgress.done()
    })

      

    贴一下之前另一个后台管理系统依靠session实现的方式:

    1.登录页面获取用户信息权限

     2.登陆后获取用户拥有的子系统权限

     3.子系统需要从新窗口打开,里面的侧边栏也是根据权限动态生成

    引入全部路由表:

    import SettingManager from "@/router/SettingManager.js";
    import PartyConstruction from "@/router/PartyConstruction.js";
    import VillageAffairs from "@/router/VillageAffairs.js";
    import Duty from "@/router/Duty.js";
    import AppOperation from "@/router/AppOperation.js";
    import Assets from "@/router/Assets.js";
    import SmartTraffic from "@/router/SmartTraffic.js";

    点击子系统:

       squareClick(item) {
          let path = item.path || "";
          let children = item.children || [];
          if (path == "" || children == []) {
            this.$message("敬请期待");
            return;
          }
          if (item.path.indexOf("http") > -1) {
            window.open(item.path, "_blank");
          } else {
            let fullRouter;
            let router;
            if (item.path == "/SettingManager") {
              fullRouter = SettingManager;
            } else if (item.path == "/Assets") {
              fullRouter = Assets;
            } else if (item.path == "/PartyConstruction") {
              fullRouter = PartyConstruction;
            } else if (item.path == "/Duty") {
              fullRouter = Duty;
            } else if (item.path == "/AppOperation") {
              fullRouter = AppOperation;
            } else if (item.path == "/VillageAffairs") {
              fullRouter = VillageAffairs;
            } else if (item.path == "/SmartTraffic") {
              fullRouter = SmartTraffic;
            }
            router = this.recursionRouter(children, fullRouter);
            if (router.length > 0) {
              this.$store.dispatch(
                "app/setSubSysInfo",
                JSON.stringify({
                  name: item.mate.title,
                })
              );
              window.sessionStorage.removeItem("asyncRouter");
              window.sessionStorage.setItem("asyncRouter", JSON.stringify(router));
              let { href } = this.$router.resolve({
                path: router[0].path, // 取路由的第一个
                replace: true,
              });
              window.open(href, "_blank");
            }
          }
        },
    

    路由的index.js

    // 需要权限的路由
    export let asyncRoutes = [
      ...SettingManager, // 设置
      ...PartyConstruction,
      ...VillageAffairs,
      ...Duty,
      ...AppOperation,
      ...Assets,
      ...SmartTraffic
    ]
    
    // 动态添加的路由,暂存至session,跳转至新页面
    let accessedRouters = []
    if (sessionStorage.getItem('asyncRouter')) {
      let localRoutes = [...JSON.parse(sessionStorage.getItem('asyncRouter'))];
      function convertRouter(asyncRouterMap) {
        const accessedRouters = []
        if (asyncRouterMap) {
          asyncRouterMap.forEach(item => {
            var parent = generateRouter(item, true)
            var children = []
            if (item.children) {
              item.children.forEach(child => {
                children.push(generateRouter(child, false))
              })
            }
            parent.children = children
            accessedRouters.push(parent)
          })
        }
        // accessedRouters.push({ path: '*', redirect: '/404', hidden: true })
        return accessedRouters
      }
    
      // 自动生成的map
      let AutoComponentsMap = parseMap(asyncRoutes);
    
      function generateRouter(item, isParent) {
        var router = {
          path: item.path,
          name: item.name,
          meta: item.meta,
          hidden: item.hidden,
          alwaysShow: item.alwaysShow,
          redirect: item.redirect,
          children: item.children,
          // component: isParent ? Layout : componentsMap[item.name] //手动map映射
          component: isParent ? Layout : AutoComponentsMap[item.name].component  //自动map映射
        }
        return router
      }
    
      // 手动写一份map映射路由表(废弃)
      const componentsMap = {
        PartyConstruction: () => import('@/layout'),
        organizationalLife: () => import('@/pages/partyConstruction/organizationalLife/index.vue'),
        organizationalLifeAdd: () => import('@/pages/partyConstruction/organizationalLife/add.vue'),
        OrganizationalLifeDetail: () => import('@/pages/partyConstruction/organizationalLife/detail.vue'),
      };
    
      // 平铺素有路由name保存为Map集合(待优化)
      function parseMap(arr) {
        const routesMap = {} //路由map
        function flat(arr) {
          return arr.reduce((pre, cur) => {
            if (cur.name) {
              routesMap[cur.name] = cur;
            }
            return pre.concat(Array.isArray(cur.children) ? flat(cur.children) : cur)
          }, [])
        }
        flat(arr);
        return routesMap
      }
      accessedRouters = convertRouter(localRoutes);
    }
    
    const createRouter = () =>
      new Router({
        scrollBehavior: () => ({ y: 0 }),
        routes: [...constantRoutes, ...accessedRouters]
      });
    
    const router = createRouter();
    

      

    放在session的路由安全性有很大隐患,所以近期会换成第一种那种形式

     

  • 相关阅读:
    Git和SVN之间的五个基本区别
    如何成为一名程序员:我的道路
    产品经理要懂多少技术?
    Unix哲学相关资源汇总
    Android.mk简介
    Android 中的 Service 全面总结
    获取Map集合中数据的方法
    少编码多思考:代码越多 问题越多
    【自定义Android带图片和文字的ImageButton】
    Android task process thread 进程与线程
  • 原文地址:https://www.cnblogs.com/rmty/p/14978266.html
Copyright © 2020-2023  润新知