---
description: 路由配置规范 - 路由定义、守卫、导航、参数声明。
globs: ["**/router/**/*.ts", "**/routes/**/*.ts"]
alwaysApply: false
---

# 路由配置规范

该规则用于路由配置：

**当创建新路由时：**

- `path`: kebab-case，`name`: PascalCase
- 组件使用懒加载 `() => import()`
- 必须配置 `name` 属性

**当页面依赖 query 参数时：**

- meta 中声明 `requiredQuery` / `optionalQuery`
- JSDoc 注释说明参数用途

**当需要权限控制时：**

- `meta.requiresAuth` + 全局守卫
- 特定路由用 `beforeEnter` 守卫

---

## 📁 文件组织

**方案 A：单文件配置**（小型项目）

```
{router目录}/
├── index.ts      # 路由实例 + 配置
└── guards.ts     # 守卫（可选）
```

**方案 B：按模块拆分**（大型项目）

```
{router目录}/
├── index.ts      # 路由实例
├── routes/
│   ├── user.ts   # 用户模块
│   └── common.ts # 公共路由
└── guards.ts     # 全局守卫
```

> 📋 具体存放路径详见 @foundation/project.mdc

---

## 🎨 设计策略

### 1. 路由命名

| 属性   | 命名规则   | 示例         |
| ------ | ---------- | ------------ |
| `path` | kebab-case | `/user-list` |
| `name` | PascalCase | `UserList`   |
| 参数   | camelCase  | `:userId`    |

```typescript
// ✅ 命名一致
{ path: "/user-detail/:userId", name: "UserDetail" }

// ❌ 避免
{ path: "/userDetail/:user_id", name: "user-detail" }
```

### 2. meta 配置

**常用字段**：

| 字段            | 类型       | 说明            |
| --------------- | ---------- | --------------- |
| `title`         | `string`   | 页面标题        |
| `requiresAuth`  | `boolean`  | 是否需要登录    |
| `permissions`   | `string[]` | 所需权限        |
| `keepAlive`     | `boolean`  | 是否缓存组件    |
| `requiredQuery` | `string[]` | 必传 query 参数 |
| `optionalQuery` | `string[]` | 可选 query 参数 |

**类型扩展**：

```typescript
// router/types.ts
declare module "vue-router" {
  interface RouteMeta {
    title?: string;
    requiresAuth?: boolean;
    requiredQuery?: string[];
    optionalQuery?: string[];
  }
}
```

### 3. 守卫策略

| 类型       | 位置                      | 适用场景           |
| ---------- | ------------------------- | ------------------ |
| 全局守卫   | `router.beforeEach`       | 权限验证、页面标题 |
| 独享守卫   | 路由配置 `beforeEnter`    | 特定路由的额外校验 |
| 组件内守卫 | 组件 `onBeforeRouteLeave` | 离开确认、数据保存 |

```typescript
// 全局守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isLogin()) {
    next({ name: "Login", query: { redirect: to.fullPath } });
    return;
  }
  document.title = to.meta.title || "App";
  next();
});
```

### 4. 导航策略

**优先使用 name 导航**：

```typescript
// ✅ 使用 name
router.push({ name: "UserDetail", params: { userId: "123" } });

// ❌ 避免硬编码路径
router.push(`/user/${id}`);
```

**params vs query**：

| 类型     | URL 表现       | 刷新保留  | 适用场景       |
| -------- | -------------- | --------- | -------------- |
| `params` | `/user/123`    | ❌ 不保留 | 资源 ID        |
| `query`  | `/user?id=123` | ✅ 保留   | 筛选条件、分页 |

### 5. 参数依赖声明

**问题**：页面依赖 query 参数，但从路由配置看不出来。

**方案**：meta 声明 + JSDoc 注释

```typescript
/**
 * 活动页
 * @requires query.uid - 用户ID
 * @requires query.activityId - 活动ID
 * @optional query.from - 来源渠道
 */
{
  path: "/activity",
  name: "Activity",
  component: () => import("@/pages/activity/index.vue"),
  meta: {
    title: "活动页",
    requiredQuery: ["uid", "activityId"],
    optionalQuery: ["from"],
  },
}
```

**守卫校验**（可选）：

```typescript
router.beforeEach((to, from, next) => {
  const required = to.meta.requiredQuery || [];
  const missing = required.filter((key) => !to.query[key]);
  if (missing.length) {
    console.warn(`[Router] ${to.name} 缺少参数: ${missing.join(", ")}`);
  }
  next();
});
```

---

## ✅ 检查清单

### 配置

- ✅ 路由必须配置 `name` 属性
- ✅ 页面组件必须使用懒加载
- ✅ 依赖 query 参数的页面必须在 meta 中声明
- ❌ 禁止大型应用所有路由写在一个文件中

### 导航

- ✅ 优先使用 `name` 导航
- ❌ 禁止硬编码路由路径
- ❌ 禁止在组件中使用 `window.location` 跳转

### 守卫

- ✅ 守卫必须处理异常情况（避免死循环）
- ❌ 禁止路由配置与业务逻辑混在一起

> 📋 命名规范详见 @foundation/naming.mdc

---

## 📋 标准模板

### 路由配置

```typescript
// router/routes/user.ts
import type { RouteRecordRaw } from "vue-router";

const userRoutes: RouteRecordRaw[] = [
  {
    path: "/user",
    name: "User",
    component: () => import("@/pages/user/index.vue"),
    meta: { title: "用户中心", requiresAuth: true },
  },
  {
    path: "/user/:userId",
    name: "UserDetail",
    component: () => import("@/pages/user/detail.vue"),
    meta: { title: "用户详情", requiresAuth: true },
  },
];

export default userRoutes;
```

### 路由实例

```typescript
// router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import userRoutes from "./routes/user";

const router = createRouter({
  history: createWebHistory(),
  routes: [...userRoutes],
});

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isLogin()) {
    next({ name: "Login", query: { redirect: to.fullPath } });
    return;
  }
  document.title = to.meta.title || "App";
  next();
});

export default router;
```
