# 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 |
