Skip to content

路由文档 - 路由配置和权限控制

概述

本文档详细介绍了 VJSP Vue3 框架的路由配置机制和权限控制系统。框架采用基于 Vue Router 4 的路由架构,结合动态路由生成和细粒度权限控制,为企业级应用提供完整的路由解决方案。

路由架构

核心特性

  • 动态路由生成:支持前端控制和后端控制两种路由生成模式
  • 权限集成:路由与权限系统深度集成,实现菜单级和按钮级权限控制
  • 路由缓存:智能路由缓存机制,提升页面切换性能
  • 多级路由支持:支持多级嵌套路由,自动扁平化处理
  • 类型安全:完整的 TypeScript 类型定义支持

技术栈

  • Vue Router 4:核心路由库
  • Pinia 状态管理:路由状态和权限状态管理
  • 动态导入:路由组件懒加载,优化首屏加载性能
  • 权限验证:基于角色的访问控制(RBAC)

路由配置机制

路由模式

框架支持两种路由配置模式:

  1. 前端控制模式:路由配置完全由前端定义,权限系统负责过滤可访问路由
  2. 后端控制模式:路由结构由后端返回,前端根据后端数据动态生成路由

路由结构

常量路由

常量路由是应用启动时必须加载的基础路由,包括:

  • 登录页面
  • 404 错误页面
  • 重定向路由
  • 系统基础页面(如仪表盘)

动态路由

动态路由根据用户权限动态生成,包括:

  • 业务功能模块路由
  • 系统管理路由
  • 个人中心路由

路由懒加载

框架采用动态导入实现路由组件的懒加载,通过 createSafeDynamicImport 函数确保组件加载的安全性:

  • 组件按需加载,减少初始包大小
  • 错误边界处理,防止加载失败影响应用
  • 支持组件预加载,提升用户体验

权限控制机制

权限层级

框架实现三级权限控制:

  1. 路由级权限:控制用户能否访问特定页面
  2. 菜单级权限:控制侧边栏菜单的显示
  3. 按钮级权限:控制页面内操作按钮的显示

权限验证流程

1. 路由守卫验证

路由守卫在每次路由跳转时执行权限验证:

  • 检查用户登录状态
  • 验证用户权限信息
  • 生成可访问的动态路由
  • 添加路由到路由器实例

2. 权限检查函数

框架提供多种权限检查函数:

  • hasPermission:检查是否拥有特定权限
  • hasAnyPermission:检查是否拥有任意指定权限
  • hasAllPermissions:检查是否拥有所有指定权限

3. 权限指令

通过 v-permission 指令实现按钮级权限控制,支持多种使用方式和严格权限模式:

基础用法
vue
<!-- 单个权限检查 -->
<el-button v-permission="'system:user:add'">新增用户</el-button>
<el-button v-permission="'system:user:edit'">编辑用户</el-button>

<!-- 多个权限检查(满足任意一个权限即可) -->
<el-button v-permission="['system:user:add', 'system:user:edit']">
  新增或编辑用户
</el-button>
严格权限模式

严格权限模式通过修饰符实现,当用户没有权限时会完全移除DOM元素:

vue
<!-- 严格模式:无权限时移除元素 -->
<el-button v-permission.strict="'system:user:delete'">删除用户</el-button>

<!-- 严格模式结合多个权限 -->
<el-button v-permission.strict="['system:user:export', 'system:user:import']">
  数据导入导出
</el-button>
权限指令特性
  • 默认行为:无权限时禁用元素(透明度降低、不可点击)
  • 严格模式:使用 .strict 修饰符,无权限时完全移除元素
  • 动态更新:权限变化时自动更新元素状态
  • 多种权限检查:支持单个权限或权限数组(满足任意权限即可)

动态路由生成

前端控制模式

在前端控制模式下,路由生成流程如下:

  1. 获取用户权限列表
  2. 过滤预定义的路由配置
  3. 生成可访问的路由树
  4. 添加到路由器实例

后端控制模式

在后端控制模式下,路由生成流程如下:

  1. 从后端获取菜单数据
  2. 转换菜单数据为路由配置
  3. 验证路由组件路径有效性
  4. 生成并添加动态路由

路由工具函数

路由生成工具

框架提供 generateRoutesByFrontEndgenerateRoutesByServer 函数分别处理前端和后端控制模式的路由生成。

路由处理工具

  • pathResolve:路径解析和规范化
  • flatMultiLevelRoutes:多级路由扁平化处理
  • isValidRoute:路由配置验证

权限检查工具

  • filterAccessibleRoutes:过滤可访问路由
  • findRouteByPath:根据路径查找路由
  • findRouteByName:根据名称查找路由

业务模块开发指南(Product 模块)

1. 创建路由配置

src/router/modules/ 目录下创建 product.ts 路由配置文件:

typescript
import type { AppRouteRecordRaw } from '@/router/types'

const ProductRoute: AppRouteRecordRaw = {
  path: '/product',
  name: 'Product',
  component: () => import('@/layout/index.vue'),
  meta: {
    title: '产品管理',
    icon: 'product',
    permission: ['product:view'],
  },
  children: [
    {
      path: 'list',
      name: 'ProductList',
      component: () => import('@/views/modules/product/list.vue'),
      meta: {
        title: '产品列表',
        permission: ['product:list'],
      },
    },
    {
      path: 'add',
      name: 'ProductAdd',
      component: () => import('@/views/modules/product/add.vue'),
      meta: {
        title: '新增产品',
        permission: ['product:add'],
        hidden: true,
      },
    },
    {
      path: 'edit/:id',
      name: 'ProductEdit',
      component: () => import('@/views/modules/product/edit.vue'),
      meta: {
        title: '编辑产品',
        permission: ['product:edit'],
        hidden: true,
      },
    },
  ],
}

export default ProductRoute

2. 注册路由模块

src/router/index.ts 中导入并注册产品路由模块:

typescript
import ProductRoute from './modules/product'

// 在动态路由配置中添加
const asyncRoutes: AppRouteRecordRaw[] = [
  // ... 其他路由
  ProductRoute,
]

3. 创建视图组件

创建对应的 Vue 组件文件:

src/views/modules/product/list.vue

vue
<template>
  <div class="product-list">
    <SearchForm :fields="searchFields" @search="handleSearch" />

    <DataTable
      :columns="tableColumns"
      :data="productList"
      :loading="loading"
      @refresh="loadProductList"
    >
      <template #action="{ row }">
        <el-button v-permission="'product:edit'" @click="handleEdit(row)"> 编辑 </el-button>
        <el-button v-permission="'product:delete'" @click="handleDelete(row)"> 删除 </el-button>
      </template>
    </DataTable>

    <el-button v-permission="'product:add'" type="primary" @click="handleAdd"> 新增产品 </el-button>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useProductApi } from '@/api/modules/product'

const router = useRouter()
const productList = ref([])
const loading = ref(false)

const searchFields = [
  { label: '产品名称', prop: 'productName', type: 'input' },
  { label: '产品编码', prop: 'productCode', type: 'input' },
]

const tableColumns = [
  { label: '产品名称', prop: 'productName' },
  { label: '产品编码', prop: 'productCode' },
  { label: '价格', prop: 'price' },
  { label: '状态', prop: 'status' },
]

const loadProductList = async (params = {}) => {
  loading.value = true
  try {
    const response = await useProductApi().getList(params)
    productList.value = response.data.list
  } finally {
    loading.value = false
  }
}

const handleAdd = () => {
  router.push({ name: 'ProductAdd' })
}

const handleEdit = row => {
  router.push({ name: 'ProductEdit', params: { id: row.id } })
}

const handleDelete = async row => {
  // 删除确认和操作
}

onMounted(() => {
  loadProductList()
})
</script>

4. 权限配置

在权限管理系统中配置产品模块的权限标识:

  • product:view:查看产品模块
  • product:list:查看产品列表
  • product:add:新增产品
  • product:edit:编辑产品
  • product:delete:删除产品

路由缓存机制

路由状态管理

框架使用 Pinia 管理路由状态,包括:

  • 当前激活的路由
  • 标签页路由列表
  • 侧边栏菜单状态
  • 路由缓存标识

组件缓存

通过 Vue Router 的 <keep-alive> 和组件 name 属性实现路由组件缓存:

  • 智能缓存管理,避免内存泄漏
  • 支持最大缓存数量限制
  • 手动清除缓存功能

路由持久化

路由状态自动持久化到 localStorage,确保:

  • 页面刷新后保持路由状态
  • 标签页状态恢复
  • 侧边栏展开状态记忆

错误处理

路由错误处理

框架实现完整的路由错误处理机制:

  • 404 页面处理
  • 权限不足页面
  • 路由加载失败处理
  • 网络错误重试机制

降级方案

当动态路由加载失败时,框架提供降级方案:

  • 使用静态路由作为后备
  • 显示友好的错误提示
  • 提供重试机制

最佳实践

路由命名规范

  • 使用驼峰命名法命名路由
  • 保持路由名称与组件名称一致
  • 使用有意义的路径结构

权限配置建议

  • 权限标识采用模块化命名
  • 保持权限粒度适中
  • 定期清理未使用的权限

性能优化

  • 合理使用路由懒加载
  • 避免过度嵌套路由
  • 定期检查路由配置

常见问题

Q: 如何添加新的业务模块路由?

A: 在 src/router/modules/ 目录下创建路由配置文件,并在主路由文件中导入注册。

Q: 如何控制按钮级权限?

A: 使用 v-permission 指令,传入对应的权限标识。

Q: 路由缓存不生效怎么办?

A: 确保组件设置了正确的 name 属性,并检查路由配置中的 meta.keepAlive 设置。

Q: 如何实现动态菜单?

A: 使用后端控制模式,从后端获取菜单数据并转换为路由配置。