記一次Vue動態渲染路由的實現

背景: 公司打算作一個(cms),最開始前端參照vue-element-admin的思路,驗證了前端鑑權的可能性,大佬寫的代碼思路清奇,值得學習,後來考慮諸多因素,接口安全性 前端鑑權的難度 以及項目的複雜度,最後選擇採用後端渲染動態路由的形式css

本文相對過期 請看Vue中後臺鑑權的另外一種思路 - 動態路由的實現與優化前端

使用的是Vue+Element的後臺管理模板githubvue

思路參考了一下文章git

Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)github

issues/293vuex

實現思路

基礎的一些思路和Vue 動態路由的實現Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)同樣,核心部分加入了本身的理解element-ui

  1. 每次路由跳轉 先判斷 是否登陸 登陸了纔會去進行路由相關邏輯
  2. 獲取變量getRouter,存在則直接放行 由於路由配置存在
  3. 假如刷新頁面getRouter變量就不存在了,因此 就要在去獲取一次
  4. 獲取到了在存儲到getRouter上,便於之後使用,減小請求

如下爲具體實現思路json

配置基礎路由

基礎路由爲不登陸也能夠訪問的路由segmentfault

const StaricRouterMap = [
  {
    path: '/login',
    component: login,
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'dashboard',
    hidden: true,
    meta: { title: '根目錄' },
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index')
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '*',
    redirect: '/404',
    hidden: true
  }, // .... 
]
export default new Router({
  mode: 'history', // 後端支持可開
  scrollBehavior: () => ({ y: 0 }),
  routes: StaricRouterMap
})
複製代碼

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

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

[{
  "id": 1,
  "name": "Nested",
  "code": null,
  "description": null,
  "url": "/nested",
  "generatemenu": 0,
  "sort": 0,
  "parentId": null,
  "permName": null,
  "redirect": "/nested/menu1",
  "title": "Nested",
  "icon": "nested",
  "children": [{
    "id": 2,
    "name": "Menu1",
    "code": null,
    "description": null,
    "url": "menu1",
    "generatemenu": 0,
    "sort": 0,
    "parentId": 1,
    "permName": null,
    "redirect": "",
    "title": "Menu1",
    "icon": "menu1",
    "children": [{
      "id": 4,
      "name": "Menu1-1",
      "code": null,
      "description": null,
      "url": "menu1-1",
      "generatemenu": 0,
      "sort": 0,
      "parentId": 2,
      "permName": null,
      "redirect": "",
      "title": "Menu1-1",
      "icon": "",
      "children": null
    }, {
      "id": 5,
      "name": "Menu1-2",
      "code": null,
      "description": null,
      "url": "menu1-2",
      "generatemenu": 0,
      "sort": 0,
      "parentId": 2,
      "permName": null,
      "redirect": "",
      "title": "Menu1-2",
      "icon": "",
      "children": null
    }]
  }, {
    "id": 3,
    "name": "Menu2",
    "code": null,
    "description": null,
    "url": "menu2",
    "generatemenu": 0,
    "sort": 0,
    "parentId": 1,
    "permName": null,
    "redirect": "",
    "title": "Menu2",
    "icon": "menu2",
    "children": null
  }]
}]
複製代碼

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

固然這不是直接用於渲染路由 咱們須要進行遞歸處理成爲咱們想要的數據

../router/_import

export default file => {
  return map[file] || null
}

const map = {
  Nested: () => import('@/views/layout/Layout'),
  Menu1: () => import('@/views/nested/menu1/index'),
  'Menu1-1': () => import('@/views/nested/menu1/menu1-1'),
  'Menu1-2': () => import('@/views/nested/menu1/menu1-2')
}
複製代碼

處理後端原始路由數據

../utils/addRouter

import _import from '../router/_import'// 獲取組件的方法

function addRouter(routerlist) {
  routerlist.forEach(e => {
    // 刪除無用屬性
    delete e.code
    delete e.sort
    delete e.generatemenu
    delete e.description
    delete e.permName
    delete e.id
    delete e.parentId

    e.path = e.url
    delete e.url
    e.component = _import(e.name) // 動態匹配組件
    if (e.redirect === '') {
      delete e.redirect
    }
    if (e.icon !== '' && e.title !== '') { // 配置 菜單標題 與 圖標
      e.meta = {
        title: e.title,
        icon: e.icon
      }
    }
    if (e.title !== '' && e.icon === '') {
      e.meta = {
        title: e.title
      }
    }
    delete e.icon
    delete e.title
    if (e.children != null) {
      // 存在子路由就遞歸
      addRouter(e.children)
    }
  })
  return routerlist
}

export { addRouter }
複製代碼

處理後的路由

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

[{
  "name": "Nested",
  "redirect": "/nested/menu1",
  "children": [{
    "name": "Menu1",
    "children": [{
      "name": "Menu1-1",
      "children": null,
      "path": "menu1-1",
      "meta": {
        "title": "Menu1-1"
      }
    }, {
      "name": "Menu1-2",
      "children": null,
      "path": "menu1-2",
      "meta": {
        "title": "Menu1-2"
      }
    }],
    "path": "menu1",
    "meta": {
      "title": "Menu1",
      "icon": "menu1"
    }
  }, {
    "name": "Menu2",
    "children": null,
    "path": "menu2",
    "component": null,
    "meta": {
      "title": "Menu2",
      "icon": "menu2"
    }
  }],
  "path": "/nested",
  "meta": {
    "title": "Nested",
    "icon": "nested"
  }
}]
複製代碼

(核心)合併路由

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

這裏也是最複雜的一塊,參考了一些別人的思路,後來完成了這個版本,這就是最上面實現思路的代碼

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 進度條
import 'nprogress/nprogress.css' // Progress 進度條樣式
import { Message } from 'element-ui'
import { addRouter } from './utils/addRouter'

var getRouter
router.beforeEach((to, from, next) => {
  NProgress.start() // 進度條
  if (localStorage.getItem('login_static') === '1') {
    if (to.path === '/login') {
      Message('您已登陸,如需切換用戶請退出從新登陸')
      next({ path: '/' })
    }
    if (!getRouter) {
      if (getRouterList('router')) {
        // 路由信息存在 說明請求階段以及完成 直接解析路由信息
        getRouter = getRouterList('router') // 拿到路由
        gotoRouter(to, next)
      } else {
        // localStorage不存在路由信息 這須要 請求路由信息 並解析路由
        setRouterList(to, next) // 存儲路由到localStorage
      }
    } else {
      // getRouter變量存在 說明路由信息存在 直接經過
      next()
    }
  } else {
    if (to.path === '/login') {
      next()
    } else {
      next(`/login`)
    }
  }
})

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

function gotoRouter(to, next) {
  try {
    getRouter = addRouter(getRouter)
    router.addRoutes(getRouter) // 動態添加路由
    global.antRouter = router.options.routes.concat(getRouter) // 將路由數據傳遞給全局變量,作側邊欄菜單渲染工做
    next({ to, replace: true })
  } catch (error) {
    localStorage.setItem('login_static', 0)
  }
}

function setRouterList(to, next) {
  store.dispatch('getRouter').then(asyncRouter => { // 請求路由數據
    localStorage.setItem('router', JSON.stringify(asyncRouter))
    getRouter = getRouterList('router') // 拿到路由
    gotoRouter(to, next)
  })
}

function getRouterList(name) {
  return JSON.parse(localStorage.getItem(name))
}
複製代碼

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

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

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

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

computed: {
	// ....
    routes() {
      return global.antRouter // 這裏應該最好使用vuex的全局變量
    },
   	// ....
  }
複製代碼

這樣就實現了動態渲染後端傳遞的路由,,感受仍是用能夠優化的地方,歡迎指正
相關文章
相關標籤/搜索