Vue3动态路由核心实现
1. 动态导入组件
使用 import.meta.glob 批量导入组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const modules = import.meta.glob('../views/modules/**/index.vue')
export function loadComponent(componentPath) { if (componentPath === 'Layout') { return () => import('@/views/layout/index.vue') } const component = modules[`../views/modules${componentPath}/index.vue`] if (component) { return component } return () => import('@/views/404/index.vue') }
|
2. 路由数据转换
将后端返回的菜单数据转换为路由配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { loadComponent } from './loadComponent'
export function transformRoutes(menuList) { const routes = [] menuList.forEach(menu => { const route = { path: menu.path, name: menu.name, component: loadComponent(menu.component), meta: { title: menu.title, icon: menu.icon, hidden: menu.hidden, keepAlive: menu.keepAlive } } if (menu.children && menu.children.length) { route.children = transformRoutes(menu.children) } routes.push(route) }) return routes }
|
3. 权限Store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { defineStore } from 'pinia' import { getMenuList } from '@/api/menu' import { transformRoutes } from '@/utils/routeHelper'
export const usePermissionStore = defineStore('permission', { state: () => ({ routes: [], menuList: [] }), actions: { async generateRoutes() { try { const { data } = await getMenuList() this.menuList = data const dynamicRoutes = transformRoutes(data) this.routes = dynamicRoutes return dynamicRoutes } catch (error) { console.error('生成路由失败:', error) return [] } } } })
|
4. 动态添加路由
在路由守卫中添加动态路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import { createRouter, createWebHistory } from 'vue-router' import { useUserStore } from '@/store/user' import { usePermissionStore } from '@/store/permission'
const router = createRouter({ history: createWebHistory(), routes: [ { path: '/login', component: () => import('@/views/login/index.vue') } ] })
router.beforeEach(async (to, from, next) => { const userStore = useUserStore() const permissionStore = usePermissionStore() if (userStore.token) { if (permissionStore.routes.length === 0) { try { const dynamicRoutes = await permissionStore.generateRoutes() dynamicRoutes.forEach(route => { router.addRoute(route) }) next({ ...to, replace: true }) } catch (error) { userStore.logout() next('/login') } } else { next() } } else { next('/login') } })
export default router
|
5. 后端数据格式
后端返回的菜单数据格式示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| [ { "id": 1, "path": "/system", "name": "System", "component": "Layout", "title": "系统管理", "icon": "system", "hidden": false, "children": [ { "id": 11, "path": "user", "name": "SystemUser", "component": "/system/user", "title": "用户管理", "icon": "user", "hidden": false } ] } ]
|
6. 文件目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| src/ ├── views/ │ ├── layout/ │ │ └── index.vue │ └── modules/ │ ├── system/ │ │ ├── user/ │ │ │ └── index.vue │ │ └── role/ │ │ └── index.vue │ └── content/ │ └── article/ │ └── index.vue
|
核心要点
import.meta.glob: 批量导入组件,避免手动维护组件映射
router.addRoute(): 动态添加路由到router实例
next({ ...to, replace: true }): 确保动态路由添加后重新导航
- 组件路径映射: 后端返回的component字段对应views目录结构