前端菜单路由
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 属性说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| hidden | Boolean | false | 当设置为 true 时,该路由不会在侧边栏显示 |
| alwaysShow | Boolean | false | 当子路由大于1个时,自动变成嵌套模式。只有一个时,会将子路由当做根路由显示。设置 true 可忽略此规则,始终显示根路由 |
| title | String | - | 设置该路由在侧边栏和面包屑中展示的名字 |
| icon | String | - | 设置该路由的图标,支持 Element Plus 图标和 Iconify 图标 |
| noCache | Boolean | false | 如果设置为 true,则不会被 <keep-alive> 缓存 |
| breadcrumb | Boolean | true | 如果设置为 false,则不会在面包屑中显示 |
| affix | Boolean | false | 如果设置为 true,则会一直固定在 tag 标签中 |
| noTagsView | Boolean | false | 如果设置为 true,则不会出现在 tag 标签中 |
| activeMenu | String | - | 显示高亮的路由路径 |
| followAuth | String | - | 跟随哪个路由进行权限过滤 |
| canTo | Boolean | false | 设置为 true 即使 hidden 为 true,也依然可以进行路由跳转 |
| dot | Boolean | false | 是否显示红点标记 |
| rank | Number | - | 路由排序等级,数值越小越靠前 |
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.id7.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.keyword7.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.query8. 路由缓存
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.keyword16.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'
}注意:本文档持续更新中,如有问题请及时反馈。
