Vue中後臺鑑權的另外一種方案 - 動態路由

案例: vue-element-asyncLoginjavascript

2020-2-8更新

優化流程,簡化邏輯,去除_import.js文件,改成直接動態導入css

優化404頁面前端

2020-1-17更新

更新爲最新的vue-element-template的模板vue

增長思路說明java

優化部分代碼git

增長QQ交流羣,有問題請加羣程序員

前言

在今年年初在掘金髮布了一篇文章記一次Vue動態渲染路由的實現,如今代碼通過不斷的Reviewgithub

如今徹底優化了以前的實現方法,代碼量減小不少,邏輯更加簡單,同時也更加穩定vue-router

demo已經部署到github,歡迎體驗~~ vue-element-asyncLogin, 你的start是個人動力!vuex

鑑權-前端路由 VS 鑑權-動態路由

​ 前端路由鑑權相信只要瞭解過vue-element-admin的都知道,前端鑑權方案是徹底可行的,思路清晰,難度適中,項目中徹底可使用,那麼相對來講動態路由優點在什麼地方呢

  1. 前端鑑權不夠靈活,線上版本每次修改權限頁面,都須要從新打包項目
  2. 中小型項目中 前端鑑權明顯更加好用,成本更低,程序員們也不用996了(霧),可是對於權限等級不少,而且比較大的項目,維護這一套鑑權路由,毫無疑問是一個大工程,而且面對頻繁變動的需求,bug會出現的更加頻繁,前端工程師工做量大大增長,這時候彷佛前端鑑權就再也不是好的方案
  3. 動態路由並非迴歸到刀耕火種的時代,而是一種新的思路,路由配置仍是由前端完成,僅僅將狀態交給了後端,不一樣角色的路由顯示交給後端控制,前端不須要管理路由,最多隻須要管理權限顆粒化的問題

實現思路

  1. 路由跳轉 先判斷是否登陸 未登陸只能訪問白名單頁面,訪問其餘頁面所有重定向到登陸頁面
  2. 登陸行爲觸發,獲取動態路由,遞歸解析動態路由信息,而且addRouter,同時存儲到Vuex,而且記錄獲取路由的狀態
  3. 跳轉頁面不會獲取動態路由,刷新頁面從新獲取動態路由

相比較以前使用localStorage存儲登陸狀態,如今把登陸狀態交給cookice進行管理

路由信息所有交給Vuex進行管理,再也不從localStorage裏面走,增長了系統的穩定性

配置基礎路由

具體的實現思路

router/router.js

// ......
// 靜態路由
export const StaticRouterMap = [
  {
    path: '/login',
    component: login,
    meta: { title: '管理員登陸' },
    hidden: true
  },
  {
    path: '/user',
    component: userLogin,
    redirect: '/user/userlogin',
    name: 'user',
    hidden: true,
    children: [
      {
        path: 'userLogin',
        component: () => import('@/views/userLogin/components/login'),
        meta: { title: '商戶登陸' }
      },
      {
        path: 'userRegistry',
        component: () => import('@/views/userLogin/components/registry'),
        meta: { title: '商戶註冊' }
      }
    ]
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        meta: { title: '根目錄', icon: 'dashboard', affix: true }
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  }
]

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: StaticRouterMap
})

複製代碼

與後端同窗定製路由結構 (如下爲json)

後端會根據當前用戶權限動態返回路由結構 前端再也不須要考慮權限問題

[{
  id: 1,
  name: 'Example',
  code: null,
  description: null,
  url: '/example',
  component: 'layout',
  generatemenu: 1,
  sort: 0,
  parentId: null,
  permName: null,
  redirect: '/example/table',
  title: '普通用戶',
  icon: 'example',
  children: [
    {
      id: 2,
      name: 'Table',
      code: null,
      description: null,
      url: 'table',
      component: 'table',
      generatemenu: 1,
      sort: 0,
      parentId: 1,
      permName: null,
      redirect: '',
      title: 'Table',
      icon: 'table',
      children: null
    },
    {
      id: 3,
      name: 'Tree',
      code: null,
      description: null,
      url: 'tree',
      component: 'tree',
      generatemenu: 1,
      sort: 0,
      parentId: 1,
      permName: null,
      redirect: '',
      title: 'Tree',
      icon: 'tree',
      children: null
    }
  ]
}]
複製代碼

解析後端初始路由數據爲可用數據

處理後端原始路由數據

../utils/addRouter

遞歸寫入比以前版本的遞歸刪除更加穩定,代碼量也更少

注意,路由結構與目錄結構是對應的

/** * 生成路由 * @param {Array} routerlist 格式化路由 * @returns */
export function addRouter(routerlist) {
  const router = []
  try {
    routerlist.forEach(e => {
      let e_new = {
        path: e.url,
        name: e.name,
        component: () => e.component === 'layout' ? import('@/layout') : import(`@/views/${e.component}/index`)
      }
      if (e.children) {
        const children = addRouter(e.children)
        // 保存權限
        e_new = { ...e_new, children: children }
      }
      if (e.redirect) {
        e_new = { ...e_new, redirect: e.redirect }
      }
      if (e.generatemenu === 0) {
        e_new = { ...e_new, hidden: true }
      }
      if (e.icon !== '' && e.title !== '') {
        e_new = { ...e_new, meta: { title: e.title, icon: e.icon } }
      } else if (e.title !== '' && e.icon === '') {
        e_new = { ...e_new, meta: { title: e.title } }
      }
      router.push(e_new)
    })
  } catch (error) {
    console.error(error)
    return []
  }
  return router
}
複製代碼

處理後的路由

咱們處理後的路由後面須要與現有的router進行拼接,這裏須要根據需求 修改處理路由的規則

(核心)合併路由

以上的都是準備工做,就是爲了將初始路由與後端返回的動態路由進行拼接

這部分代碼也是優化的核心

import router from './router'
import store from './store'
import user from './store/modules/user'
import { getToken, removeToken } from './utils/auth'
import NProgress from 'nprogress' // Progress 進度條
import 'nprogress/nprogress.css' // Progress 進度條樣式
import { Message } from 'element-ui'
import { getRouter } from './api/login'
import { addRouter } from './utils/addRouter'

const whiteList = ['/login']
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    // 判斷cookice是否存在 不存在即爲未登陸
    if (to.path !== '/login') {
      if (user.state.init) {
        // 獲取了動態路由 data必定true,就無需再次請求 直接放行
        next()
      } else {
        // data爲false,必定沒有獲取動態路由,就跳轉到獲取動態路由的方法
        gotoRouter(to, next)
      }
    } else {
      Message({ message: '您已經登陸', type: 'info' })
      next('/')
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      // 免登錄白名單 直接進入
      next()
    } else {
      if (to.path !== '/login') {
        // 重定向到登陸頁面 不能這麼寫 由於假如以前的角色是 管理員頁面 後又登錄了非管理員 重定向的頁面就可能不存在,就會致使404
        // next(`/login?redirect=${to.path}`)
        next('/login')
      } else {
        next()
      }
    }
  }
})

router.afterEach((to, from) => {
  NProgress.done() // 結束Progress
})

function gotoRouter(to, next) {
  getRouter(store.getters.token) // 獲取動態路由的方法
    .then(res => {
      console.log('解析後端動態路由', res)
      const asyncRouter = addRouter(res.data.router) // 進行遞歸解析
      store.dispatch('user/setroles', res.data.permit)
      return asyncRouter
    })
    .then(asyncRouter => {
      router.addRoutes(asyncRouter) // vue-router提供的addRouter方法進行路由拼接
      console.log(asyncRouter)
      store.dispatch('user/setRouterList', asyncRouter) // 存儲到vuex
      store.dispatch('user/GetInfo')
      store.commit('user/set_init', true)
      next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
    })
    .catch(e => {
      console.log(e)
      removeToken()
    })
}
複製代碼

Vuex內部的邏輯

import { StaticRouterMap } from '../../router/index'

 state: {
    //.....
    RouterList: [] // 動態路由
 },

mutations: {
    set_router: (state, RouterList) => {
      state.RouterList = RouterList
    }
},

action: {
    // 動態設置路由 此爲設置設置途徑
    setRouterList({ commit }, routerList) {
      commit('set_router', StaticRouterMap.concat(routerList)) // 進行路由拼接並存儲
    },
}
複製代碼

相對以前的邏輯要簡單不少

修改側邊欄的應用路由地址

須要注意的是 經過 addRoutes合併的路由 不會被this.$router.options.routes獲取到,因此須要將獲取的路由拼接到this.$router.options.routes

最後修改渲染側邊欄部分部分的代碼

src\views\layout\components\Sidebar\index.vue

computed: {
	// ....
    routes() {
      return this.$store.getters.routerList
    },
   	// ....
  }
複製代碼

我已精心準備了一個簡單的demo vue-element-asyncLogin,歡迎體驗,若是對你有幫助,請不要吝嗇你的start~~1

相關文章
相關標籤/搜索