vue-element-plus-admin 第4期|权限系统实战:动态路由 + 权限控制机制全解析

1. 路由系统架构设计

vue-element-plus-admin 项目采用了灵活且强大的路由系统设计,基于 Vue Router 实现,并结合权限系统形成了一个完整的路由控制方案。

1.1 路由系统整体架构

项目路由系统的整体架构可以概括为以下几个部分:

  1. 路由定义 :在 src/router/index.ts 中定义静态路由和异步路由
  2. 路由生成 :通过 src/utils/routerHelper.ts 中的辅助函数,根据权限动态生成路由
  3. 路由守卫 :在 src/permission.ts 中实现路由拦截与权限控制
  4. 状态管理:使用 Pinia 中的 permission store 管理路由状态
  5. 组件渲染:基于路由配置渲染菜单和面包屑等导航组件

这种设计将路由配置、权限控制和UI渲染有机结合,实现了灵活的页面访问控制和导航展示。

1.2 路由类型定义

项目使用 TypeScript 定义了详细的路由类型,扩展了 Vue Router 原有的类型定义:

typescript 复制代码
// types/router.d.ts (简化版)
declare global {
  interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
    name: string
    meta: RouteMeta
    component?: Component | string
    children?: AppRouteRecordRaw[]
    props?: Recordable
    fullPath?: string
  }

  interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
    name: string
    meta: RouteMeta
    component: string
    path: string
    redirect?: string
    children?: AppCustomRouteRecordRaw[]
  }
}

关键扩展:

  • AppRouteRecordRaw:扩展了 Vue Router 的 RouteRecordRaw,添加了更丰富的元数据
  • AppCustomRouteRecordRaw:用于从后端获取的路由结构,组件以字符串形式表示路径

1.3 路由配置结构

src/router/index.ts 中,路由被分为两大类:

  1. 静态路由(constantRouterMap):不需要权限即可访问的路由

    typescript 复制代码
    export const constantRouterMap: AppRouteRecordRaw[] = [
      {
        path: '/',
        component: Layout,
        redirect: '/dashboard/analysis',
        name: 'Root',
        meta: {
          hidden: true
        }
      },
      {
        path: '/login',
        component: () => import('@/views/Login/Login.vue'),
        name: 'Login',
        meta: {
          hidden: true,
          title: t('router.login'),
          noTagsView: true
        }
      },
      // 其他基础路由...
    ]
  2. 动态路由(asyncRouterMap):需要根据用户权限动态加载的路由

    typescript 复制代码
    export const asyncRouterMap: AppRouteRecordRaw[] = [
      {
        path: '/dashboard',
        component: Layout,
        redirect: '/dashboard/analysis',
        name: 'Dashboard',
        meta: {
          title: t('router.dashboard'),
          icon: 'vi-ant-design:dashboard-filled',
          alwaysShow: true
        },
        children: [
          {
            path: 'analysis',
            component: () => import('@/views/Dashboard/Analysis.vue'),
            name: 'Analysis',
            meta: {
              title: t('router.analysis'),
              noCache: true,
              affix: true
            }
          },
          // 其他子路由...
        ]
      },
      // 更多功能模块路由...
    ]

1.4 路由元数据(Meta)的应用

路由元数据(Meta)是实现高级路由功能的关键,项目中路由元数据包括:

typescript 复制代码
interface RouteMeta {
  // 标题,用于显示在菜单和面包屑
  title: string
  // 图标,用于菜单显示
  icon?: string
  // 是否在菜单中隐藏
  hidden?: boolean
  // 总是显示根菜单
  alwaysShow?: boolean
  // 角色列表,控制页面权限
  roles?: string[]
  // 按钮级权限控制
  permission?: string[]
  // 是否缓存该页面
  noCache?: boolean
  // 是否添加到标签视图
  noTagsView?: boolean
  // 是否固定在标签视图中
  affix?: boolean
  // 高亮菜单
  activeMenu?: string
  // 面包屑中是否可见
  breadcrumb?: boolean
  // 过渡名称
  transitionName?: string
  // 子菜单是否允许折叠
  noCollapse?: boolean
  // 不可跳转
  canTo?: boolean
  // 其他自定义属性...
}

这些元数据不仅用于控制路由的访问权限,还用于指导UI组件(如菜单、面包屑、标签页)的渲染行为。

2. 静态路由与动态路由实现

vue-element-plus-admin 项目支持三种路由生成模式:静态路由、前端动态路由和后端动态路由,这种设计极大地提高了系统的灵活性。

2.1 静态路由模式

静态路由是最简单的路由模式,直接使用预定义的路由配置:

typescript 复制代码
// src/permission.ts 中的相关代码
if (!appStore.getDynamicRouter) {
  await permissionStore.generateRoutes('static')
}

在静态模式下,generateRoutes 方法会直接使用 asyncRouterMap 作为动态路由部分:

typescript 复制代码
// src/store/modules/permission.ts 中的相关代码
if (type === 'static') {
  // 直接读取静态路由表
  routerMap = cloneDeep(asyncRouterMap)
}

这种模式适合小型项目或权限结构简单的场景,所有用户看到相同的菜单结构。

2.2 前端动态路由模式

前端动态路由模式基于用户角色在前端过滤路由,实现方式如下:

typescript 复制代码
// src/permission.ts 中的相关代码
if (appStore.getDynamicRouter) {
  appStore.serverDynamicRouter
    ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
    : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
}

前端动态路由的核心是通过 generateRoutesByFrontEnd 函数,根据用户拥有的权限标识过滤路由:

typescript 复制代码
// src/utils/routerHelper.ts 中的相关代码
export const generateRoutesByFrontEnd = (
  routes: AppRouteRecordRaw[],
  keys: string[],
  basePath = '/'
): AppRouteRecordRaw[] => {
  const res: AppRouteRecordRaw[] = []

  for (const route of routes) {
    // 略过隐藏路由
    if (meta.hidden && !meta.canTo) {
      continue
    }

    // 根据用户权限 keys 匹配路由
    for (const item of keys) {
      if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
        data = Object.assign({}, route)
      } else {
        const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
        if (routePath === item || meta.followRoute === item) {
          data = Object.assign({}, route)
        }
      }
    }

    // 递归处理子路由
    if (route.children && data) {
      data.children = generateRoutesByFrontEnd(
        route.children,
        keys,
        pathResolve(basePath, data.path)
      )
    }
    
    // 添加到结果集
    if (data) {
      res.push(data as AppRouteRecordRaw)
    }
  }
  return res
}

这种模式适合权限结构较为稳定,但需要根据用户角色显示不同菜单的场景。

2.3 后端动态路由模式

后端动态路由是最灵活的模式,路由结构完全由后端返回:

typescript 复制代码
// 后端路由模式
if (appStore.serverDynamicRouter) {
  await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
}

后端返回的路由通过 generateRoutesByServer 函数处理:

typescript 复制代码
export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
  const res: AppRouteRecordRaw[] = []

  for (const route of routes) {
    const data: AppRouteRecordRaw = {
      path: route.path,
      name: route.name,
      redirect: route.redirect,
      meta: route.meta
    }
    
    // 动态解析组件
    if (route.component) {
      const comModule = modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`]
      const component = route.component as string
      
      // 特殊处理布局组件
      data.component =
        component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule
    }
    
    // 递归处理子路由
    if (route.children) {
      data.children = generateRoutesByServer(route.children)
    }
    res.push(data as AppRouteRecordRaw)
  }
  return res
}

这里的关键是将字符串形式的组件路径解析为实际的组件:

  • # 表示使用主布局组件
  • ## 表示使用父级布局
  • 其他情况通过动态导入加载组件

后端动态路由模式最为灵活,适合权限结构频繁变动或需要在运行时调整菜单结构的场景。

2.4 路由动态添加机制

无论哪种模式生成的路由,最终都通过 Vue Router 的 addRoute 方法动态添加到路由系统:

typescript 复制代码
// src/permission.ts
permissionStore.getAddRouters.forEach((route) => {
  router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
})

添加完成后,设置路由初始化标志并跳转到目标页面:

typescript 复制代码
const redirectPath = from.query.redirect || to.path
const redirect = decodeURIComponent(redirectPath as string)
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
permissionStore.setIsAddRouters(true)
next(nextData)

3. 路由守卫与鉴权流程

vue-element-plus-admin 项目通过全局路由守卫实现了完整的鉴权流程,核心代码位于 src/permission.ts

3.1 全局前置守卫

项目使用 router.beforeEach 全局前置守卫拦截所有路由导航,实现权限控制:

typescript 复制代码
router.beforeEach(async (to, from, next) => {
  start() // 开始加载进度条
  loadStart() // 开始页面加载动画
  
  const permissionStore = usePermissionStoreWithOut()
  const appStore = useAppStoreWithOut()
  const userStore = useUserStoreWithOut()
  
  // 用户已登录
  if (userStore.getUserInfo) {
    // 如果访问的是登录页,重定向到首页
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 已经添加过动态路由,直接通过
      if (permissionStore.getIsAddRouters) {
        next()
        return
      }

      // 获取用户角色路由
      const roleRouters = userStore.getRoleRouters || []

      // 根据配置选择路由生成模式
      if (appStore.getDynamicRouter) {
        appStore.serverDynamicRouter
          ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
          : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
      } else {
        await permissionStore.generateRoutes('static')
      }

      // 动态添加路由
      permissionStore.getAddRouters.forEach((route) => {
        router.addRoute(route as unknown as RouteRecordRaw)
      })
      
      // 处理重定向
      const redirectPath = from.query.redirect || to.path
      const redirect = decodeURIComponent(redirectPath as string)
      const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
      permissionStore.setIsAddRouters(true)
      next(nextData)
    }
  } 
  // 用户未登录
  else {
    // 白名单中的页面允许未登录访问
    if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) {
      next()
    } else {
      // 其他页面重定向到登录页
      next(`/login?redirect=${to.path}`)
    }
  }
})

鉴权流程的核心逻辑:

  1. 检查用户是否已登录(有无用户信息)
  2. 已登录用户:
    • 首次访问时动态生成并添加路由
    • 后续访问直接通过
  3. 未登录用户:
    • 白名单页面可直接访问
    • 其他页面重定向到登录页

3.2 全局后置守卫

项目还使用了全局后置守卫处理路由切换后的操作:

typescript 复制代码
router.afterEach((to) => {
  useTitle(to?.meta?.title as string) // 设置页面标题
  done() // 结束进度条
  loadDone() // 结束页面加载动画
})

3.3 权限白名单

项目通过常量定义了不需要重定向和不需要重置的路由白名单:

typescript 复制代码
// src/constants/index.ts
/**
 * 不重定向白名单
 */
export const NO_REDIRECT_WHITE_LIST = ['/login']

/**
 * 不重置路由白名单
 */
export const NO_RESET_WHITE_LIST = ['Redirect', 'RedirectWrap', 'Login', 'NoFind', 'Root']

这些白名单确保了特定页面(如登录页)能被正确处理,不会陷入重定向循环。

3.4 用户登录与权限获取

用户登录成功后,会设置token并获取用户信息和权限数据:

typescript 复制代码
// 登录成功后的处理(简化)
const handleActionAfterLogin = async (loginRes: any) => {
  // 1. 设置token
  userStore.setToken(loginRes.token)
  
  // 2. 获取用户信息
  const userInfoRes = await getUserInfoApi()
  userStore.setUserInfo(userInfoRes)
  
  // 3. 获取用户路由权限
  userStore.setRoleRouters(userInfoRes.routers || [])
  
  // 4. 跳转到首页或重定向页
  router.replace(redirect || '/')
}

4. 菜单权限与路由权限联动

vue-element-plus-admin 项目实现了菜单与路由的无缝联动,同时提供了按钮级别的细粒度权限控制。

4.1 菜单生成与路由映射

项目的菜单组件(src/components/Menu)基于路由配置自动生成,实现了菜单与路由的一一对应:

typescript 复制代码
// src/components/Menu/src/Menu.vue
const routers = computed(() =>
  unref(layout) === 'cutMenu' 
    ? permissionStore.getMenuTabRouters 
    : permissionStore.getRouters
)

// 渲染菜单项
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
  return routers
    .filter((v) => !v.meta?.hidden) // 过滤隐藏菜单
    .map((v) => {
      const meta = v.meta ?? {}
      const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
      const fullPath = isUrl(v.path) 
        ? v.path 
        : pathResolve(parentPath, v.path)

      // 处理只有一个子菜单的情况
      if (
        oneShowingChild &&
        (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
        !meta?.alwaysShow
      ) {
        return (
          <ElMenuItem
            index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
          >
            {{
              default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
            }}
          </ElMenuItem>
        )
      } 
      // 处理有多个子菜单的情况
      else {
        return (
          <ElSubMenu
            index={fullPath}
            teleported
            popperClass={unref(menuMode) === 'vertical' ? `${prefixCls}-popper--vertical` : ''}
          >
            {{
              title: () => renderMenuTitle(meta),
              default: () => renderMenuItem(v.children!, fullPath) // 递归渲染子菜单
            }}
          </ElSubMenu>
        )
      }
    })
}

菜单与路由联动的关键点:

  1. 使用相同的数据源(路由配置)
  2. 保持路径一致性,确保菜单点击能正确跳转到对应路由
  3. 根据路由元数据决定菜单的展示形式(图标、标题等)

4.2 面包屑导航

面包屑组件(src/components/Breadcrumb)同样基于路由配置自动生成,提供了页面层次结构导航:

typescript 复制代码
// src/components/Breadcrumb/src/Breadcrumb.vue
const getBreadcrumb = () => {
  const currentPath = currentRoute.value.matched.slice(-1)[0].path
  levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
    return node.path === currentPath
  })
}

const renderBreadcrumb = () => {
  const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
  return breadcrumbList.map((v) => {
    const disabled = !v.redirect || v.redirect === 'noredirect'
    const meta = v.meta
    return (
      <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
        {meta?.icon && breadcrumbIcon.value ? (
          <>
            <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
          </>
        ) : (
          t(v?.meta?.title || '')
        )}
      </ElBreadcrumbItem>
    )
  })
}

面包屑随着路由变化自动更新:

typescript 复制代码
watch(
  () => currentRoute.value,
  (route: RouteLocationNormalizedLoaded) => {
    if (route.path.startsWith('/redirect/')) {
      return
    }
    getBreadcrumb()
  },
  {
    immediate: true
  }
)

4.3 按钮级权限控制

项目通过自定义指令 v-hasPermi 实现了按钮级的细粒度权限控制:

typescript 复制代码
// src/directives/permission/hasPermi.ts
const hasPermission = (value: string): boolean => {
  const permission = (router.currentRoute.value.meta.permission || []) as string[]
  if (!value) {
    throw new Error(t('permission.hasPermission'))
  }
  if (permission.includes(value)) {
    return true
  }
  return false
}

function hasPermi(el: Element, binding: DirectiveBinding) {
  const value = binding.value

  const flag = hasPermission(value)
  if (!flag) {
    el.parentNode?.removeChild(el)
  }
}

const permiDirective: Directive = {
  mounted
}

export const setupPermissionDirective = (app: App<Element>) => {
  app.directive('hasPermi', permiDirective)
}

在组件中使用:

html 复制代码
<!-- 只有拥有 'system:user:add' 权限的用户才能看到这个按钮 -->
<el-button v-hasPermi="'system:user:add'" type="primary">新增用户</el-button>

这个指令检查当前路由的 meta.permission 数组中是否包含指定的权限标识,不包含则从DOM中移除元素,实现了更精细的权限控制。

5. 路由缓存与性能优化

vue-element-plus-admin 项目实现了基于路由的组件缓存机制,避免频繁切换路由导致的组件重复创建和销毁,提升性能和用户体验。

5.1 路由组件缓存实现

项目通过 Vue 内置的 <keep-alive> 和动态缓存列表实现了路由组件缓存:

html 复制代码
<!-- src/layout/components/AppView.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <transition :name="getTransitionName" mode="out-in" appear>
      <keep-alive :include="getCaches">
        <component :is="Component" :key="getKey" />
      </keep-alive>
    </transition>
  </router-view>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useTagsViewStore } from '@/store/modules/tagsView'

const route = useRoute()
const tagsViewStore = useTagsViewStore()

// 获取需要缓存的组件名称列表
const getCaches = computed(() => {
  return tagsViewStore.getCachedViews
})

// 生成组件key,用于强制刷新
const getKey = computed(() => {
  return route.path
})

// 获取过渡动画名称
const getTransitionName = computed(() => {
  return route?.meta?.transitionName || 'fade'
})
</script>

5.2 缓存控制机制

缓存控制由 tagsView store 负责,实现了增加、删除和清空缓存的功能:

typescript 复制代码
// src/store/modules/tagsView.ts
export const useTagsViewStore = defineStore('tagsView', {
  state: (): TagsViewState => ({
    visitedViews: [],
    cachedViews: new Set(), // 使用Set存储缓存视图
    selectedTag: undefined
  }),
  
  getters: {
    getCachedViews(): string[] {
      return Array.from(this.cachedViews)
    }
  },
  
  actions: {
    // 添加视图到缓存
    addCachedView() {
      const cacheMap: Set<string> = new Set()
      for (const v of this.visitedViews) {
        const item = getRawRoute(v)
        const needCache = !item?.meta?.noCache // 根据meta.noCache判断是否需要缓存
        if (!needCache) {
          continue
        }
        const name = item.name as string
        cacheMap.add(name)
      }
      if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())
        return
      this.cachedViews = cacheMap
    },
    
    // 删除缓存
    delCachedView() {
      const route = router.currentRoute.value
      const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
      if (index > -1) {
        this.cachedViews.delete(this.getCachedViews[index])
      }
    }
    
    // 其他缓存管理方法...
  }
})

缓存控制的关键点:

  1. 使用 Set 数据结构存储缓存视图名称,确保唯一性
  2. 根据路由的 meta.noCache 决定是否缓存
  3. 提供增加、删除、清空等操作方法

5.3 缓存优化策略

项目实现了多种缓存优化策略:

  1. 选择性缓存 :通过路由元数据 meta.noCache 控制是否缓存组件

    typescript 复制代码
    const needCache = !item?.meta?.noCache
  2. 缓存与标签页联动:关闭标签页时同时清除相应缓存

    typescript 复制代码
    delView(view: RouteLocationNormalizedLoaded) {
      this.delVisitedView(view)
      this.addCachedView() // 更新缓存列表
    }
  3. 基于组件名称的缓存 :与 Vue 的 <keep-alive> 机制配合

    typescript 复制代码
    const name = item.name as string
    cacheMap.add(name)
  4. 路由切换动画 :通过 transitionName 配置路由切换动画,提升用户体验

    typescript 复制代码
    const getTransitionName = computed(() => {
      return route?.meta?.transitionName || 'fade'
    })

6. 多级路由与面包屑实现

vue-element-plus-admin 项目支持多级路由和面包屑导航,为复杂应用提供了良好的导航结构。

6.1 多级路由的处理

为了更好地支持多级路由,项目实现了路由扁平化处理:

typescript 复制代码
// src/utils/routerHelper.ts
export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {
  const modules: AppRouteRecordRaw[] = cloneDeep(routes)
  for (let index = 0; index < modules.length; index++) {
    const route = modules[index]
    if (!isMultipleRoute(route)) {
      continue
    }
    promoteRouteLevel(route)
  }
  return modules
}

这个函数用于将多级嵌套路由转换为二级路由,适用于某些布局需求:

typescript 复制代码
// 层级是否大于2
const isMultipleRoute = (route: AppRouteRecordRaw) => {
  if (!route || !Reflect.has(route, 'children') || !route.children?.length) {
    return false
  }

  const children = route.children
  let flag = false
  for (let index = 0; index < children.length; index++) {
    const child = children[index]
    if (child.children?.length) {
      flag = true
      break
    }
  }
  return flag
}

// 生成二级路由
const promoteRouteLevel = (route: AppRouteRecordRaw) => {
  let router: Router | null = createRouter({
    routes: [route as RouteRecordRaw],
    history: createWebHashHistory()
  })

  const routes = router.getRoutes()
  addToChildren(routes, route.children || [], route)
  router = null

  route.children = route.children?.map((item) => omit(item, 'children'))
}

这种路由扁平化处理解决了多级路由在某些UI布局下的展示问题。

6.2 面包屑实现

面包屑组件(src/components/Breadcrumb)提供了页面导航层次结构的可视化:

typescript 复制代码
// src/components/Breadcrumb/src/helper.ts
import { RouteRecordNormalized } from 'vue-router'

/**
 * 过滤出可用的路由对象
 * @param routes - 路由列表
 */
export const filterBreadcrumb = (routes: RouteRecordNormalized[]) => {
  const res: RouteRecordNormalized[] = []
  
  for (const route of routes) {
    const tmp = { ...route }
    if (tmp.meta?.breadcrumb !== false) {
      res.push(tmp)
    }
  }
  return res
}
typescript 复制代码
// src/components/Breadcrumb/src/Breadcrumb.vue
const levelList = ref<AppRouteRecordRaw[]>([])

const menuRouters = computed(() => {
  const routers = permissionStore.getRouters
  return filterBreadcrumb(routers)
})

const getBreadcrumb = () => {
  const currentPath = currentRoute.value.matched.slice(-1)[0].path
  levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
    return node.path === currentPath
  })
}

const renderBreadcrumb = () => {
  const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
  return breadcrumbList.map((v) => {
    const disabled = !v.redirect || v.redirect === 'noredirect'
    const meta = v.meta
    return (
      <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
        {meta?.icon && breadcrumbIcon.value ? (
          <>
            <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
          </>
        ) : (
          t(v?.meta?.title || '')
        )}
      </ElBreadcrumbItem>
    )
  })
}

面包屑的关键特性:

  1. 路由同步:面包屑与当前路由同步,展示当前页面的层级关系
  2. 可点击导航:支持点击面包屑项导航到对应页面
  3. 图标支持:可展示与菜单相同的图标,提供一致的视觉体验
  4. 国际化支持 :通过 useI18n hook 支持多语言显示

6.3 路由与布局的整合

项目提供了多种布局模式,路由组件被渲染在不同的布局容器中:

typescript 复制代码
// 布局组件
export const Layout = () => import('@/layout/Layout.vue')

// 父级布局
export const getParentLayout = () => {
  return () =>
    new Promise((resolve) => {
      resolve({
        name: 'ParentLayout'
      })
    })
}

路由定义中使用布局组件:

typescript 复制代码
{
  path: '/dashboard',
  component: Layout, // 使用主布局
  redirect: '/dashboard/analysis',
  children: [
    {
      path: 'analysis',
      component: () => import('@/views/Dashboard/Analysis.vue'),
      // ...
    }
  ]
}

这种设计让系统可以灵活地处理不同层级的路由,同时保持一致的UI布局。

7. 总结

vue-element-plus-admin 项目的路由系统和权限控制展现了高度的灵活性和可扩展性,主要体现在以下方面:

7.1 系统亮点

  1. 三种路由模式:支持静态路由、前端动态路由和后端动态路由,适应不同场景需求
  2. 细粒度权限控制:实现了路由级和按钮级的权限控制
  3. 组件缓存机制 :通过 keep-alive 和动态缓存列表优化性能
  4. 多级路由处理:支持嵌套路由并提供扁平化处理
  5. 完整的导航体系:菜单、面包屑、标签页三位一体的导航系统
  6. TypeScript支持:完整的类型定义提升了代码质量和开发体验

7.2 适用场景

这种路由与权限的设计方案特别适合以下场景:

  1. 企业级管理系统:需要严格的权限控制和复杂的菜单结构
  2. 多角色应用:不同用户角色需要看到不同的功能模块
  3. 需要动态调整菜单的系统:菜单结构可能需要根据业务变化动态调整
  4. 大型前端应用:复杂的路由结构和页面间导航需求

7.3 扩展思考

在实际项目中,可以基于这套方案进行以下扩展:

  1. 权限数据与用户体系集成:将权限控制与实际的用户认证系统对接
  2. 更复杂的权限逻辑:添加基于数据范围、时间、业务状态等的权限控制
  3. 性能优化:针对大型应用进一步优化路由加载和组件缓存策略
  4. 微前端集成:将路由系统扩展为支持微前端架构的导航系统

vue-element-plus-admin 项目的路由与权限控制系统提供了一个很好的基础框架,可以根据具体业务需求进行定制和扩展,满足各种复杂场景的需求。

相关推荐
Thomas游戏开发16 小时前
Unity Root Motion 开发详解
前端框架·unity3d·游戏开发
chancygcx_17 小时前
前端框架Vue3(四)——组件通信及其他API
vue.js·前端框架
德育处主任18 小时前
p5.js 3D模型(model)入门指南
前端·前端框架·canvas
前端缘梦21 小时前
前端工程化包管理器:从npm基础到nvm多版本管理实战
前端·前端工程化
懋学的前端攻城狮1 天前
从 UI = f(state) 到 Fiber 架构:解构 React 设计哲学的“第一性原理”
前端·react.js·前端框架
全球网站建设2 天前
从结构到交互:HTML5进阶开发全解析——语义化标签、Canvas绘图与表单设计实战
javascript·前端框架·php·reactjs·css3·html5
页面魔术2 天前
🔥来听听尤雨溪是怎么亲述无虚拟dom的吧
前端·vue.js·前端框架
WildBlue2 天前
从 0 到 1 上手 React 中的 mitt,前端小白也能秒懂!🤓
前端·react.js·前端框架
懋学的前端攻城狮2 天前
深入浅出Vue源码 - 剖析diff算法的核心实现
前端·vue.js·前端框架