library-picturebook-activity/frontend/docs/add-new-route.md
2026-01-08 09:17:46 +08:00

263 lines
8.1 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.

# 新增前端页面路由指南
本文档介绍如何在比赛管理系统中新增前端页面路由。
## 概述
系统采用**动态路由**机制,路由配置存储在数据库中,前端根据用户权限动态加载。新增页面需要完成以下 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` | 主布局、侧边栏菜单渲染 |