vue項(xiàng)目 動(dòng)態(tài)路由怎么做

vue項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)路由的方式大體可分為兩種:

1.簡(jiǎn)單的角色路由設(shè)置:比如只涉及到管理員和普通用戶的權(quán)限。通常直接在前端進(jìn)行簡(jiǎn)單的角色權(quán)限設(shè)置

前端這邊把路由寫(xiě)好,登錄的時(shí)候根據(jù)用戶的角色權(quán)限來(lái)動(dòng)態(tài)展示路由,(前端控制路由)
詳情可參閱花褲衩大佬的項(xiàng)目手把手...

2.復(fù)雜的路由權(quán)限設(shè)置:比如OA系統(tǒng)、多種角色的權(quán)限配置。通常需要后端返回路由列表,前端渲染使用

后臺(tái)傳來(lái)當(dāng)前用戶對(duì)應(yīng)權(quán)限的路由表,前端通過(guò)調(diào)接口拿到后處理(后端處理路由)

這兩種方法各有優(yōu)點(diǎn),效果都能實(shí)現(xiàn),我們公司是通過(guò)第二中種方法實(shí)現(xiàn)的,原因就是公司項(xiàng)目里有一個(gè)專(zhuān)門(mén)的用戶中心,里邊邏輯很復(fù)雜,不好返給前端用戶權(quán)限,擔(dān)心路由放到前端不安全(以上的話是公司的后臺(tái)同學(xué)講的),那好吧,抱著都試試、鍛煉下自己能力的態(tài)度,我們搞了第二種方法。

Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)(后臺(tái)傳遞路由,前端拿到并生成側(cè)邊欄)

思路整理

大體步驟:攔截路由->后臺(tái)取到路由->保存路由到localStorage(用戶登錄進(jìn)來(lái)只會(huì)從后臺(tái)取一次,其余都從本地取,所以用戶,只有退出在登錄路由才會(huì)更新)

后端管理創(chuàng)建菜單需要

菜單名字menName;
菜單路徑menPath;
菜單指向的資源menuUrl(也就是組件地址, 一般從views層級(jí)開(kāi)始寫(xiě))

前端登錄后通過(guò)接口請(qǐng)求拿到菜單數(shù)據(jù)后,

將菜單數(shù)據(jù)轉(zhuǎn)換格式 轉(zhuǎn)成前端需要的路由表

menName ---> name
menPath ---> path
menuUrl ---->components文件

轉(zhuǎn)換時(shí),用到這個(gè)方法找組件資源 把 menuUrl 可以變?yōu)閏omponents的格式,轉(zhuǎn)為組件文件

module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+

生成路由表

可以再過(guò)濾一遍生成的路由表
下面這個(gè)方法找到views底下所有的組件資源

require.context('@/views', true, /\.vue$/).keys().forEach(v => map.set(v.replace(/^\.(.*)\.vue$/,'$1'), true))

export default map

路由表里路由的組件在所有組件資源里沒(méi)找到時(shí),將該路由的path變?yōu)?404

過(guò)濾后,再動(dòng)態(tài)添加

getRouter.push({ path: '*', redirect: '/404', hidden: true });
router.addRoutes(getRouter); //動(dòng)態(tài)添加路由

代碼

前置工作:配置項(xiàng)目路由文件,該文件中沒(méi)有路由,或者存在一部分公共路由,即沒(méi)有權(quán)限的路由

import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout';
Vue.use(Router)
// 配置項(xiàng)目中沒(méi)有涉及權(quán)限的公共路由
export const constantRoutes = [
    {
        path: '/login',
        component: () => import('@/views/login'),
        hidden: true
    },
    {
        path: '/404',
        component: () => import('@/views/404'),
        hidden: true
    },
]

const createRouter = () => new Router({
    mode: 'history',
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRoutes
})
const router = createRouter()

export function resetRouter() {
    const newRouter = createRouter()
    router.matcher = newRouter.matcher
}

export default router

1.登錄后通過(guò)接口拿到后端返回的路由數(shù)據(jù)

每個(gè)路由都使用到組件Layout,這個(gè)組件是整體的頁(yè)面布局:左側(cè)菜單列,右側(cè)頁(yè)面,所以children下邊的第一級(jí)路由就是你自己的開(kāi)發(fā)的頁(yè)面,meta里包含著路由的名字,以及路由對(duì)應(yīng)的icon;
因?yàn)榭赡軙?huì)有多級(jí)菜單,所以會(huì)出現(xiàn)children下邊嵌套children的情況;
路由是數(shù)組格式

后端返回的路由格式
"data": {
    "router": [
      {
        "path": "",
        "component": "Layout",
        "redirect": "dashboard",
        "children": [
          {
            "path": "dashboard",
            "component": "dashboard/index",  
            "meta": {
              "title": "首頁(yè)",
              "icon": "dashboard"
            }
          }
        ]
      },
      {
        "path": "/example",
        "component": "Layout",
        "redirect": "/example/table",
        "name": "Example",
        "meta": {
          "title": "案例",
          "icon": "example"
        },
        "children": [
          {
            "path": "table",
            "name": "Table",
            "component": "table/index",
            "meta": {
              "title": "表格",
              "icon": "table"
            }
          },
          {
            "path": "tree",
            "name": "Tree",
            "component": "tree/index",
            "meta": {
              "title": "樹(shù)形菜單",
              "icon": "tree"
            }
          }
        ]
      },
      {
        "path": "/form",
        "component": "Layout",
        "children": [
          {
            "path": "index",
            "name": "Form",
            "component": "form/index",
            "meta": {
              "title": "表單",
              "icon": "form"
            }
          }
        ]
      },
      {
        "path": "*",
        "redirect": "/404",
        "hidden": true
      }
    ]
  }

實(shí)際前端需要的 component是 component: () => import('@/views/content/classify'),

2.將后端傳回的"component": "Layout", 轉(zhuǎn)為 "component": Layout組件對(duì)象

因?yàn)橛卸嗉?jí)路由的出現(xiàn),所以要寫(xiě)成遍歷遞歸方法,確保把每個(gè)component轉(zhuǎn)成組件對(duì)象
因?yàn)楹笈_(tái)傳回的是字符串,所以要把加載組件的過(guò)程 封裝成一個(gè)方法,用這個(gè)方法在遍歷中使用;詳情查看項(xiàng)目里的router文件夾下的 _import_development.js和_import_production.js文件
Layout我放的目錄跟其他文件的目錄不一樣,所以我在遍歷里單獨(dú)處理,各位小伙伴可自己調(diào)整哈

router 文件夾下的 _import_development.js
module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+
filterAsyncRouter方法 將后端返回的路由改為加載組件的過(guò)程
const _import = require('./router/_import_' + process.env.NODE_ENV) // 獲取組件的方法

import LayoutPage from '@/views/layout'  // Layout 是架構(gòu)組件,不在后臺(tái)返回,在文件里單獨(dú)引入

function filterAsyncRouter(asyncRouterMap) { // 遍歷后臺(tái)傳來(lái)的路由字符串,轉(zhuǎn)換為組件對(duì)象
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') { // Layout組件特殊處理
        route.component = LayoutPage 
      } else {
        route.component = _import(route.component)
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })

  return accessedRouters
}

3.使用beforeEach、addRoutes、localStorage來(lái)配合實(shí)現(xiàn)拿到數(shù)據(jù)再轉(zhuǎn)換成路由

beforeEach路由攔截,進(jìn)入判斷,如果發(fā)現(xiàn)本地沒(méi)有路由數(shù)據(jù),那就利用axios后臺(tái)取一次,取完以后,利用localStorage存儲(chǔ)起來(lái),利用addRoutes動(dòng)態(tài)添加路由,

ps:beforeEach好壞啊,一步小心就進(jìn)入到了他的死循環(huán),瀏覽器都tm崩了,得在一開(kāi)始就加判斷,拿到路由了,就直接next(),嚶嚶嚶
global.antRouter是為了傳遞數(shù)據(jù)給左側(cè)菜單組件進(jìn)行渲染

import axios from 'axios'

var getRouter   //  用來(lái)獲取后臺(tái)拿到的路由

router.beforeEach((to, from, next) => {
  if (!getRouter) {  // 不加這個(gè)判斷,路由會(huì)陷入死循環(huán) (如果沒(méi)有路由)
    if (!getObjArr('router')) {  // 緩存里沒(méi)有路由,axios重新獲取
      axios.get('https://www.easy-mock.com/mock/5a5da330d9b48c260cb42ca8/example/antrouter').then(res => {
        getRouter = res.data.data.router//后臺(tái)拿到路由
        saveObjArr('router', getRouter) //存儲(chǔ)路由到localStorage

        routerGo(to, next) //  添加路由  執(zhí)行路由跳轉(zhuǎn)方法
      })
    } else { //  從localStorage拿到了路由
      getRouter = getObjArr('router')//拿到路由
      routerGo(to, next)  //  //  添加路由  執(zhí)行路由跳轉(zhuǎn)方法
    }
  } else {
    next()
  }

})

// routerGo
function routerGo(to, next) {
 // 這里后端返回的數(shù)據(jù)getRouter  可能不是我們需要的結(jié)構(gòu),那么需要先有一步轉(zhuǎn)換的步驟
// 直到轉(zhuǎn)換成前面第一步那種vue標(biāo)準(zhǔn)路由表的格式
  getRouter = filterAsyncRouter(getRouter)  // 過(guò)濾路由  轉(zhuǎn)換路由的component   
  // filterAsyncRoute方法是把后端返回的路由數(shù)據(jù) 轉(zhuǎn)換為前端要的路由結(jié)構(gòu)
  getRouter.push({ path: '*', redirect: '/404', hidden: true });
  router.addRoutes(getRouter) //動(dòng)態(tài)添加路由
  global.antRouter = getRouter //將路由數(shù)據(jù)傳遞給全局變量,做側(cè)邊欄菜單渲染工作
  next({ ...to, replace: true })
}

function saveObjArr(name, data) { //localStorage 存儲(chǔ)數(shù)組對(duì)象的方法
  localStorage.setItem(name, JSON.stringify(data))
}

function getObjArr(name) { //localStorage 獲取數(shù)組對(duì)象的方法
  return JSON.parse(window.localStorage.getItem(name));

}

4.拿到遍歷好的路由,進(jìn)行左側(cè)菜單渲染

上邊第三步會(huì)給 global.antRouter賦值,這是一個(gè)全局變量(可以用vuex替代),菜單那邊拿到路由,進(jìn)行渲染

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容