从零开始Vue3+Element Plus后台管理系统(18)——权限路由实现

一开始打算做两种模式的路由权限,最后还是分成了3种,分别是:

  1. 前端固定路由,所有路由是固定的,通过权限过滤菜单和显示
  2. 前端动态路由,通过权限过滤路由表和菜单
  3. 后端动态路由,获取接口返回数据,挂载路由表和菜单
VITE_PERMISSION_MODE = 'CONSTANT'
# VITE_PERMISSION_MODE = 'FRONT'
# VITE_PERMISSION_MODE = 'BACK'

因为权限配置灵活,实际工作中一般采用第3种。在个人demo或者权限简单的项目中,第一种也够用。

简要流程

下图是一个简单流程说明,主要在路由的全局前置守卫router.beforeEach体现。

从零开始Vue3+Element Plus后台管理系统(18)——权限路由实现_第1张图片

各个模块的路由对应views,分文件放在router/modules,方便后面统一使用
从零开始Vue3+Element Plus后台管理系统(18)——权限路由实现_第2张图片

路由入口文件 router/index.ts

  1. 存放系统固定的几个路由(登录、注册、403、404…)
const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    redirect: '/dashboard',
    hidden: true,
    meta: {
      title: 'home'
    }
  },
  {
    path: '/login',
    name: 'Login',
    hidden: true,
    meta: {
      title: 'signIn'
    },
    component: () => import(/* webpackChunkName: "login" */ '../views/login/login.vue')
  },
  {
    path: '/403',
    name: '403',
    hidden: true,
    meta: {
      title: '没有权限'
    },
    component: () => import(/* webpackChunkName: "400" */ '../views/403.vue')
  }
]
const lastRoutes = [
  {
    path: '/:pathMatch(.*)*',
    name: '404',
    hidden: true,
    meta: {
      title: '404'
    },
    component: () => import(/* webpackChunkName: "400" */ '../views/404.vue')
  }
]
  1. 获取router/modules文件夹中注册个模块路由
const modules = import.meta.glob('./modules/**/*.ts', { eager: true })

let routeModuleList: RouteRecordRaw[] = []

// 获取路由并排序
Object.values(modules).forEach((key: any) => {
  const mod = key.default || []
  const modList = Array.isArray(mod) ? [...mod] : [mod]
  routeModuleList.push(...modList)
})
  1. 导出挂载在app上的router。

前端固定路由模式会在此把所有路由挂载上。动态路由只需要挂载constanctRoutes,动态路由在前置守卫中挂载。

let routes = constantRoutes

// 前端固定路由模式
if (import.meta.env.VITE_PERMISSION_MODE === 'CONSTANT') {
  routes = [...routeModuleList, ...constantRoutes]
}

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router
export { constantRoutes, routeModuleList, lastRoutes }

除了router文件夹中的文件,我们还需要:

  • useUserStore 管理用户状态,判断路由权限时会用到它,侧边栏菜单的路由表也暂时放在这里。
  • 两个API:获取用户信息、获取路由信息

通用判断

当一个导航触发时,我们通过前置守卫来处理各种情况下的导航——判断是否放行,或是跳转其他页面。

代码放在router/permission.ts,如果你习惯放在src下也没问题。

  • 如果用户未登录,
    当前路由在白名单则直接放行,否则跳转到登录页面/login

  • 如果用户已登录,导航到/login,则直接进入系统首页/

// 用户已登录
  if (useUser.userid) {
    if (to.path === '/login') {
      return '/'
    }
    ...
  } else {
    // 白名单,直接放行
    if (whiteList.indexOf(to.path) > -1) return true
    // 非白名单,去登录
    else return '/login'
  }

继续往下走,需要用到开始说的3种路由模式分别处理。

1. 前端固定路由

我们扩展了_RouteRecordBase接口,增加roles用于保存可访问路由的角色

declare module 'vue-router' {
  interface _RouteRecordBase {
    hidden?: boolean
  }

  interface RouteMeta {
    order?: number
    roles?: array
  }
}

router/modules/system.ts

    ...
    {
        path: 'account',
        name: 'account',
        meta: {
          title: 'accountManagement',
          roles: [RoleEnum.USER]   // 用于判断角色权限
        },
        component: () => import('~/views/system/account/index.vue')
    },
    ...

在登录状态下,只需要判断用户角色是否能够访问这个路由即可,如果没有权限则跳转去403页面

// 前端固定路由模式,如果没有权限,进入403页面
    if (
      import.meta.env.VITE_PERMISSIOIN_MODE === 'CONSTANT' &&
      to.meta.roles &&
      !to.meta.roles.includes(role)
    ) {
      return '/403'
    }

前端动态路由模式

无论是前端还是后端动态路由模式,都需要动态挂载。

    // 前端固定路由模式,如果没有权限,进入403页面
    if (
      ...
    }
    // 前端动态路由和后端动态路由,动态挂载路由
    else {
      if (!to.redirectedFrom) {
        await addAsyncRoutes(router, useUser)

        return { ...to, replace: true }
      } else return true
    }

前端模式中,通过路由角色权限和当前用户角色比对,筛选出有权限的路由,挂载到router上

后端模式中,通过接口返回数据,组装成前端可用的路由数据,挂载到router上

动态挂载路由: router.addRoute(route)

在此,因为addRoute不会改变router.optioins.routes(创建路由器时的原始routes),所以挂载后的路由数据,我们需要保存起来,以便侧边栏菜单使用。

async function addAsyncRoutes(router, userStore) {
  const permissionMode = import.meta.env.VITE_PERMISSIOIN_MODE

  let filteredRoutes = <RouteRecordRaw[]>[]
  // 前端动态路由模式
  if (permissionMode === 'FRONT') {
    filteredRoutes = filterFunc(routeModuleList)
  }
  // 后端动态路由模式
  if (permissionMode === 'BACK') {
    let routes = await getBackAsyncRoutes(userStore.userid)
    filteredRoutes = formatAsyncRoutes(routes)
  }
  console.log(filteredRoutes)
  filteredRoutes.forEach((val) => router.addRoute(val))
  userStore.setAsyncRoutes(filteredRoutes) // 保存最新的路由信息
}

所以前端动态路由,只需过滤出如何角色权限的路由即可,在router/index导出的模块路由数据中比对role即可。

后端动态路由

它和前端模式的区别就是,路由数据通过接口获取,拿到数据后,最重要的操作就是重新拼装成前端可用的形式。

async function getBackAsyncRoutes(userid: number) {
  return await systemApi.getRoutes({ userid })
}

const modules = import.meta.glob('~/views/**/**.vue')

// 把接口路由组装成前端可用结构
function formatAsyncRoutes(routes: any[]) {
  routes.forEach((r) => {
    if (r.component === 'layout' || !r.component) {
      r.component = Layout
    } else {
      r.component = modules[`/src/views${r.component}`]
    }
    if (r.children && r.children.length > 0) {
      r.children = formatAsyncRoutes(r.children)
    }
  })
  return routes
}

产品需求千千万,万变不离其宗

写在最后

路由权限这块,这也是项目最初立下的Flag,在我✍️写到系统第18篇文章的时候终于写了,刚开始的时候还是碰到一些问题,但是当我细细梳理,画出思维导图,写完文章后,觉得一切都清晰起来。

如果一味前行不作停留,不去回顾和总结,很多东西带着一知半解就过去了忘记了。

在忙碌的项目中,我觉得自己变成了一个螺丝钉,没有丝毫成长。停下来做一些输出和总结,整装待发反而更好。

项目地址

本项目GIT地址:https://github.com/lucidity99/mocha-vue3-system

如果有帮助,给个star ✨ 点个赞

你可能感兴趣的:(Plus的后台管理系统,数学建模)