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

6.5 KiB
Raw Blame History

Ether 项目前端路由最佳实践

概述

本文档总结了 Ether 项目前端路由开发中的最佳实践,以及常见问题和解决方案。

路由架构设计

1. 路由分层结构

路由层级:
├── 公开路由 (Public Routes)
│   ├── /login - 登录页
│   └── /no-project - 无项目提示页
│
├── 布局路由 (Layout Route)
│   └── / (Layout)
│       ├── /space-node - 空间节点管理
│       ├── /mdm/projects - 项目管理
│       ├── /mdm/visitors - 访客管理
│       └── ... 其他业务页面
│
└── 404 路由
    └── /* - 匹配所有未定义路由

2. 动态路由生成机制

// 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 导航可能找不到对应路由

解决方案:

// ❌ 错误方式:使用 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 路由优先级问题

解决方案:

// 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 的路由
  • 热更新时重复添加

解决方案:

// 添加前检查是否已存在
menuRoutes.forEach(route => {
  const existingRoute = router.getRoutes().find(r => r.name === route.name);
  if (!existingRoute) {
    router.addRoute("Layout", route);
  }
});

问题 4: 路由路径格式不一致

症状: 菜单数据和路由配置的路径不匹配。

根本原因:

  • 后端返回的 menuPath 包含前导 /,但动态添加时需要移除
  • 路由配置中 path 格式不统一

解决方案:

// 统一路径格式
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"
  ...
});

数据流设计

菜单数据结构

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.tsdynamicRouteComponents 中添加
  3. 配置后端权限: 在数据库中添加菜单和权限数据
  4. 验证路由: 确保 componentNamemenuPath 匹配

2. 路由配置检查清单

  • componentNamedynamicRouteComponents 中有对应映射
  • menuPath 格式统一(以 / 开头)
  • 路由 name 唯一,不与其他路由冲突
  • 动态路由添加前检查是否已存在

3. 调试技巧

// 查看所有已注册路由
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