# 动态路由代码示例
## 📦 简化版代码示例
### 1. 路由守卫(简化版)
```typescript
// src/permission.ts (简化版)
router.beforeEach(async (to, from, next) => {
// 1. 检查是否已登录
if (getToken()) {
// 2. 检查用户信息是否已加载
if (useUserStore().roles.length === 0) {
// 3. 获取用户信息
await useUserStore().getInfo();
// 4. 生成动态路由
const routes = await usePermissionStore().generateRoutes();
// 5. 注册路由
routes.forEach(route => {
router.addRoute(route);
});
// 6. 重新跳转
next({ ...to, replace: true });
} else {
next();
}
} else {
next('/login');
}
});
```
---
### 2. 生成路由(简化版)
```typescript
// src/store/modules/permission.ts (简化版)
const generateRoutes = async () => {
// 步骤1: 调用后端接口
const res = await getRouters();
const menuData = res.data;
// 步骤2: 转换路由格式
const routes = transformRoutes(menuData);
// 步骤3: 保存到 Store
setSidebarRouters(routes);
return routes;
};
// 转换函数(简化版)
const transformRoutes = (menuData) => {
return menuData.map(menu => {
const route = {
name: menu.name,
path: menu.path,
component: loadComponent(menu.component), // 动态加载组件
meta: menu.meta,
children: menu.children ? transformRoutes(menu.children) : undefined
};
// 处理特殊组件
if (menu.component === 'Layout') {
route.component = Layout;
}
return route;
});
};
// 加载组件(简化版)
const loadComponent = (componentPath) => {
// componentPath: 'system/user/index'
// 返回: () => import('@/views/system/user/index.vue')
return () => import(`@/views/${componentPath}.vue`);
};
```
---
### 3. 后端返回数据示例
```json
// GET /system/menu/getRouters 返回的数据
[
{
"name": "System",
"path": "/system",
"component": "Layout",
"redirect": "/system/user",
"alwaysShow": true,
"meta": {
"title": "系统管理",
"icon": "system"
},
"children": [
{
"name": "User",
"path": "user",
"component": "system/user/index",
"meta": {
"title": "用户管理",
"icon": "user"
}
}
]
},
{
"name": "Platform",
"path": "/platform",
"component": "Layout",
"redirect": "/platform/carousel",
"alwaysShow": true,
"meta": {
"title": "平台装修",
"icon": "system"
},
"children": [
{
"name": "Carousel",
"path": "carousel",
"component": "platform/decoration/carousel/index",
"meta": {
"title": "轮播广告",
"icon": "component"
}
}
]
}
]
```
---
### 4. 转换后的路由对象示例
```typescript
// 转换后的路由对象(Vue Router 格式)
[
{
name: "System",
path: "/system",
component: Layout, // 已转换为组件对象
redirect: "/system/user",
alwaysShow: true,
meta: {
title: "系统管理",
icon: "system"
},
children: [
{
name: "User",
path: "user",
component: () => import('@/views/system/user/index.vue'), // 动态导入
meta: {
title: "用户管理",
icon: "user"
}
}
]
},
{
name: "Platform",
path: "/platform",
component: Layout,
redirect: "/platform/carousel",
alwaysShow: true,
meta: {
title: "平台装修",
icon: "system"
},
children: [
{
name: "Carousel",
path: "carousel",
component: () => import('@/views/platform/decoration/carousel/index.vue'),
meta: {
title: "轮播广告",
icon: "component"
}
}
]
}
]
```
---
### 5. 注册路由示例
```typescript
// 在路由守卫中注册路由
const routes = await generateRoutes();
routes.forEach(route => {
// 将路由添加到 Vue Router
router.addRoute(route);
// 现在可以通过以下方式访问:
// router.push('/system/user')
// router.push('/platform/carousel')
});
```
---
### 6. 侧边栏使用路由示例
```vue
{{ route.meta.title }}
{{ child.meta.title }}
{{ route.meta.title }}
```
---
## 🔄 完整流程示例
### 场景:用户登录后访问 `/platform/carousel`
```typescript
// 步骤1: 用户访问 /platform/carousel
router.push('/platform/carousel');
// 步骤2: 路由守卫拦截
router.beforeEach(async (to, from, next) => {
// to.path = '/platform/carousel'
// 步骤3: 检查 Token
if (getToken()) {
// 步骤4: 检查用户信息
if (useUserStore().roles.length === 0) {
// 步骤5: 获取用户信息
await useUserStore().getInfo();
// 步骤6: 调用后端接口
const res = await getRouters();
// res.data = [{ name: "Platform", path: "/platform", ... }]
// 步骤7: 转换路由
const routes = await generateRoutes();
// routes = [{ name: "Platform", component: Layout, ... }]
// 步骤8: 注册路由
routes.forEach(route => {
router.addRoute(route);
});
// 步骤9: 重新跳转
next({ path: '/platform/carousel', replace: true });
} else {
// 用户信息已加载,直接放行
next();
}
}
});
// 步骤10: 路由匹配成功,显示页面
// 渲染 Carousel 组件
```
---
## 📊 数据流转图
```
后端数据库 (sys_menu)
↓
后端接口 (/system/menu/getRouters)
↓
前端 API (getRouters())
↓
路由转换 (filterAsyncRouter)
↓
组件加载 (loadView)
↓
路由注册 (router.addRoute)
↓
侧边栏显示 (sidebarRouters)
↓
页面渲染 ()
```
---
## 🎯 关键函数说明
### `generateRoutes()`
- **作用**:生成动态路由的主函数
- **输入**:无(内部调用后端接口)
- **输出**:路由对象数组
- **流程**:获取数据 → 转换格式 → 保存到 Store → 返回路由
### `filterAsyncRouter()`
- **作用**:将后端 JSON 转换为 Vue Router 格式
- **输入**:后端返回的路由 JSON 数组
- **输出**:Vue Router 路由对象数组
- **关键**:处理组件加载、递归处理子路由
### `loadView()`
- **作用**:根据路径字符串动态加载 Vue 组件
- **输入**:组件路径字符串(如 `'system/user/index'`)
- **输出**:组件导入函数(如 `() => import('@/views/system/user/index.vue')`)
- **原理**:使用 `import.meta.glob()` 预扫描所有组件文件
### `router.addRoute()`
- **作用**:将路由添加到 Vue Router 实例
- **输入**:路由对象
- **输出**:无
- **效果**:路由立即可用,可以通过 `router.push()` 访问
---
## 💡 常见问题
### Q1: 为什么需要重新跳转?
**A**: 因为路由是异步注册的,需要确保路由已注册后再跳转,否则会 404。
### Q2: 组件路径如何匹配?
**A**: 后端返回的 `component` 路径(如 `'system/user/index'`)必须对应 `views` 目录下的文件路径(`views/system/user/index.vue`)。
### Q3: 路由名称重复会怎样?
**A**: `duplicateRouteChecker` 会检查并报错,可能导致路由冲突和 404。
### Q4: 如何实现权限控制?
**A**: 后端根据用户角色和权限过滤菜单数据,前端只显示后端返回的菜单。
### Q5: 静态路由和动态路由的区别?
**A**:
- **静态路由**:写在 `router/index.ts` 中,所有用户都能访问
- **动态路由**:从后端获取,根据用户权限动态生成