ether-docs/_archive/04-TECHNICAL/ROUTING_BEST_PRACTICES.md

243 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ether 项目前端路由最佳实践
## 概述
本文档总结了 Ether 项目前端路由开发中的最佳实践,以及常见问题和解决方案。
## 路由架构设计
### 1. 路由分层结构
```
路由层级:
├── 公开路由 (Public Routes)
│ ├── /login - 登录页
│ └── /no-project - 无项目提示页
├── 布局路由 (Layout Route)
│ └── / (Layout)
│ ├── /space-node - 空间节点管理
│ ├── /mdm/projects - 项目管理
│ ├── /mdm/visitors - 访客管理
│ └── ... 其他业务页面
└── 404 路由
└── /* - 匹配所有未定义路由
```
### 2. 动态路由生成机制
```typescript
// 1. 定义组件映射表
const dynamicRouteComponents: Record<string, () => Promise<any>> = {
"Mdm/projects": () => import("../views/mdm/project/index.vue"),
"Mdm/visitors": () => import("../views/mdm/visitor/index.vue"),
// ...
};
// 2. 根据后端菜单数据生成路由
function generateRoutesFromMenus(menus: MenuInfo[]): RouteRecordRaw[] {
return menus.map(menu => ({
path: menu.menuPath, // 如: "/mdm/visitors"
name: menu.componentName, // 如: "Mdm/visitors"
component: dynamicRouteComponents[menu.componentName],
meta: {
title: menu.permissionName,
icon: menu.menuIcon,
},
}));
}
// 3. 动态添加到路由表
menuRoutes.forEach(route => {
router.addRoute("Layout", {
path: route.path.substring(1), // 移除开头的 "/"
name: route.name,
component: route.component,
meta: route.meta,
});
});
```
## 常见问题与解决方案
### 问题 1: 菜单点击后页面不跳转
**症状**: 点击侧边栏菜单页面没有切换URL 不变。
**根本原因**:
- 菜单点击使用 `router.push({ name: key })`,但动态路由的 `name` 可能未正确注册
- 路由守卫中动态添加路由后,使用 `name` 导航可能找不到对应路由
**解决方案**:
```typescript
// ❌ 错误方式:使用 name 导航
const handleMenuClick = ({ key }: { key: string }) => {
router.push({ name: key }); // 可能找不到路由
};
// ✅ 正确方式:使用 path 导航
const handleMenuClick = ({ key }: { key: string }) => {
const menu = findMenuByComponentName(menus, key);
if (menu?.menuPath) {
router.push(menu.menuPath); // 使用 path 更可靠
}
};
```
### 问题 2: 动态路由添加后刷新 404
**症状**: 动态添加路由后可以访问,但刷新页面后显示 404。
**根本原因**:
- 动态路由只在路由守卫中添加一次,刷新后需要重新添加
- 404 路由优先级问题
**解决方案**:
```typescript
// 1. 确保每次路由守卫都检查并添加路由
router.beforeEach(async (to, from, next) => {
if (!permissionStore.isInitialized) {
await permissionStore.fetchUserPermissions();
// 重新生成并添加路由
const menuRoutes = generateRoutesFromMenus(permissionStore.menus);
menuRoutes.forEach(route => {
const existing = router.getRoutes().find(r => r.name === route.name);
if (!existing) {
router.addRoute("Layout", route);
}
});
// 重新导航
next({ ...to, replace: true });
return;
}
next();
});
// 2. 404 路由最后添加,确保优先级最低
router.addRoute({
path: "/:pathMatch(.*)*",
name: "NotFound",
component: () => import("../views/404/index.vue"),
});
```
### 问题 3: 路由名称冲突
**症状**: 控制台警告路由名称已存在。
**根本原因**:
- 多次添加相同 name 的路由
- 热更新时重复添加
**解决方案**:
```typescript
// 添加前检查是否已存在
menuRoutes.forEach(route => {
const existingRoute = router.getRoutes().find(r => r.name === route.name);
if (!existingRoute) {
router.addRoute("Layout", route);
}
});
```
### 问题 4: 路由路径格式不一致
**症状**: 菜单数据和路由配置的路径不匹配。
**根本原因**:
- 后端返回的 `menuPath` 包含前导 `/`,但动态添加时需要移除
- 路由配置中 path 格式不统一
**解决方案**:
```typescript
// 统一路径格式
const normalizePath = (path: string): string => {
// 确保以 / 开头
if (!path.startsWith('/')) {
path = '/' + path;
}
// 移除尾部 /
return path.replace(/\/$/, '') || '/';
};
// 动态添加时移除前导 /
router.addRoute("Layout", {
path: route.path.substring(1), // "/mdm/visitors" -> "mdm/visitors"
...
});
```
## 数据流设计
### 菜单数据结构
```typescript
interface MenuInfo {
id: string;
permissionCode: string; // 权限编码: "mdm:visitor:menu"
permissionName: string; // 显示名称: "访客管理"
menuPath: string; // 路由路径: "/mdm/visitors"
menuIcon?: string; // 图标: "UserAddOutlined"
componentName: string; // 组件名: "Mdm/visitors"
parentId?: string; // 父菜单ID
sortOrder: number; // 排序
children?: MenuInfo[]; // 子菜单
}
```
### 关键映射关系
| 后端字段 | 前端用途 | 示例 |
|---------|---------|------|
| `componentName` | 路由 name / 菜单 key | `Mdm/visitors` |
| `menuPath` | 路由 path | `/mdm/visitors` |
| `permissionName` | 页面标题 / 菜单标签 | `访客管理` |
| `menuIcon` | 菜单图标 | `UserAddOutlined` |
## 开发规范
### 1. 新增页面步骤
1. **创建页面组件**: `src/views/{module}/{page}/index.vue`
2. **注册路由映射**: 在 `src/router/index.ts``dynamicRouteComponents` 中添加
3. **配置后端权限**: 在数据库中添加菜单和权限数据
4. **验证路由**: 确保 `componentName``menuPath` 匹配
### 2. 路由配置检查清单
- [ ] `componentName``dynamicRouteComponents` 中有对应映射
- [ ] `menuPath` 格式统一(以 `/` 开头)
- [ ] 路由 `name` 唯一,不与其他路由冲突
- [ ] 动态路由添加前检查是否已存在
### 3. 调试技巧
```typescript
// 查看所有已注册路由
console.log(router.getRoutes());
// 检查特定路由是否存在
const route = router.getRoutes().find(r => r.name === 'Mdm/visitors');
console.log(route);
// 监听路由变化
router.beforeEach((to, from) => {
console.log('Navigating from', from.path, 'to', to.path);
});
```
## 相关文件
- `src/router/index.ts` - 路由配置和动态路由生成
- `src/views/layout/index.vue` - 布局组件和菜单处理
- `src/stores/permission.ts` - 权限状态和菜单数据
## 更新记录
| 日期 | 更新内容 | 作者 |
|------|---------|------|
| 2026-02-10 | 初始版本,总结路由问题和解决方案 | AI Assistant |