記錄一次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的路由安全性有很大隱患,所以近期會換成第一種那種形式

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM