# 新增前端页面路由指南

本文档介绍如何在活动管理系统中新增前端页面路由。

## 概述

系统采用**动态路由**机制，路由配置存储在数据库中，前端根据用户权限动态加载。新增页面需要完成以下 4 个步骤：

```
┌──────────────────────────────────────────────────────────────┐
│  Step 1: 创建 Vue 组件文件                                    │
│          frontend/src/views/xxx/Index.vue                    │
├──────────────────────────────────────────────────────────────┤
│  Step 2: 在 componentMap 中注册组件                           │
│          frontend/src/utils/menu.ts                          │
├──────────────────────────────────────────────────────────────┤
│  Step 3: 在数据库 menus 表中添加菜单记录                       │
├──────────────────────────────────────────────────────────────┤
│  Step 4: 在数据库 tenant_menus 表中关联租户                    │
└──────────────────────────────────────────────────────────────┘
```

## 详细步骤

### Step 1: 创建 Vue 组件文件

在 `frontend/src/views/` 目录下创建对应的 Vue 组件。

**目录结构规范：**
```
frontend/src/views/
├── workbench/          # 工作台模块
│   └── Index.vue
├── contests/           # 活动管理模块
│   ├── Index.vue       # 活动列表
│   ├── Create.vue      # 创建活动
│   ├── Detail.vue      # 活动详情
│   ├── registrations/  # 报名管理
│   │   └── Index.vue
│   ├── works/          # 作品管理
│   │   └── Index.vue
│   └── ...
├── system/             # 系统管理模块
│   ├── users/
│   │   └── Index.vue
│   ├── roles/
│   │   └── Index.vue
│   └── ...
└── your-module/        # 你的新模块
    └── Index.vue
```

**组件模板示例：**
```vue
<template>
  <div class="page-container">
    <h1>页面标题</h1>
    <!-- 页面内容 -->
  </div>
</template>

<script setup lang="ts">
// 组件逻辑
</script>

<style scoped lang="scss">
.page-container {
  // 样式
}
</style>
```

### Step 2: 注册组件映射

在 `frontend/src/utils/menu.ts` 文件中的 `componentMap` 对象中添加组件映射。

**文件位置：** `frontend/src/utils/menu.ts`

```typescript
const componentMap: Record<string, () => Promise<any>> = {
  // 工作台
  "workbench/Index": () => import("@/views/workbench/Index.vue"),

  // 活动管理模块
  "contests/Index": () => import("@/views/contests/Index.vue"),
  "contests/Create": () => import("@/views/contests/Create.vue"),
  // ...

  // 系统管理模块
  "system/users/Index": () => import("@/views/system/users/Index.vue"),
  // ...

  // ========== 新增组件映射 ==========
  "your-module/Index": () => import("@/views/your-module/Index.vue"),
  "your-module/Detail": () => import("@/views/your-module/Detail.vue"),
};
```

**注意事项：**
- Key 格式：`模块名/组件名`，不需要 `@/views/` 前缀和 `.vue` 后缀
- Value 格式：使用动态 import 函数
- 如果未注册，控制台会输出警告信息

### Step 3: 添加数据库菜单记录

在数据库的 `menus` 表中插入菜单记录。

**menus 表结构：**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 主键，自增 |
| name | varchar | 菜单名称（显示文本） |
| path | varchar | 路由路径，如 `/contests` |
| component | varchar | 组件路径，对应 componentMap 的 key |
| icon | varchar | 图标名称，使用 Ant Design 图标 |
| parent_id | int | 父菜单ID，顶级菜单为 NULL |
| sort | int | 排序序号 |
| permission | varchar | 权限标识，如 `contest:read` |
| tenant_id | int | 租户ID |

**SQL 示例：**

```sql
-- 添加顶级菜单
INSERT INTO menus (name, path, component, icon, parent_id, sort, tenant_id)
VALUES ('新模块', '/your-module', 'your-module/Index', 'AppstoreOutlined', NULL, 10, 1);

-- 获取刚插入的菜单ID
SET @parent_id = LAST_INSERT_ID();

-- 添加子菜单
INSERT INTO menus (name, path, component, icon, parent_id, sort, tenant_id)
VALUES ('子页面1', '/your-module/sub1', 'your-module/Sub1', NULL, @parent_id, 1, 1);

INSERT INTO menus (name, path, component, icon, parent_id, sort, tenant_id)
VALUES ('子页面2', '/your-module/sub2', 'your-module/Sub2', NULL, @parent_id, 2, 1);
```

**常用图标名称：**
- `HomeOutlined` - 首页
- `AppstoreOutlined` - 应用
- `TrophyOutlined` - 奖杯/活动
- `TeamOutlined` - 团队
- `UserOutlined` - 用户
- `SettingOutlined` - 设置
- `FileOutlined` - 文件
- `FolderOutlined` - 文件夹

更多图标请参考：[Ant Design Icons](https://ant.design/components/icon-cn)

### Step 4: 关联租户菜单

在 `tenant_menus` 表中添加租户与菜单的关联关系。

**SQL 示例：**

```sql
-- 假设菜单ID为 30，租户ID为 1
INSERT INTO tenant_menus (tenant_id, menu_id) VALUES (1, 30);

-- 如果刚插入菜单，可以使用 LAST_INSERT_ID()
INSERT INTO tenant_menus (tenant_id, menu_id) VALUES (1, LAST_INSERT_ID());

-- 批量关联（关联多个租户）
INSERT INTO tenant_menus (tenant_id, menu_id) VALUES
(1, 30),
(2, 30),
(3, 30);
```

## 完整示例

假设要新增一个「公告管理」页面：

### 1. 创建组件文件

**文件：** `frontend/src/views/announcements/Index.vue`

```vue
<template>
  <div>
    <a-card title="公告管理">
      <a-table :columns="columns" :data-source="data" />
    </a-card>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const columns = [
  { title: '标题', dataIndex: 'title' },
  { title: '发布时间', dataIndex: 'createdAt' },
  { title: '操作', key: 'action' },
]

const data = ref([])
</script>
```

### 2. 注册组件

**文件：** `frontend/src/utils/menu.ts`

```typescript
const componentMap: Record<string, () => Promise<any>> = {
  // ... 其他组件
  "announcements/Index": () => import("@/views/announcements/Index.vue"),
};
```

### 3. 添加菜单记录

```sql
INSERT INTO menus (name, path, component, icon, parent_id, sort, permission, tenant_id)
VALUES ('公告管理', '/announcements', 'announcements/Index', 'NotificationOutlined', NULL, 5, 'announcement:read', 1);
```

### 4. 关联租户

```sql
INSERT INTO tenant_menus (tenant_id, menu_id) VALUES (1, LAST_INSERT_ID());
```

### 5. 验证

1. 重新登录系统（清除缓存）
2. 检查侧边栏是否显示新菜单
3. 点击菜单验证页面是否正常加载

## 常见问题

### Q1: 菜单不显示？

检查以下几点：
1. `tenant_menus` 表是否已关联当前租户
2. 用户角色是否有该菜单的权限
3. 清除浏览器缓存后重新登录

### Q2: 点击菜单报 404？

检查以下几点：
1. `componentMap` 是否已注册该组件
2. 组件文件路径是否正确
3. 查看控制台是否有警告信息

### Q3: 控制台警告「组件路径未在 componentMap 中定义」？

在 `menu.ts` 的 `componentMap` 中添加对应的组件映射。

### Q4: 如何添加需要权限的页面？

在 `menus` 表的 `permission` 字段设置权限标识，如 `announcement:read`，然后确保用户角色拥有该权限。

## 相关文件

| 文件 | 说明 |
|------|------|
| `frontend/src/utils/menu.ts` | 组件映射、菜单转换工具 |
| `frontend/src/router/index.ts` | 路由配置、动态路由加载 |
| `frontend/src/stores/auth.ts` | 用户认证、菜单数据存储 |
| `frontend/src/layouts/BasicLayout.vue` | 主布局、侧边栏菜单渲染 |
