最近公司項目不是很忙,可以寫個博客總結一下,最近公司項目的后台管理系統使用了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的路由安全性有很大隱患,所以近期會換成第一種那種形式