// 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');
}
});
// 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`);
};
// 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"
}
}
]
}
]
// 转换后的路由对象(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"
}
}
]
}
]
// 在路由守卫中注册路由
const routes = await generateRoutes();
routes.forEach(route => {
// 将路由添加到 Vue Router
router.addRoute(route);
// 现在可以通过以下方式访问:
// router.push('/system/user')
// router.push('/platform/carousel')
});
<!-- src/layout/components/Sidebar/index.vue -->
<template>
<el-menu :default-active="activeMenu">
<template v-for="route in sidebarRouters" :key="route.path">
<!-- 有子菜单 -->
<el-sub-menu v-if="route.children" :index="route.path">
<template #title>
<el-icon><component :is="route.meta.icon" /></el-icon>
<span>{{ route.meta.title }}</span>
</template>
<el-menu-item
v-for="child in route.children"
:key="child.path"
:index="child.path"
@click="handleMenuClick(child)"
>
{{ child.meta.title }}
</el-menu-item>
</el-sub-menu>
<!-- 无子菜单 -->
<el-menu-item v-else :index="route.path" @click="handleMenuClick(route)">
<el-icon><component :is="route.meta.icon" /></el-icon>
<span>{{ route.meta.title }}</span>
</el-menu-item>
</template>
</el-menu>
</template>
<script setup>
import { usePermissionStore } from '@/store/modules/permission';
import { useRouter } from 'vue-router';
const permissionStore = usePermissionStore();
const router = useRouter();
// 获取侧边栏路由
const sidebarRouters = computed(() => {
return permissionStore.getSidebarRoutes();
});
// 菜单点击事件
const handleMenuClick = (route) => {
router.push(route.path);
};
</script>
/platform/carousel// 步骤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: 路由匹配成功,显示页面
// <router-view> 渲染 Carousel 组件
后端数据库 (sys_menu)
↓
后端接口 (/system/menu/getRouters)
↓
前端 API (getRouters())
↓
路由转换 (filterAsyncRouter)
↓
组件加载 (loadView)
↓
路由注册 (router.addRoute)
↓
侧边栏显示 (sidebarRouters)
↓
页面渲染 (<router-view>)
generateRoutes()filterAsyncRouter()loadView()'system/user/index')() => import('@/views/system/user/index.vue'))import.meta.glob() 预扫描所有组件文件router.addRoute()router.push() 访问A: 因为路由是异步注册的,需要确保路由已注册后再跳转,否则会 404。
A: 后端返回的 component 路径(如 'system/user/index')必须对应 views 目录下的文件路径(views/system/user/index.vue)。
A: duplicateRouteChecker 会检查并报错,可能导致路由冲突和 404。
A: 后端根据用户角色和权限过滤菜单数据,前端只显示后端返回的菜单。
A:
router/index.ts 中,所有用户都能访问