Skip to content

前端菜单路由

1. 路由配置概述

梵医云前端项目使用 Vue Router 进行路由管理,路由配置位于 src/router/modules/remaining.ts 文件中。

1.1 路由基本结构

typescript
const remainingRouter: AppRouteRecordRaw[] = [
  {
    path: '/user',
    component: Layout,
    name: 'UserInfo',
    meta: {
      hidden: true
    },
    children: [
      {
        path: 'profile',
        component: () => import('@/views/Profile/Index.vue'),
        name: 'Profile',
        meta: {
          title: '个人中心',
          icon: 'ep:user'
        }
      }
    ]
  }
]

1.2 路由类型

  • 静态路由:在 remaining.ts 配置文件中直接定义的路由
  • 动态路由:从后端获取的菜单权限路由,通过 generateRoute 函数生成
  • 公共路由:不需要权限即可访问的路由(如登录页、404页)

1.3 路由类型定义

typescript
// 路由记录类型
type AppRouteRecordRaw = RouteRecordRaw & {
  meta?: RouteMeta
}

// 自定义路由记录类型(后端返回)
type AppCustomRouteRecordRaw = {
  path: string
  name: string
  component?: string
  componentName?: string
  redirect?: string
  icon?: string
  visible?: boolean
  keepAlive?: boolean
  alwaysShow?: boolean
  dot?: boolean
  parentId?: number
  children?: AppCustomRouteRecordRaw[]
}

2. 路由 Meta 属性

2.1 Meta 属性说明

属性类型默认值说明
hiddenBooleanfalse当设置为 true 时,该路由不会在侧边栏显示
alwaysShowBooleanfalse当子路由大于1个时,自动变成嵌套模式。只有一个时,会将子路由当做根路由显示。设置 true 可忽略此规则,始终显示根路由
titleString-设置该路由在侧边栏和面包屑中展示的名字
iconString-设置该路由的图标,支持 Element Plus 图标和 Iconify 图标
noCacheBooleanfalse如果设置为 true,则不会被 <keep-alive> 缓存
breadcrumbBooleantrue如果设置为 false,则不会在面包屑中显示
affixBooleanfalse如果设置为 true,则会一直固定在 tag 标签中
noTagsViewBooleanfalse如果设置为 true,则不会出现在 tag 标签中
activeMenuString-显示高亮的路由路径
followAuthString-跟随哪个路由进行权限过滤
canToBooleanfalse设置为 true 即使 hidden 为 true,也依然可以进行路由跳转
dotBooleanfalse是否显示红点标记
rankNumber-路由排序等级,数值越小越靠前

2.2 Meta 属性示例

typescript
{
  path: '/user',
  component: Layout,
  name: 'UserInfo',
  meta: {
    hidden: true,              // 不在侧边栏显示
    title: '个人中心',          // 侧边栏标题
    icon: 'ep:user',            // 图标
    noCache: true,              // 不缓存
    breadcrumb: false,          // 不在面包屑显示
    affix: true,               // 固定在 tag 标签
    noTagsView: false,          // 显示在 tag 标签
    activeMenu: '/dashboard',    // 高亮路由
    canTo: true,               // 允许跳转
    dot: true,                 // 显示红点
    rank: 1                    // 排序等级
  }
}

3. 路由配置示例

3.1 首页路由

typescript
{
  path: '/',
  component: Layout,
  redirect: '/index',
  name: 'Home',
  meta: {},
  children: [
    {
      path: 'index',
      component: () => import('@/views/Home/Index.vue'),
      name: 'Index',
      meta: {
        title: t('router.home'),
        icon: 'ep:home-filled',
        noCache: false,
        affix: true
      }
    }
  ]
}

3.2 用户中心路由

typescript
{
  path: '/user',
  component: Layout,
  name: 'UserInfo',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'profile',
      component: () => import('@/views/Profile/Index.vue'),
      name: 'Profile',
      meta: {
        canTo: true,
        hidden: true,
        noTagsView: false,
        icon: 'ep:user',
        title: t('common.profile')
      }
    },
    {
      path: 'notify-message',
      component: () => import('@/views/system/notify/my/index.vue'),
      name: 'MyNotifyMessage',
      meta: {
        canTo: true,
        hidden: true,
        noTagsView: false,
        icon: 'ep:message',
        title: '我的站内信'
      }
    }
  ]
}

3.3 字典数据路由

typescript
{
  path: '/dict',
  component: Layout,
  name: 'dict',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'type/data/:dictType',
      component: () => import('@/views/system/dict/data/index.vue'),
      name: 'SystemDictData',
      meta: {
        title: '字典数据',
        noCache: true,
        hidden: true,
        canTo: true,
        icon: '',
        activeMenu: '/system/dict'
      }
    }
  ]
}

3.4 代码生成路由

typescript
{
  path: '/codegen',
  component: Layout,
  name: 'CodegenEdit',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'edit',
      component: () => import('@/views/infra/codegen/EditTable.vue'),
      name: 'InfraCodegenEditTable',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '修改生成配置',
        activeMenu: 'infra/codegen/index'
      }
    }
  ]
}

3.5 定时任务路由

typescript
{
  path: '/job',
  component: Layout,
  name: 'JobL',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'job-log',
      component: () => import('@/views/infra/job/logger/index.vue'),
      name: 'InfraJobLog',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '调度日志',
        activeMenu: 'infra/job/index'
      }
    }
  ]
}

3.6 工作流路由

typescript
{
  path: '/bpm',
  component: Layout,
  name: 'bpm',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'manager/form/edit',
      component: () => import('@/views/bpm/form/editor/index.vue'),
      name: 'BpmFormEditor',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '设计流程表单',
        activeMenu: '/bpm/manager/form'
      }
    },
    {
      path: 'manager/model/edit',
      component: () => import('@/views/bpm/model/editor/index.vue'),
      name: 'BpmModelEditor',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '设计流程',
        activeMenu: '/bpm/manager/model'
      }
    },
    {
      path: 'manager/simple/workflow/model/edit',
      component: () => import('@/views/bpm/simpleWorkflow/index.vue'),
      name: 'SimpleWorkflowDesignEditor',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '仿钉钉设计流程',
        activeMenu: '/bpm/manager/model'
      }
    },
    {
      path: 'manager/definition',
      component: () => import('@/views/bpm/definition/index.vue'),
      name: 'BpmProcessDefinition',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '流程定义',
        activeMenu: '/bpm/manager/model'
      }
    },
    {
      path: 'process-instance/detail',
      component: () => import('@/views/bpm/processInstance/detail/index.vue'),
      name: 'BpmProcessInstanceDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '流程详情',
        activeMenu: '/bpm/task/my'
      }
    },
    {
      path: 'oa/leave/create',
      component: () => import('@/views/bpm/oa/leave/create.vue'),
      name: 'OALeaveCreate',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '发起 OA 请假',
        activeMenu: '/bpm/oa/leave'
      }
    },
    {
      path: 'oa/leave/detail',
      component: () => import('@/views/bpm/oa/leave/detail.vue'),
      name: 'OALeaveDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        title: '查看 OA 请假',
        activeMenu: '/bpm/oa/leave'
      }
    }
  ]
}

3.7 门店管理路由

typescript
{
  path: '/store',
  component: Layout,
  name: 'StoreCenter',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'list/add',
      component: () => import('@/views/store/list/form/index.vue'),
      name: 'StoreAdd',
      meta: {
        noCache: false,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '门店添加',
        activeMenu: '/store/list'
      }
    },
    {
      path: 'list/edit/:id(\\d+)',
      component: () => import('@/views/store/list/form/index.vue'),
      name: 'StoreEdit',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '门店编辑',
        activeMenu: '/store/list'
      }
    },
    {
      path: 'list/detail/:id(\\d+)',
      component: () => import('@/views/store/list/form/index.vue'),
      name: 'StoreDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:view',
        title: '门店详情',
        activeMenu: '/store/list'
      }
    }
  ]
}

3.8 医院管理路由

typescript
{
  path: '/hospital',
  component: Layout,
  name: 'HospitalCenter',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'info/add',
      component: () => import('@/views/hospital/info/form/index.vue'),
      name: 'HospitalInfoAdd',
      meta: {
        noCache: false,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '医院添加',
        activeMenu: '/hospital/info'
      }
    },
    {
      path: 'info/edit/:id(\\d+)',
      component: () => import('@/views/hospital/info/form/index.vue'),
      name: 'HospitalInfoEdit',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '医院编辑',
        activeMenu: '/hospital/info'
      }
    },
    {
      path: 'info/detail/:id(\\d+)',
      component: () => import('@/views/hospital/info/form/index.vue'),
      name: 'HospitalInfoDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:view',
        title: '医院详情',
        activeMenu: '/hospital/info'
      }
    },
    {
      path: 'goods/add',
      component: () => import('@/views/hospital/goods/form/index.vue'),
      name: 'HospitalGoodsAdd',
      meta: {
        noCache: false,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品添加',
        activeMenu: '/hospital/goods'
      }
    },
    {
      path: 'goods/edit/:id(\\d+)',
      component: () => import('@/views/hospital/goods/form/index.vue'),
      name: 'HospitalGoodsEdit',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品编辑',
        activeMenu: '/hospital/goods'
      }
    },
    {
      path: 'goods/detail/:id(\\d+)',
      component: () => import('@/views/hospital/goods/form/index.vue'),
      name: 'HospitalGoodsDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:view',
        title: '商品详情',
        activeMenu: '/hospital/goods'
      }
    }
  ]
}

3.9 商品管理路由

typescript
{
  path: '/mall/product',
  component: Layout,
  name: 'ProductCenter',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'spu/add',
      component: () => import('@/views/mall/product/spu/form/index.vue'),
      name: 'ProductSpuAdd',
      meta: {
        noCache: false,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品添加',
        activeMenu: '/mall/product/spu'
      }
    },
    {
      path: 'spu/edit/:id(\\d+)',
      component: () => import('@/views/mall/product/spu/form/index.vue'),
      name: 'ProductSpuEdit',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品编辑',
        activeMenu: '/mall/product/spu'
      }
    },
    {
      path: 'spu/edit/:id(\\d+)',
      component: () => import('@/views/mall/product/spu/form/index.vue'),
      name: 'ProductAuditEdit',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品编辑',
        activeMenu: '/mall/product/audit'
      }
    },
    {
      path: 'spu/detail/:id(\\d+)',
      component: () => import('@/views/mall/product/spu/form/index.vue'),
      name: 'ProductAuditDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:edit',
        title: '商品详情',
        activeMenu: '/mall/product/audit'
      }
    },
    {
      path: 'spu/detail/:id(\\d+)',
      component: () => import('@/views/mall/product/spu/form/index.vue'),
      name: 'ProductSpuDetail',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:view',
        title: '商品详情',
        activeMenu: '/mall/product/spu'
      }
    },
    {
      path: 'property/value/:propertyId(\\d+)',
      component: () => import('@/views/mall/product/property/value/index.vue'),
      name: 'ProductPropertyValue',
      meta: {
        noCache: true,
        hidden: true,
        canTo: true,
        icon: 'ep:view',
        title: '商品属性值',
        activeMenu: '/product/property'
      }
    }
  ]
}

3.10 订单管理路由

typescript
{
  path: '/mall/trade',
  component: Layout,
  name: 'TradeCenter',
  meta: {
    hidden: true
  },
  children: [
    {
      path: 'order/detail/:id(\\d+)',
      component: () => import('@/views/mall/trade/order/detail/index.vue'),
      name: 'TradeOrderDetail',
      meta: { 
        title: '订单详情', 
        icon: 'ep:view', 
        activeMenu: '/mall/trade/order' 
      }
    },
    {
      path: 'order-prescription/detail/:id(\\d+)',
      component: () => import('@/views/mall/trade/order-prescription/detail/index.vue'),
      name: 'TradeOrderPrescriptionDetail',
      meta: {
        title: '处方药订单详情',
        icon: 'ep:view',
        activeMenu: '/mall/trade/order-prescription'
      }
    },
    {
      path: 'after-sale/detail/:id(\\d+)',
      component: () => import('@/views/mall/trade/afterSale/detail/index.vue'),
      name: 'TradeAfterSaleDetail',
      meta: { 
        title: '退款详情', 
        icon: 'ep:view', 
        activeMenu: '/mall/trade/after-sale' 
      }
    },
    {
      path: 'delivery/logistics/detail/:id(\\d+)',
      component: () => import('@/views/mall/trade/delivery/logistics/detail/index.vue'),
      name: 'TradeDeliveryLogisticsDetail',
      meta: { 
        title: '订单详情', 
        icon: 'ep:view', 
        activeMenu: '/mall/trade/delivery/logistics' 
      }
    }
  ]
}

3.11 CRM 路由

typescript
{
  path: '/crm',
  component: Layout,
  name: 'CrmCenter',
  meta: { 
    hidden: true 
  },
  children: [
    {
      path: 'clue/detail/:id',
      name: 'CrmClueDetail',
      meta: {
        title: '线索详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/clue'
      },
      component: () => import('@/views/crm/clue/detail/index.vue')
    },
    {
      path: 'customer/detail/:id',
      name: 'CrmCustomerDetail',
      meta: {
        title: '客户详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/customer'
      },
      component: () => import('@/views/crm/customer/detail/index.vue')
    },
    {
      path: 'business/detail/:id',
      name: 'CrmBusinessDetail',
      meta: {
        title: '商机详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/business'
      },
      component: () => import('@/views/crm/business/detail/index.vue')
    },
    {
      path: 'contract/detail/:id',
      name: 'CrmContractDetail',
      meta: {
        title: '合同详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/contract'
      },
      component: () => import('@/views/crm/contract/detail/index.vue')
    },
    {
      path: 'receivable-plan/detail/:id',
      name: 'CrmReceivablePlanDetail',
      meta: {
        title: '回款计划详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/receivable-plan'
      },
      component: () => import('@/views/crm/receivable/plan/detail/index.vue')
    },
    {
      path: 'receivable/detail/:id',
      name: 'CrmReceivableDetail',
      meta: {
        title: '回款详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/receivable'
      },
      component: () => import('@/views/crm/receivable/detail/index.vue')
    },
    {
      path: 'contact/detail/:id',
      name: 'CrmContactDetail',
      meta: {
        title: '联系人详情',
        noCache: true,
        hidden: true,
        activeMenu: '/crm/contact'
      },
      component: () => import('@/views/crm/contact/detail/index.vue')
    }
  ]
}

3.12 会员中心路由

typescript
{
  path: '/member',
  component: Layout,
  name: 'MemberCenter',
  meta: { 
    hidden: true 
  },
  children: [
    {
      path: 'user/detail/:id',
      name: 'MemberUserDetail',
      meta: {
        title: '会员详情',
        noCache: true,
        hidden: true
      },
      component: () => import('@/views/member/user/detail/index.vue')
    }
  ]
}

3.13 支付路由

typescript
{
  path: '/pay',
  component: Layout,
  name: 'pay',
  meta: { 
    hidden: true 
  },
  children: [
    {
      path: 'cashier',
      name: 'PayCashier',
      meta: {
        title: '收银台',
        noCache: true,
        hidden: true
      },
      component: () => import('@/views/pay/cashier/index.vue')
    }
  ]
}

3.14 DIY装修路由

typescript
{
  path: '/diy',
  name: 'DiyCenter',
  meta: { 
    hidden: true 
  },
  component: Layout,
  children: [
    {
      path: 'template/decorate/:id',
      name: 'DiyTemplateDecorate',
      meta: {
        title: '模板装修',
        noCache: true,
        hidden: true,
        activeMenu: '/mall/promotion/diy/template'
      },
      component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
    },
    {
      path: 'page/decorate/:id',
      name: 'DiyPageDecorate',
      meta: {
        title: '页面装修',
        noCache: true,
        hidden: true,
        activeMenu: '/mall/promotion/diy/page'
      },
      component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
    }
  ]
}

4. 动态路由

4.1 动态路由加载

动态路由从后端获取,根据用户权限动态添加到路由中:

typescript
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'

const userStore = useUserStore()
const permissionStore = usePermissionStore()

// 获取用户权限路由
const routes = await userStore.getPermissionRoutes()

// 生成路由
const generatedRoutes = generateRoute(routes)

// 添加到路由
generatedRoutes.forEach(route => {
  router.addRoute(route)
})

4.2 动态路由结构

后端返回的路由结构:

typescript
{
  "path": "/system/user",
  "component": "Layout",
  "redirect": "/system/user/page",
  "name": "SystemUser",
  "meta": {
    "title": "用户管理",
    "icon": "ep:user",
    "type": 0
  },
  "children": [
    {
      "path": "page",
      "name": "SystemUserPage",
      "component": "system/user/index",
      "meta": {
        "title": "用户列表",
        "icon": "ep:list",
        "type": 1
      }
    }
  ]
}

4.3 路由生成函数

项目使用 generateRoute 函数将后端返回的路由转换为前端可用的路由:

typescript
// src/utils/routerHelper.ts
export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
  const res: AppRouteRecordRaw[] = []
  const modulesRoutesKeys = Object.keys(modules)
  
  for (const route of routes) {
    // 1. 生成 meta 菜单元数据
    const meta = {
      title: route.name,
      icon: route.icon,
      hidden: !route.visible,
      noCache: !route.keepAlive,
      dot: route.dot,
      alwaysShow:
        route.children &&
        route.children.length === 1 &&
        (route.alwaysShow !== undefined ? route.alwaysShow : true)
    } as any

    // 2. 生成 data(AppRouteRecordRaw)
    let data: AppRouteRecordRaw = {
      path: route.path.indexOf('?') > -1 ? route.path.split('?')[0] : route.path,
      name:
        route.componentName && route.componentName.length > 0
          ? route.componentName
          : toCamelCase(route.path, true),
      redirect: route.redirect,
      meta: meta
    }

    // 处理组件加载
    // ...

    res.push(data as AppRouteRecordRaw)
  }
  
  return res
}

5. 路由权限控制

5.1 路由守卫

使用路由守卫进行权限控制:

typescript
// src/router/guards/permission.ts
import router from '@/router'
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()
  const token = userStore.token

  if (token) {
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      if (!permissionStore.isRoutesLoaded) {
        await permissionStore.generateRoutes()
        next({ ...to, replace: true })
      } else {
        next()
      }
    }
  } else {
    if (to.path === '/login') {
      next()
    } else {
      next({ path: '/login' })
    }
  }
})

5.2 权限指令

使用权限指令控制元素显示:

vue
<template>
  <el-button v-hasPermi="['system:user:add']">新增</el-button>
  <el-button v-hasPermi="['system:user:edit']">编辑</el-button>
  <el-button v-hasPermi="['system:user:delete']">删除</el-button>
</template>

6. 路由跳转

6.1 编程式导航

typescript
import { useRouter } from 'vue-router'

const router = useRouter()

// 跳转到指定路由
router.push('/user/profile')

// 带参数跳转
router.push({
  path: '/user/detail',
  query: { id: 123 }
})

// 命名路由跳转
router.push({
  name: 'UserProfile',
  params: { id: 123 }
})

// 替换当前路由
router.replace('/user/profile')

// 返回上一页
router.back()

// 前进一页
router.forward()

6.2 声明式导航

vue
<template>
  <!-- 路径跳转 -->
  <router-link to="/user/profile">个人中心</router-link>

  <!-- 命名路由跳转 -->
  <router-link :to="{ name: 'UserProfile' }">个人中心</router-link>

  <!-- 带参数跳转 -->
  <router-link :to="{ path: '/user/detail', query: { id: 123 } }">
    用户详情
  </router-link>

  <!-- 动态路由跳转 -->
  <router-link :to="`/user/detail/${userId}`">用户详情</router-link>
</template>

7. 路由参数

7.1 路径参数

typescript
// 路由定义
{
  path: 'detail/:id(\\d+)',
  component: () => import('@/views/user/detail/index.vue'),
  name: 'UserDetail'
}

// 获取参数
import { useRoute } from 'vue-router'

const route = useRoute()
const userId = route.params.id

7.2 查询参数

typescript
// 路由定义
{
  path: 'list',
  component: () => import('@/views/user/list/index.vue'),
  name: 'UserList'
}

// 获取参数
import { useRoute } from 'vue-router'

const route = useRoute()
const page = route.query.page
const keyword = route.query.keyword

7.3 Meta参数

typescript
// 获取meta参数
import { useRoute } from 'vue-router'

const route = useRoute()
const title = route.meta.title
const icon = route.meta.icon
const query = route.meta.query

8. 路由缓存

8.1 keep-alive 缓存

使用 <keep-alive> 缓存组件:

vue
<template>
  <router-view v-slot="{ Component }">
    <keep-alive :include="cachedViews">
      <component :is="Component" :key="$route.fullPath" />
    </keep-alive>
  </router-view>
</template>

<script setup lang="ts">
import { useTagsViewStore } from '@/store/modules/tagsView'

const tagsViewStore = useTagsViewStore()
const cachedViews = computed(() => tagsViewStore.cachedViews)
</script>

8.2 控制缓存

通过 meta.noCache 控制是否缓存:

typescript
{
  path: 'list',
  component: () => import('@/views/user/list/index.vue'),
  name: 'UserList',
  meta: {
    noCache: true  // 不缓存
  }
}

8.3 路由排序

使用 meta.rank 控制路由排序:

typescript
{
  path: '/dashboard',
  component: Layout,
  name: 'Dashboard',
  meta: {
    rank: 1  // 排序等级,数值越小越靠前
  }
}

9. 路由嵌套

9.1 单层嵌套

typescript
{
  path: '/system',
  component: Layout,
  children: [
    {
      path: 'user',
      component: () => import('@/views/system/user/index.vue'),
      name: 'SystemUser',
      meta: {
        title: '用户管理',
        icon: 'ep:user'
      }
    }
  ]
}

9.2 多层嵌套

typescript
{
  path: '/system',
  component: Layout,
  children: [
    {
      path: 'user',
      component: () => import('@/views/system/user/index.vue'),
      name: 'SystemUser',
      meta: {
        title: '用户管理',
        icon: 'ep:user'
      },
      children: [
        {
          path: 'list',
          component: () => import('@/views/system/user/list/index.vue'),
          name: 'SystemUserList',
          meta: {
            title: '用户列表'
          }
        }
      ]
    }
  ]
}

9.3 路由降级

使用 flatMultiLevelRoutes 函数将多层路由降级为二级路由:

typescript
import { flatMultiLevelRoutes } from '@/utils/routerHelper'

// 将多层路由降级为二级路由
const routes = flatMultiLevelRoutes(originalRoutes)

10. 路由重定向

10.1 基础重定向

typescript
{
  path: '/',
  redirect: '/index'
}

10.2 命名路由重定向

typescript
{
  path: '/',
  redirect: { name: 'Index' }
}

10.3 动态重定向

typescript
{
  path: '/user/:id',
  redirect: to => {
    return { path: `/user/detail/${to.params.id}` }
  }
}

11. 路由别名

typescript
{
  path: '/user/profile',
  component: () => import('@/views/user/profile/index.vue'),
  name: 'UserProfile',
  alias: '/profile'  // 别名
}

12. 路由过渡动画

12.1 基础过渡

vue
<template>
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

12.2 滑动过渡

vue
<template>
  <router-view v-slot="{ Component }">
    <transition name="slide" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<style scoped>
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
}

.slide-leave-to {
  transform: translateX(-100%);
}
</style>

13. 路由错误处理

13.1 404 页面

typescript
{
  path: '/404',
  component: () => import('@/views/Error/404.vue'),
  name: 'NoFound',
  meta: {
    hidden: true,
    title: '404',
    noTagsView: true
  }
}

13.2 500 页面

typescript
{
  path: '/500',
  component: () => import('@/views/Error/500.vue'),
  name: 'Error',
  meta: {
    hidden: true,
    title: '500',
    noTagsView: true
  }
}

13.3 403 页面

typescript
{
  path: '/403',
  component: () => import('@/views/Error/403.vue'),
  name: 'NoAccess',
  meta: {
    hidden: true,
    title: '403',
    noTagsView: true
  }
}

14. 路由工具函数

14.1 Layout组件

typescript
import { Layout } from '@/utils/routerHelper'

// 主布局组件
const layout = Layout()

14.2 父级Layout

typescript
import { getParentLayout } from '@/utils/routerHelper'

// 父级布局组件
const parentLayout = getParentLayout()

14.3 路由排序

typescript
import { ascending } from '@/utils/routerHelper'

// 按照路由中meta下的rank等级升序来排序路由
const sortedRoutes = ascending(routes)

14.4 路由降级

typescript
import { flatMultiLevelRoutes } from '@/utils/routerHelper'

// 将多层路由降级为二级路由
const flatRoutes = flatMultiLevelRoutes(routes)

14.5 路径解析

typescript
import { pathResolve } from '@/utils/routerHelper'

// 路径解析
const fullPath = pathResolve(parentPath, childPath)

15. 路由最佳实践

15.1 路由命名规范

  • 使用 PascalCase 命名路由
  • 路由名称应该具有描述性
  • 避免使用特殊字符
typescript
// ✅ 好的命名
{
  name: 'UserProfile',
  name: 'UserList',
  name: 'UserDetail'
}

// ❌ 不好的命名
{
  name: 'userProfile',
  name: 'user_list',
  name: 'user-detail'
}

15.2 路由组织原则

  • 按功能模块组织路由
  • 使用嵌套路由表示层级关系
  • 合理使用路由元信息
typescript
// ✅ 好的组织
{
  path: '/system',
  children: [
    { path: 'user', ... },
    { path: 'role', ... },
    { path: 'menu', ... }
  ]
}

// ❌ 不好的组织
{
  path: '/user',
  children: [ ... ]
},
{
  path: '/role',
  children: [ ... ]
},
{
  path: '/menu',
  children: [ ... ]
}

15.3 性能优化

  • 使用路由懒加载
  • 合理使用路由缓存
  • 避免深层嵌套
typescript
// ✅ 懒加载
{
  component: () => import('@/views/user/index.vue')
}

// ❌ 不懒加载
{
  component: () => import('@/views/user/index.vue')
}

15.4 组件注册

使用 registerComponent 函数动态注册组件:

typescript
import { registerComponent } from '@/utils/routerHelper'

// 注册一个异步组件
const component = registerComponent('/bpm/oa/leave/detail')

16. 常见问题

16.1 如何处理路由参数?

typescript
// 获取路径参数
const route = useRoute()
const id = route.params.id

// 获取查询参数
const page = route.query.page
const keyword = route.query.keyword

16.2 如何控制路由缓存?

typescript
// 在路由meta中设置noCache
{
  path: 'list',
  component: () => import('@/views/user/list/index.vue'),
  name: 'UserList',
  meta: {
    noCache: true  // 不缓存
  }
}

16.3 如何处理外链路由?

typescript
// 外链路由会被自动处理
{
  path: 'https://example.com',
  component: Layout,
  meta: {
    name: '外链'
  }
}

16.4 如何处理路由重定向?

typescript
// 使用redirect属性
{
  path: '/',
  redirect: '/index'
}

注意:本文档持续更新中,如有问题请及时反馈。