Appearance
路由相关问题
markdown
用户访问页面 ->
判断是否登录 ->
是 -> 是否已经注册动态路由 ->
否 -> 请求后端菜单 -> 转成 routes -> addRoute -> next({ ...to, replace: true })
是 -> 正常放行
否 -> 判断是否在白名单 ->
是 -> 放行
否 -> 重定向登录页Vue 3 + Vue Router 4 + Pinia 动态路由权限控制方案文档
📘 一、项目核心依赖
- Vue 3
- Vue Router 4
- Pinia
- Axios
- TypeScript(推荐)
📁 二、项目目录结构
bash
src/
├── router/
│ ├── index.ts # 初始化静态路由
│ ├── dynamicRoutes.ts # Layout 嵌套下的动态子路由
│ ├── permission.ts # 路由守卫 + 权限控制核心逻辑
├── stores/
│ ├── routesList.ts # 存储动态路由 & 菜单
│ └── userInfo.ts # 用户信息、角色权限
├── views/
│ └── xxx.vue # 页面组件
├── layout/
│ └── index.vue # Layout 主框架
└── utils/
├── auth.ts # token 存取
└── menuToRoute.ts # 后端菜单转成 RouteRecordRaw🔁 三、权限流程图
text
初始化 Vue App ->
静态路由注册完成 ->
路由守卫开始 ->
如果未登录 -> 重定向登录页
如果已登录且未加载动态路由 ->
拉取菜单接口 -> 转换为 RouteRecordRaw -> addRoute ->
next({ ...to, replace: true }) 强制跳转已注册的路由
如果已加载过动态路由 -> next()🧩 四、静态路由配置(router/index.ts)
typescript
const staticRoutes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/login.vue')
},
{
path: '/:path(.*)*',
name: 'notFound',
component: () => import('@/views/error/404.vue')
}
];🧱 五、动态路由模板(dynamicRoutes.ts)
typescript
export const dynamicRoutes: RouteRecordRaw[] = [
{
path: '/',
name: '/',
component: () => import('@/layout/index.vue'),
redirect: '/home',
children: [] // 后端菜单处理后放这里
}
];⚙️ 六、菜单转换工具(utils/menuToRoute.ts)
typescript
export function transformMenusToRoutes(menus: any[]): RouteRecordRaw[] {
return menus.map(menu => ({
path: menu.path,
name: menu.name,
component: () => import(`@/views/${menu.component}.vue`),
meta: menu.meta
}));
}🔒 七、权限守卫逻辑(router/permission.ts)
typescript
router.beforeEach(async (to, from, next) => {
const token = getToken();
const whiteList = ['/login'];
if (!token && !whiteList.includes(to.path)) {
return next('/login');
}
const storesRoutesList = useRoutesList(pinia);
const { routesList } = storeToRefs(storesRoutesList);
if (token && routesList.value.length === 0) {
const menus = await getMenuFromServer();
const dynamicChildren = transformMenusToRoutes(menus);
dynamicRoutes[0].children = dynamicChildren;
router.addRoute(dynamicRoutes[0]);
routesList.value = dynamicChildren;
return next({ ...to, replace: true });
}
next();
});✅ 八、注意事项清单
| 项 | 建议 |
|---|---|
| addRoute() 位置 | 在路由守卫中首次登录后添加 |
| next() 跳转方式 | 使用 `next({ ...to, replace: true })` 避免匹配失败 |
| 404/401 路由 | 也放到 layout.children 下 |
| 菜单转换 | 使用工具方法处理 component 路径映射 |
| 防止重复注册 | 用 Pinia 或标志变量控制只注册一次 |
| 动态 children 添加 | 只注册 layout 层,children 自动注册 |
🎁 九、进阶拓展
- 动态根据角色过滤菜单
- 添加按钮级别权限指令(如
v-permission) - 动态侧边栏高亮激活处理
- 页面缓存(配合
meta.isKeepAlive)
📦 十、后端返回菜单结构示例
json
[
{
"path": "/home",
"name": "Home",
"component": "home/index",
"meta": {
"title": "首页",
"roles": ["admin", "common"]
}
},
{
"path": "/user",
"name": "User",
"component": "user/index",
"meta": {
"title": "用户管理",
"roles": ["admin"]
}
}
]