# 赛事管理模块产品方案与实现计划

## 📋 目录
1. [产品交互方案](#产品交互方案)
2. [数据库设计分析](#数据库设计分析)
3. [功能模块划分](#功能模块划分)
4. [实现计划](#实现计划)
5. [技术实现要点](#技术实现要点)

---

## 产品交互方案

### 1. 赛事创建

#### 1.1 功能概述
管理员创建赛事，填写赛事基本信息、时间安排、参赛范围等。

#### 1.2 交互流程
```
管理员进入"赛事管理" → 点击"创建赛事" → 填写表单 → 保存草稿/提交审核
```

#### 1.3 表单字段（基于 t_contest 表）
- **基本信息**
  - 赛事名称（必填，唯一性校验）
  - 赛事类型（字典：individual/team）
  - 赛事状态（默认：unpublished）
  - 封面图（上传）
  - 海报图（上传）
  - 赛事详情（富文本编辑器）

- **时间安排**
  - 赛事开始时间
  - 赛事结束时间
  - 报名开始时间
  - 报名结束时间
  - 作品提交开始时间
  - 作品提交结束时间
  - 评审开始时间
  - 评审结束时间
  - 结果发布时间（可选）

- **参赛范围**
  - 授权租户（多选，支持租户列表选择）
  - 提交规则（once/resubmit）

- **联系信息**
  - 联系人姓名
  - 联系电话
  - 联系人二维码（上传）

- **组织信息**
  - 主办单位（数组）
  - 协办单位（数组）
  - 赞助单位（数组）

- **线下信息**
  - 线下地址（可选）

- **评审规则**
  - 评审规则ID（关联评审规则配置）

#### 1.4 业务规则
- 时间顺序校验：报名开始 < 报名结束 < 提交开始 < 提交结束 < 评审开始 < 评审结束 < 结果发布
- 赛事名称在系统内唯一
- 创建后状态为 `unpublished`，需要发布后才能被租户看到
- 支持保存草稿（可多次编辑）

---

### 2. 赛事发布

#### 2.1 功能概述
管理员将已创建的赛事发布，使其对授权租户可见。

#### 2.2 交互流程
```
赛事列表 → 选择赛事 → 点击"发布" → 确认发布 → 更新状态为 published
```

#### 2.3 业务规则
- 只有状态为 `unpublished` 的赛事可以发布
- 发布前校验必填字段完整性
- 发布后，授权租户可以看到该赛事
- 已发布的赛事可以撤回（状态改回 `unpublished`），但需要检查是否有报名记录

---

### 3. 赛事公告

#### 3.1 功能概述
管理员发布赛事相关公告，通知参赛者重要信息。

#### 3.2 交互流程
```
赛事详情页 → "公告管理" → 创建公告 → 编辑内容 → 发布公告
```

#### 3.3 功能设计
**注意**：当前 SQL 中没有公告表，需要新增：
- `t_contest_notice` 表
  - id, contest_id, title, content, notice_type, priority, publish_time, creator, create_time, modify_time, valid_state

#### 3.4 公告类型
- 系统公告（系统自动生成）
- 人工公告（管理员发布）
- 紧急通知（高优先级）

---

### 4. 赛事报名

#### 4.1 功能概述
授权租户的用户（学生/老师）报名参加赛事，支持个人赛和团队赛。

#### 4.2 交互流程

**个人赛报名：**
```
租户用户登录 → 浏览已发布赛事 → 选择赛事 → 点击"立即报名" → 填写信息 → 提交报名
```

**团队赛报名：**
```
队长创建团队 → 邀请成员 → 成员确认加入 → 队长提交团队报名 → 等待审核
```

#### 4.3 报名流程详细设计

**4.3.1 个人赛报名**
1. 用户选择赛事
2. 检查报名时间是否在有效期内
3. 检查用户是否已报名（防止重复报名）
4. 填写报名信息（账号信息自动填充）
5. 提交报名（状态：pending）
6. 管理员审核（可选，根据赛事配置）
7. 审核通过（状态：passed）或拒绝（状态：rejected）

**4.3.2 团队赛报名**
1. 队长创建团队
   - 填写团队名称（租户内唯一）
   - 设置最大成员数
   - 邀请成员（通过账号搜索）
2. 成员确认加入
   - 收到邀请通知
   - 确认/拒绝加入
3. 队长提交团队报名
   - 检查团队成员数量是否符合要求
   - 提交报名（所有成员状态：pending）
4. 管理员审核团队报名
   - 审核通过：所有成员状态改为 passed
   - 审核拒绝：所有成员状态改为 rejected

#### 4.4 数据表关系
- `t_contest_registration`：报名记录表
  - 个人赛：registration_type = 'individual'，team_id = null
  - 团队赛：registration_type = 'team'，team_id 关联 t_contest_team
- `t_contest_team`：团队表
- `t_contest_team_member`：团队成员表

#### 4.5 业务规则
- 报名时间限制：必须在 `register_start_time` 和 `register_end_time` 之间
- 报名状态流转：pending → passed/rejected/withdrawn
- 已通过的报名可以撤回（withdrawn），但需要检查是否已提交作品
- 团队名称在同一赛事、同一租户内唯一
- 团队成员角色：leader（队长）、member（队员）、mentor（指导教师）

---

### 5. 赛事作品提交

#### 5.1 功能概述
已报名的用户提交参赛作品，支持单次提交和多次提交（根据赛事配置）。

#### 5.2 交互流程
```
已报名用户 → 进入"我的赛事" → 选择赛事 → 点击"提交作品" → 上传作品文件 → 填写作品信息 → 提交
```

#### 5.3 作品提交表单
- 作品标题（必填）
- 作品说明（可选）
- 作品文件（支持多文件上传）
  - 图片、视频、3D模型等
  - 文件类型和大小限制
- 作品预览URL（可选，用于3D/视频预览）
- AI建模元数据（可选，JSON格式）

#### 5.4 提交规则

**单次提交（submit_rule = 'once'）：**
- 只能提交一次作品
- 提交后状态为 `submitted`，不可修改

**多次提交（submit_rule = 'resubmit'）：**
- 可以多次提交作品
- 每次提交创建新版本（version 递增）
- 旧版本 `is_latest = 0`，新版本 `is_latest = 1`
- 只有最新版本参与评审

#### 5.5 数据表关系
- `t_contest_work`：作品主表
  - entry_id 关联 t_contest_registration.id
  - files 字段存储简易文件列表（JSON）
- `t_contest_work_attachment`：作品附件表（详细文件信息）

#### 5.6 业务规则
- 提交时间限制：必须在 `submit_start_time` 和 `submit_end_time` 之间
- 必须已通过报名审核（registration_state = 'passed'）
- 作品编号（work_no）自动生成，格式：CONTEST-{contest_id}-{序号}
- 作品状态流转：
  - submitted（已提交）
  - locked（已锁定，不可修改）
  - reviewing（评审中）
  - rejected（已拒绝）
  - accepted（已接受）

---

### 6. 赛事作品评审

#### 6.1 功能概述
评委对提交的作品进行评分，支持多维度评分和评语。

#### 6.2 交互流程
```
评委登录 → 进入"评审管理" → 选择赛事 → 查看分配的作品列表 → 点击作品 → 查看作品详情 → 评分 → 提交评分
```

#### 6.3 评审流程设计

**6.3.1 作品分配**
- 需要新增表：`t_contest_work_judge_assignment`（作品分配表）
  - id, contest_id, work_id, judge_id, assignment_time, status
- 管理员或系统自动分配作品给评委
- 支持手动分配和自动分配（轮询、随机等）

**6.3.2 评分界面**
- 显示作品信息（标题、说明、文件、预览等）
- 显示评审规则（review_rule_id 关联的评审规则）
- 多维度评分表单
  - 根据评审规则动态生成评分维度
  - 每个维度设置分数范围
  - 总分自动计算（根据评审规则配置的权重）
- 评语输入框
- 提交评分按钮

**6.3.3 评分数据**
- `t_contest_work_score`：评分表
  - dimension_scores：JSON格式，存储各维度分数
  - total_score：总分（根据规则计算）
  - comments：评语

#### 6.4 评审规则设计
需要新增表：`t_contest_review_rule`（评审规则表）
- id, contest_id, rule_name, dimensions（JSON，存储评分维度配置）
- 示例维度配置：
  ```json
  {
    "dimension1": {
      "name": "创意性",
      "weight": 0.3,
      "maxScore": 100
    },
    "dimension2": {
      "name": "技术性",
      "weight": 0.4,
      "maxScore": 100
    },
    "dimension3": {
      "name": "完成度",
      "weight": 0.3,
      "maxScore": 100
    }
  }
  ```

#### 6.5 业务规则
- 评审时间限制：必须在 `review_start_time` 和 `review_end_time` 之间
- 作品状态更新：评审开始时，作品状态改为 `reviewing`
- 每个作品可以被多个评委评审
- 最终得分计算：取所有评委的平均分，或根据评审规则计算
- 评审完成后，作品状态改为 `accepted` 或 `rejected`

---

### 7. 赛事结果公布

#### 7.1 功能概述
管理员公布赛事评审结果，包括获奖名单、排名等。

#### 7.2 交互流程
```
管理员 → 进入"赛事管理" → 选择赛事 → 点击"公布结果" → 确认公布 → 结果发布
```

#### 7.3 结果公布内容
- 获奖名单（按奖项分类）
- 作品排名（按总分排序）
- 各维度平均分统计
- 评审统计信息（参与评审人数、作品数量等）

#### 7.4 业务规则
- 只有评审已完成的赛事可以公布结果
- 公布后，`result_publish_time` 设置为当前时间
- 结果公布后，所有用户可以看到排名和获奖信息
- 支持导出结果（Excel/PDF）

---

## 数据库设计分析

### 现有表结构

#### 1. t_contest（赛事表）
✅ **优点：**
- 字段设计完整，覆盖赛事全生命周期
- 支持多租户（contest_tenant 字段）
- 时间字段齐全

⚠️ **注意事项：**
- `contest_tenant` 使用 text 类型存储租户列表，建议考虑 JSON 类型或关联表
- `organizers`、`co_organizers`、`sponsors` 使用 text 存储数组，建议使用 JSON

#### 2. t_contest_attachment（赛事附件表）
✅ 设计合理，支持多种文件类型

#### 3. t_contest_work（作品表）
✅ **优点：**
- 支持版本控制（version、is_latest）
- 支持多种提交来源（teacher/student/team_leader）
- 支持 AI 建模元数据

⚠️ **问题：**
- 索引 `idx_submit_filter` 引用了不存在的字段 `review_status`，应删除或修正

#### 4. t_contest_work_attachment（作品附件表）
⚠️ **问题：**
- 第102行末尾有多余的逗号，需要删除

#### 5. t_contest_work_score（评分表）
⚠️ **问题：**
- 表定义重复（104-123行和125-144行），需要删除重复定义

#### 6. t_contest_registration（报名表）
✅ 设计合理，支持个人和团队报名

#### 7. t_contest_team（团队表）
⚠️ **问题：**
- 唯一索引 `uk_team_name` 引用了不存在的字段 `name`，应改为 `team_name`

#### 8. t_contest_team_member（团队成员表）
✅ 设计合理

### 缺失的表结构

#### 1. t_contest_notice（赛事公告表）
```sql
CREATE TABLE `t_contest_notice` (
  `id` varchar(63) NOT NULL COMMENT '主键id',
  `contest_id` varchar(63) NOT NULL COMMENT '赛事id',
  `title` varchar(255) NOT NULL COMMENT '公告标题',
  `content` text NOT NULL COMMENT '公告内容',
  `notice_type` varchar(31) NOT NULL DEFAULT 'manual' COMMENT '公告类型：system/manual/urgent',
  `priority` int DEFAULT 0 COMMENT '优先级（数字越大优先级越高）',
  `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
  `creator` varchar(63) NOT NULL DEFAULT '' COMMENT '创建人',
  `modifier` varchar(63) NOT NULL DEFAULT '' COMMENT '修改人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modify_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `valid_state` varchar(1) NOT NULL DEFAULT '1' COMMENT '有效状态（1-有效，2-失效）',
  PRIMARY KEY (`id`),
  KEY `idx_contest` (`contest_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛事公告表';
```

#### 2. t_contest_review_rule（评审规则表）
```sql
CREATE TABLE `t_contest_review_rule` (
  `id` varchar(63) NOT NULL COMMENT '主键id',
  `contest_id` varchar(63) NOT NULL COMMENT '赛事id',
  `rule_name` varchar(127) NOT NULL COMMENT '规则名称',
  `dimensions` json NOT NULL COMMENT '评分维度配置JSON',
  `calculation_rule` varchar(31) DEFAULT 'average' COMMENT '计算规则：average/max/min/weighted',
  `creator` varchar(63) NOT NULL DEFAULT '' COMMENT '创建人',
  `modifier` varchar(63) NOT NULL DEFAULT '' COMMENT '修改人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modify_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `valid_state` varchar(1) NOT NULL DEFAULT '1' COMMENT '有效状态（1-有效，2-失效）',
  PRIMARY KEY (`id`),
  KEY `idx_contest` (`contest_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评审规则表';
```

#### 3. t_contest_work_judge_assignment（作品分配表）
```sql
CREATE TABLE `t_contest_work_judge_assignment` (
  `id` varchar(63) NOT NULL COMMENT '主键id',
  `contest_id` varchar(63) NOT NULL COMMENT '赛事id',
  `work_id` varchar(63) NOT NULL COMMENT '作品id',
  `judge_id` varchar(63) NOT NULL COMMENT '评委用户id',
  `assignment_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '分配时间',
  `status` varchar(31) NOT NULL DEFAULT 'assigned' COMMENT '分配状态：assigned/reviewing/completed',
  `creator` varchar(63) NOT NULL DEFAULT '' COMMENT '创建人',
  `modifier` varchar(63) NOT NULL DEFAULT '' COMMENT '修改人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modify_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_work_judge` (`work_id`, `judge_id`),
  KEY `idx_contest_judge` (`contest_id`, `judge_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='作品分配表';
```

---

## 功能模块划分

### 后端模块结构

```
backend/src/contests/
├── contests.module.ts          # 赛事主模块
├── contests.controller.ts       # 赛事控制器
├── contests.service.ts         # 赛事服务
├── dto/
│   ├── create-contest.dto.ts
│   ├── update-contest.dto.ts
│   ├── query-contest.dto.ts
│   └── publish-contest.dto.ts
├── works/
│   ├── works.module.ts
│   ├── works.controller.ts
│   ├── works.service.ts
│   └── dto/
│       ├── create-work.dto.ts
│       ├── update-work.dto.ts
│       └── submit-work.dto.ts
├── registrations/
│   ├── registrations.module.ts
│   ├── registrations.controller.ts
│   ├── registrations.service.ts
│   └── dto/
│       ├── create-registration.dto.ts
│       ├── review-registration.dto.ts
│       └── create-team.dto.ts
├── teams/
│   ├── teams.module.ts
│   ├── teams.controller.ts
│   ├── teams.service.ts
│   └── dto/
│       ├── create-team.dto.ts
│       └── invite-member.dto.ts
├── reviews/
│   ├── reviews.module.ts
│   ├── reviews.controller.ts
│   ├── reviews.service.ts
│   └── dto/
│       ├── create-score.dto.ts
│       ├── assign-work.dto.ts
│       └── create-review-rule.dto.ts
└── notices/
    ├── notices.module.ts
    ├── notices.controller.ts
    ├── notices.service.ts
    └── dto/
        ├── create-notice.dto.ts
        └── update-notice.dto.ts
```

### 前端模块结构

```
frontend/src/views/contests/
├── Index.vue                    # 赛事列表页
├── Create.vue                   # 创建赛事页
├── Detail.vue                   # 赛事详情页
├── Edit.vue                     # 编辑赛事页
├── works/
│   ├── Index.vue                # 作品列表页
│   ├── Submit.vue               # 提交作品页
│   └── Detail.vue               # 作品详情页
├── registrations/
│   ├── Index.vue                # 报名列表页
│   ├── Register.vue            # 报名页
│   └── Review.vue              # 审核报名页
├── teams/
│   ├── Index.vue                # 团队列表页
│   ├── Create.vue               # 创建团队页
│   └── Detail.vue               # 团队详情页
├── reviews/
│   ├── Index.vue                # 评审列表页
│   ├── Score.vue                # 评分页
│   └── Results.vue              # 结果公布页
└── notices/
    ├── Index.vue                # 公告列表页
    └── Create.vue                # 创建公告页
```

---

## 实现计划

### 阶段一：基础功能（2-3周）

#### 1.1 数据库迁移
- [ ] 修复 SQL 文件中的错误
  - 删除 `t_contest_work_score` 重复定义
  - 修复 `t_contest_work_attachment` 的逗号错误
  - 修复 `t_contest_team` 索引字段名错误
  - 修复 `t_contest_work` 索引字段错误
- [ ] 创建缺失的表（公告表、评审规则表、作品分配表）
- [ ] 更新 Prisma schema
- [ ] 执行数据库迁移

#### 1.2 后端基础模块
- [ ] 创建 contests 模块（Controller、Service、DTO）
- [ ] 实现赛事 CRUD 接口
- [ ] 实现赛事发布/撤回接口
- [ ] 实现租户权限控制
- [ ] 编写单元测试

#### 1.3 前端基础页面
- [ ] 创建赛事列表页
- [ ] 创建赛事创建/编辑页
- [ ] 创建赛事详情页
- [ ] 集成权限控制
- [ ] 实现路由配置

### 阶段二：报名功能（2周）

#### 2.1 后端实现
- [ ] 创建 registrations 模块
- [ ] 实现个人报名接口
- [ ] 实现团队报名接口
- [ ] 实现报名审核接口
- [ ] 实现报名状态流转逻辑

#### 2.2 前端实现
- [ ] 创建报名页面
- [ ] 创建团队管理页面
- [ ] 创建报名审核页面
- [ ] 实现报名状态展示

### 阶段三：作品提交功能（2周）

#### 3.1 后端实现
- [ ] 创建 works 模块
- [ ] 实现作品提交接口
- [ ] 实现文件上传功能
- [ ] 实现作品版本控制逻辑
- [ ] 实现作品状态管理

#### 3.2 前端实现
- [ ] 创建作品提交页面
- [ ] 实现文件上传组件
- [ ] 创建作品列表页
- [ ] 创建作品详情页

### 阶段四：评审功能（2-3周）

#### 4.1 后端实现
- [ ] 创建 reviews 模块
- [ ] 实现评审规则管理接口
- [ ] 实现作品分配接口
- [ ] 实现评分接口
- [ ] 实现评分计算逻辑

#### 4.2 前端实现
- [ ] 创建评审规则配置页
- [ ] 创建作品分配页
- [ ] 创建评分页面
- [ ] 实现多维度评分表单

### 阶段五：结果公布与公告（1周）

#### 5.1 后端实现
- [ ] 创建 notices 模块
- [ ] 实现公告 CRUD 接口
- [ ] 实现结果公布接口
- [ ] 实现结果统计接口

#### 5.2 前端实现
- [ ] 创建公告管理页面
- [ ] 创建结果公布页面
- [ ] 实现结果展示页面

### 阶段六：优化与测试（1-2周）

#### 6.1 功能优化
- [ ] 性能优化（数据库查询优化、缓存）
- [ ] 用户体验优化
- [ ] 错误处理完善

#### 6.2 测试
- [ ] 单元测试
- [ ] 集成测试
- [ ] 端到端测试
- [ ] 压力测试

---

## 技术实现要点

### 1. 权限设计

#### 1.1 权限编码规划
```
contest:create      # 创建赛事
contest:read        # 查看赛事
contest:update      # 更新赛事
contest:delete      # 删除赛事
contest:publish     # 发布赛事
contest:register    # 报名赛事
work:submit         # 提交作品
work:read           # 查看作品
work:update         # 更新作品
review:assign       # 分配作品
review:score        # 评分
review:read         # 查看评审
result:publish      # 公布结果
notice:create       # 创建公告
notice:read         # 查看公告
```

#### 1.2 角色规划
- **超级管理员**：所有权限
- **赛事管理员**：contest:*、notice:*、result:publish
- **评委**：review:assign、review:score、review:read、work:read
- **参赛者**：contest:read、contest:register、work:submit、work:read

### 2. 租户隔离

#### 2.1 数据隔离策略
- 赛事创建：超级租户创建，通过 `contest_tenant` 字段控制可见范围
- 报名数据：通过 `tenant_key` 字段隔离
- 作品数据：通过 `tenant_key` 字段隔离
- 评审数据：通过 `tenant_key` 字段隔离

#### 2.2 接口权限控制
- 使用 `@TenantId()` 装饰器获取租户信息
- Service 层自动过滤租户数据
- Controller 层验证租户权限

### 3. 时间状态管理

#### 3.1 赛事状态机
```
unpublished → published → (可撤回) → unpublished
```

#### 3.2 报名状态机
```
pending → passed/rejected/withdrawn
```

#### 3.3 作品状态机
```
submitted → locked → reviewing → accepted/rejected
```

#### 3.4 定时任务
- 自动更新报名状态（根据时间）
- 自动更新作品提交状态
- 自动更新评审状态
- 自动发送通知（可选）

### 4. 文件上传

#### 4.1 文件存储
- 使用对象存储（OSS/S3）或本地存储
- 文件类型限制：图片、视频、3D模型、文档等
- 文件大小限制：根据文件类型设置

#### 4.2 文件管理
- 文件上传接口
- 文件删除接口
- 文件预览接口
- 文件下载接口

### 5. 通知系统

#### 5.1 通知类型
- 报名成功通知
- 报名审核结果通知
- 作品提交成功通知
- 评审结果通知
- 结果公布通知

#### 5.2 通知方式（可选）
- 站内消息
- 邮件通知
- 短信通知（可选）

### 6. 数据统计

#### 6.1 赛事统计
- 报名人数统计
- 作品提交数量统计
- 评审进度统计
- 结果统计

#### 6.2 报表导出
- 报名名单导出（Excel）
- 作品列表导出（Excel）
- 评审结果导出（Excel）
- 结果报告导出（PDF）

---

## 注意事项

### 1. SQL 文件问题修复
在开始开发前，必须先修复 SQL 文件中的错误：
- 删除重复的 `t_contest_work_score` 表定义
- 修复 `t_contest_work_attachment` 表的语法错误
- 修复 `t_contest_team` 表的索引错误
- 修复 `t_contest_work` 表的索引错误

### 2. 数据一致性
- 报名和作品的关系：一个报名可以对应多个作品版本
- 团队和报名的关系：一个团队对应多个报名记录（每个成员一条）
- 作品和评分的关系：一个作品可以有多条评分记录（多个评委）

### 3. 性能考虑
- 赛事列表查询需要分页
- 作品列表查询需要分页和筛选
- 评分计算需要缓存
- 文件上传需要异步处理

### 4. 安全性
- 文件上传需要验证文件类型和大小
- 接口需要权限验证
- 敏感操作需要日志记录
- 防止 SQL 注入和 XSS 攻击

---

## 总结

本方案基于现有的多租户 RBAC 系统架构，设计了完整的赛事管理功能模块。主要特点：

1. **完整的生命周期管理**：从赛事创建到结果公布的全流程
2. **灵活的参赛方式**：支持个人赛和团队赛
3. **强大的评审系统**：支持多维度评分和自定义评审规则
4. **良好的扩展性**：模块化设计，易于扩展新功能

建议按照阶段逐步实现，每个阶段完成后进行测试和优化，确保系统稳定可靠。

