欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3純前端實(shí)現(xiàn)Vue路由權(quán)限的方法詳解

 更新時(shí)間:2022年05月12日 10:41:53   作者:liy1wen  
這篇文章主要給大家介紹了關(guān)于Vue3純前端實(shí)現(xiàn)Vue路由權(quán)限的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Vue3具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

前言

在開發(fā)管理后臺(tái)時(shí),都會(huì)存在多個(gè)角色登錄,登錄成功后,不同的角色會(huì)展示不同的菜單路由。這就是我們通常所說的動(dòng)態(tài)路由權(quán)限,實(shí)現(xiàn)路由權(quán)限的方案有多種,比較常用的是由前端使用addRoutes(V3版本改成了addRoute)動(dòng)態(tài)掛載路由和服務(wù)端返回可訪問的路由菜單這兩種。今天主要是從前端角度,實(shí)現(xiàn)路由權(quán)限的功能。

RBAC模型

前端實(shí)現(xiàn)路由權(quán)限主要是基于RBAC模型。

RBAC(Role-Based Access Control)即:基于角色的權(quán)限控制。通過角色關(guān)聯(lián)用戶,角色關(guān)聯(lián)權(quán)限的方式間接賦予用戶權(quán)限。

代碼實(shí)現(xiàn)

登錄

首先是登錄,登錄成功后,服務(wù)端會(huì)返回用戶登錄的角色、token以及用戶信息等。用戶角色如:role: ['admin']。我們一般會(huì)將這些信息保存到Vuex里。

const login = () => {
  ruleFormRef.value?.validate((valid: boolean) => {
    if (valid) {
      store.dispatch('userModule/login', { ...accountForm })
    } else {
      console.log('error submit!')
    }
  })
}

信息存儲(chǔ)在Vuex:

async login({ commit }, payload: IRequest) {
  // 登錄獲取token
  const { data } = await accountLogin(payload)
  commit('SET_TOKEN', data.token)
  localCache.setCache('token', data.token)
  // 獲取用戶信息
  const userInfo = await getUserInfo(data.id)
  commit('SET_USERINFO', userInfo.data)
  localCache.setCache('userInfo', userInfo.data)
  router.replace('/')
},

服務(wù)端返回token:

服務(wù)端返回用戶信息:

菜單信息

路由菜單信息分為兩種,一種是默認(rèn)路由constantRoutes,即所有人都能夠訪問的頁面,不需去通過用戶角色去判斷,如login、404、首頁等等。還有一種就是動(dòng)態(tài)路由asyncRoutes,用來放置有權(quán)限(roles 屬性)的路由,這部分的路由是需要訪問權(quán)限的。我們最終將在動(dòng)態(tài)路由里面根據(jù)用戶角色篩選出能訪問的動(dòng)態(tài)路由列表。

我們將默認(rèn)路由和動(dòng)態(tài)路由都寫在router/index.ts里。

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const Layout = () => import('@/Layout')

/** 常駐路由 */
export const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue'),
    meta: {
      title: '登錄',
      hidden: true
    }
  },
  {
    path: '/',
    component: Layout,
    redirect: '/analysis/dashboard',
    name: 'Analysis',
    meta: {
      hidden: false,
      icon: 'icon-home',
      title: '系統(tǒng)總覽'
    },
    children: [
      {
        path: '/analysis/dashboard',
        name: 'Dashboard',
        component: () => import('@/views/analysis/dashboard/dashboard.vue'),
        meta: { title: '商品統(tǒng)計(jì)', hidden: false }
      },
      {
        path: '/analysis/overview',
        name: 'Overview',
        component: () => import('@/views/analysis/overview/overview.vue'),
        meta: { title: '核心技術(shù)', hidden: false }
      }
    ]
  },
  {
    path: '/product',
    component: Layout,
    redirect: '/product/category',
    name: 'Product',
    meta: {
      hidden: false,
      icon: 'icon-tuijian',
      title: '商品中心'
    },
    children: [
      {
        path: '/product/category',
        name: 'Category',
        component: () => import('@/views/product/category/category.vue'),
        meta: { title: '商品類別', hidden: false }
      },
      {
        path: '/product/goods',
        name: 'Goods',
        component: () => import('@/views/product/goods/goods.vue'),
        meta: { title: '商品信息', hidden: false }
      }
    ]
  },
  {
    path: '/story',
    component: Layout,
    redirect: '/story/chat',
    name: 'Story',
    meta: {
      hidden: false,
      icon: 'icon-xiaoxi',
      title: '隨便聊聊'
    },
    children: [
      {
        path: '/story/chat',
        name: 'Story',
        component: () => import('@/views/story/chat/chat.vue'),
        meta: { title: '你的故事', hidden: false }
      },
      {
        path: '/story/list',
        name: 'List',
        component: () => import('@/views/story/list/list.vue'),
        meta: { title: '故事列表', hidden: false }
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404.vue'),
    meta: {
      title: 'Not Found',
      hidden: true
    }
  },
  {
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    meta: {
      hidden: true,
      title: 'Not Found'
    }
  }
]
/**
 * 動(dòng)態(tài)路由
 * 用來放置有權(quán)限(roles 屬性)的路由
 * 必須帶有 name 屬性
 */
export const asyncRoutes: RouteRecordRaw[] = [
  {
    path: '/system',
    component: Layout,
    redirect: '/system/department',
    name: 'System',
    meta: {
      hidden: false,
      icon: 'icon-shezhi',
      title: '系統(tǒng)管理'
    },
    children: [
      {
        path: '/system/department',
        name: 'Department',
        component: () => import('@/views/system/department/department.vue'),
        meta: { title: '部門管理', hidden: false, role: ['admin'] }
      },
      {
        path: '/system/menu',
        name: 'Menu',
        component: () => import('@/views/system/menu/menu.vue'),
        meta: { title: '菜單管理', hidden: false, role: ['admin'] }
      },
      {
        path: '/system/role',
        name: 'Role',
        component: () => import('@/views/system/role/role.vue'),
        meta: { title: '角色管理', hidden: false, role: ['editor'] }
      },
      {
        path: '/system/user',
        name: 'User',
        component: () => import('@/views/system/user/user.vue'),
        meta: { title: '用戶管理', hidden: false, role: ['editor'] }
      }
    ]
  }
]
const router = createRouter({
  history: createWebHashHistory(),
  routes: constantRoutes
})
export default router

我們將系統(tǒng)管理這個(gè)菜單作為動(dòng)態(tài)路由部分,里面的子菜單meta屬性下都分配有一個(gè)訪問權(quán)限的role屬性,我們需要將role屬性和用戶角色去匹配是否用戶具有訪問權(quán)限。

動(dòng)態(tài)路由篩選

思路:

我們登錄得到了用戶角色role和寫好路由信息(分為默認(rèn)路由列表和動(dòng)態(tài)路由列表),之后我們需要做的就是通過用戶角色role去匹配動(dòng)態(tài)路由列表里面每個(gè)子路由的role屬性,得到能夠訪問的動(dòng)態(tài)路由部分,將默認(rèn)路由和我們得到的動(dòng)態(tài)路由進(jìn)行拼接這樣我們就得到了用戶能夠訪問的完整前端路由,最后使用addRoute將完整路由掛載到router上。

有了這樣一個(gè)比較清晰的思路,接下來我們就來嘗試著實(shí)現(xiàn)它。

我們可以將這塊的邏輯也放在Vuex里面,在store/modules下新建一個(gè)permission.ts文件。

首先我們需要寫一個(gè)方法去判斷用戶是否具有訪問單個(gè)路由的權(quán)限:

/**
 * 判斷用戶是否有權(quán)限訪問單個(gè)路由
 * roles:用戶角色
 * route:訪問的路由
 */
const hasPermission = (roles: string[], route: any) => {
  if (route.meta && route.meta.roles) {
    return roles.some((role) => {
      if (route.meta?.roles !== undefined) {
        return route.meta.roles.includes(role)
      } else {
        return false
      }
    })
  } else {
    return true
  }
}

實(shí)現(xiàn)的核心是route.meta.roles.includes(role),即路由的roles是否包含了用戶的角色,包含了就可以訪問,否則不能。

對(duì)用戶角色進(jìn)行some遍歷主要是用戶的角色可能存在多個(gè),如:['admin', 'editor']。

這樣我們就實(shí)現(xiàn)了單個(gè)路由訪問權(quán)限的篩選,但是動(dòng)態(tài)路由列表是一個(gè)數(shù)組,每個(gè)一級(jí)路由下可能有二級(jí)路由、三級(jí)路由甚至更多,這樣我們就需要用到遞歸函數(shù)進(jìn)行篩選:

/**
 * 篩選可訪問的動(dòng)態(tài)路由
 * roles:用戶角色
 * route:訪問的動(dòng)態(tài)列表
 */
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
  const res: RouteRecordRaw[] = []
  routes.forEach((route) => {
    const r = { ...route }
    if (hasPermission(roles, r)) {
      if (r.children) {
        r.children = filterAsyncRoutes(r.children, roles)
      }
      res.push(r)
    }
  })
  return res
}

這樣,通過調(diào)用filterAsyncRoutes這個(gè)函數(shù),然后傳入utes:動(dòng)態(tài)路由列表,roles:用戶角色兩個(gè)參數(shù)就能得到我們能訪問的動(dòng)態(tài)路由了。

然后我們將篩選得到的動(dòng)態(tài)路由和默認(rèn)路由通過concat拼接得到完整可訪問路由,最后通過addRoute掛載。

我們將以上代碼邏輯整理到sion.ts里:

import { Module } from 'vuex'
import { RouteRecordRaw } from 'vue-router'
import { constantRoutes, asyncRoutes } from '@/router'
import { IRootState } from '../types'
import router from '@/router'
/**
 * 判斷用戶是否有權(quán)限訪問單個(gè)路由
 * roles:用戶角色
 * route:訪問的路由
 */
const hasPermission = (roles: string[], route: any) => {
  if (route.meta && route.meta.roles) {
    return roles.some((role) => {
      if (route.meta?.roles !== undefined) {
        return route.meta.roles.includes(role)
      } else {
        return false
      }
    })
  } else {
    return true
  }
}
/**
 * 篩選可訪問的動(dòng)態(tài)路由
 * roles:用戶角色
 * route:訪問的動(dòng)態(tài)列表
 */
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
  const res: RouteRecordRaw[] = []
  routes.forEach((route) => {
    const r = { ...route }
    if (hasPermission(roles, r)) {
      if (r.children) {
        r.children = filterAsyncRoutes(r.children, roles)
      }
      res.push(r)
    }
  })
  return res
}
interface IPermissionState {
  routes: RouteRecordRaw[]
  dynamicRoutes: RouteRecordRaw[]
}
export const routesModule: Module<IPermissionState, IRootState> = {
  namespaced: true,
  state: {
    routes: [],
    dynamicRoutes: []
  },
  getters: {},
  mutations: {
    SET_ROUTES(state, routes) {
      state.routes = routes
    },
    SET_DYNAMICROUTES(state, routes) {
      state.dynamicRoutes = routes
    }
  },
  actions: {
    generateRoutes({ commit }, { roles }) {
      // accessedRoutes: 篩選出的動(dòng)態(tài)路由
      const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      // 將accessedRoutes和默認(rèn)路由constantRoutes拼接得到完整可訪問路由
      commit('SET_ROUTES', constantRoutes.concat(accessedRoutes))
      commit('SET_DYNAMICROUTES', accessedRoutes)
      // 通過addRoute將路由掛載到router上
      accessedRoutes.forEach((route) => {
        router.addRoute(route)
      })
    }
  }
}

這樣就實(shí)現(xiàn)了所有代碼邏輯。有個(gè)問題,addRoute應(yīng)該何時(shí)調(diào)用,在哪里調(diào)用?

登錄后,獲取用戶的權(quán)限信息,然后篩選有權(quán)限訪問的路由,再調(diào)用addRoute添加路由。這個(gè)方法是可行的。但是不可能每次進(jìn)入應(yīng)用都需要登錄,用戶刷新瀏覽器又要登錄一次。所以addRoute還是要在全局路由守衛(wèi)里進(jìn)行調(diào)用。

我們?cè)趓outer文件夾下創(chuàng)建一個(gè)permission.ts,用于寫全局路由守衛(wèi)相關(guān)邏輯:

import router from '@/router'
import { RouteLocationNormalized } from 'vue-router'
import localCache from '@/utils/cache'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store'

NProgress.configure({ showSpinner: false })
const whiteList = ['/login']
router.beforeEach(
  async (
    to: RouteLocationNormalized,
    from: RouteLocationNormalized,
    next: any
  ) => {
    document.title = to.meta.title as string
    const token: string = localCache.getCache('token')
    NProgress.start()
    // 判斷該用戶是否登錄
    if (token) {
      if (to.path === '/login') {
        // 如果登錄,并準(zhǔn)備進(jìn)入 login 頁面,則重定向到主頁
        next({ path: '/' })
        NProgress.done()
      } else {
        const roles = store.state.userModule.roles
        store.dispatch('routesModule/generateRoutes', { roles })
        // 確保添加路由已完成
        // 設(shè)置 replace: true, 因此導(dǎo)航將不會(huì)留下歷史記錄
        next({ ...to, replace: true })
        // next()
      }
    } else {
      // 如果沒有 token
      if (whiteList.includes(to.path)) {
        // 如果在免登錄的白名單中,則直接進(jìn)入
        next()
      } else {
        // 其他沒有訪問權(quán)限的頁面將被重定向到登錄頁面
        next('/login')
        NProgress.done()
      }
    }
  }
)
router.afterEach(() => {
  NProgress.done()
})

這樣,完整的路由權(quán)限功能就完成了。我們可以做一下驗(yàn)證:

動(dòng)態(tài)路由

我們登錄的用戶角色為roles: ['editor'],動(dòng)態(tài)路由為系統(tǒng)管理菜單,里面有四個(gè)子路由對(duì)應(yīng)有roles,正常情況下我們可以訪問系統(tǒng)管理菜單下的角色管理和用戶管理。

渲染菜單界面

篩選出的動(dòng)態(tài)路由

沒有任何問題!

總結(jié)

前端實(shí)現(xiàn)動(dòng)態(tài)路由是基于RBAC思想,通過用戶角色去篩選出可以訪問的路由掛載在router上。這樣實(shí)現(xiàn)有一點(diǎn)不好的地方在于菜單信息是寫死在前端,以后要改個(gè)顯示文字或權(quán)限信息,需要重新修改然后編譯。

到此這篇關(guān)于Vue3純前端實(shí)現(xiàn)Vue路由權(quán)限的文章就介紹到這了,更多相關(guān)Vue3純前端實(shí)現(xiàn)路由權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論