# 动态路由代码示例 ## 📦 简化版代码示例 ### 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 ``` --- ## 🔄 完整流程示例 ### 场景:用户登录后访问 `/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` 中,所有用户都能访问 - **动态路由**:从后端获取,根据用户权限动态生成