运动会智能编排系统 - 完整详细需求规格说明书

运动会智能编排系统 - 完整详细需求规格说明书

文档版本:V5.0

更新日期:2026-01-20

状态:待开发


目录

  1. 项目概述
  2. 技术架构
  3. 用户角色与多端设计
  4. 功能需求详细
    • 4.1 系统配置模块
      • 4.1.1 基础规则配置
      • 4.1.2 号码簿规则配置
      • 4.1.3 编排规则配置
      • 4.1.4 Excel 列别名配置
      • 4.1.5 积分规则配置
    • 4.2 班级管理模块
    • 4.3 运动员管理模块
    • 4.4 项目管理模块(可自定义)
    • 4.5 报名管理模块
    • 4.6 编排算法模块
    • 4.7 成绩管理模块
    • 4.8 排名与积分模块
    • 4.9 统计报表模块
    • 4.10 Excel 导入导出模块
  5. 多端功能详细
  6. 非功能需求
  7. 数据模型详细
  8. [API 接口详细](#API 接口详细)
  9. 前端界面详细
  10. 私有部署方案
  11. 附录

1. 项目概述

1.1 项目背景

学校运动会组织过程中,涉及多个角色(体育老师、班主任、学生),存在以下痛点:

  • 体育老师:需要统一管理所有项目、编排、成绩统计
  • 班主任:需要为本班学生报名、查看本班成绩
  • 学生:希望查看自己的比赛安排和成绩
  • 数据分散,缺乏统一平台
  • 编排工作依赖人工,容易出现同班运动员在同一跑道、跨年级混排等问题

1.2 项目目标

开发一套完整的运动会智能编排系统,实现:

  • 多角色端:体育老师端(管理)、班主任端(报名)、学生端(查看)
  • 私有部署:学校内网部署,数据安全可控
  • 自定义项目:项目管理端可灵活配置比赛项目
  • 自动化编排:满足禁止跨年级、同班尽量不同道等约束
  • 全流程管理:报名→编排→成绩→排名→导出

1.3 项目范围

包含范围

  • 基础数据管理(班级、运动员、项目)
  • 报名管理(班主任报名、体育老师审核)
  • 智能编排(道次分配、分组)
  • 成绩录入与管理
  • 排名与积分统计
  • Excel 导入导出(支持别名映射)
  • 多端界面(体育老师端、班主任端、学生端)
  • 报表生成与导出

不包含范围

  • 电子计时硬件对接(预留接口)
  • 移动端 APP(提供响应式 Web)
  • 在线直播/实时成绩推送(后续版本)

2. 技术架构

2.1 整体架构图(多端 + 私有部署)

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                                   前端层                                      │
│  ┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐  │
│  │    体育老师端        │  │     班主任端         │  │     学生端(可选)   │  │
│  │  Vue 3 + Element    │  │  Vue 3 + Element    │  │  Vue 3 + Vant      │  │
│  │  管理后台风格        │  │  管理后台风格        │  │  移动端风格         │  │
│  └─────────────────────┘  └─────────────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      │ HTTPS/HTTP(内网)
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                            Nginx(反向代理 + 静态资源)                       │
└─────────────────────────────────────────────────────────────────────────────┘
                    │                           │
                    ▼                           ▼
┌───────────────────────────────┐ ┌───────────────────────────────────────────┐
│         FastAPI 网关           │ │              Django + DRF                 │
│  ┌─────────────────────────┐  │ │  ┌─────────────────────────────────────┐  │
│  │ 编排算法 API            │  │ │  │ 用户认证与权限(多角色)              │  │
│  │ - 道次编排              │  │ │  │ - JWT Token                         │  │
│  │ - 成绩处理              │  │ │  │ - RBAC(体育老师/班主任/学生)       │  │
│  │ - 排名计算              │  │ │  ├─────────────────────────────────────┤  │
│  │ - 实时预览              │  │ │  │ 数据管理 API                         │  │
│  └─────────────────────────┘  │ │  │ - 运动员 CRUD                        │  │
│                               │ │  │ - 项目 CRUD(可自定义)               │  │
│                               │ │  │ - 报名管理                           │  │
│                               │ │  │ - 成绩管理                           │  │
│                               │ │  ├─────────────────────────────────────┤  │
│                               │ │  │ Django Admin(内网管理)             │  │
│                               │ │  └─────────────────────────────────────┘  │
└───────────────────────────────┘ └───────────────────────────────────────────┘
                    │                           │
                    └───────────┬───────────────┘
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           数据层(私有部署)                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │              PostgreSQL / MySQL(推荐生产环境)                       │    │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │    │
│  │  │ 班级表  │ │运动员表 │ │ 项目表  │ │ 报名表  │ │编排结果表│       │    │
│  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘       │    │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │    │
│  │  │ 成绩表  │ │ 用户表  │ │角色权限表│ │ 配置表  │ │ 日志表  │       │    │
│  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘       │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                           文件存储                                    │    │
│  │  /data/uploads/   - 导入文件                                         │    │
│  │  /data/exports/   - 导出文件                                         │    │
│  │  /data/logs/      - 系统日志                                         │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 技术栈详细

后端

组件 技术 版本 说明
算法网关 FastAPI 0.115.0+ 异步高性能,自动生成API文档
管理后台 Django 4.2.0+ 完善的ORM和Admin
API框架 Django REST Framework 3.15.0+ RESTful API 开发
数据库 SQLite 3.0+ 开发/小规模默认
数据库 PostgreSQL 15+ 生产环境推荐
数据库 MySQL 8.0+ 备选方案
数据验证 Pydantic 2.5.0+ FastAPI数据验证
算法 OR-Tools 9.10+ 约束求解(可选)
Excel处理 pandas 2.0.0+ 数据读写、清洗
Excel处理 numpy 1.24.0+ 数值计算
Excel写入 openpyxl 3.1.0+ Excel 文件写入
异步任务 Celery 5.3.0+ 耗时任务处理(可选)
缓存 Redis 7.0+ 会话缓存(可选)

前端

组件 技术 版本 说明
框架 Vue 3 3.4.0+ 组合式API
构建工具 Vite 5.0.0+ 快速冷启动,懒加载
UI组件库(管理端) Element Plus 2.6.0+ 美观后台组件
UI组件库(学生端) Vant 4.8.0+ 移动端组件
路由 Vue Router 4.2.0+ 支持路由懒加载
状态管理 Pinia 2.1.0+ 轻量状态管理
HTTP客户端 Axios 1.6.0+ 请求拦截、响应处理
图表 ECharts 5.5.0+ 统计图表展示
日期处理 dayjs 1.11.0+ 轻量日期库

部署

组件 技术 说明
容器化 Docker 应用容器化
容器编排 Docker Compose 多服务编排
Web服务器 Nginx 静态资源、反向代理
进程管理 Gunicorn Django WSGI服务器
异步服务器 Uvicorn FastAPI ASGI服务器

2.3 私有部署架构特点

特性 说明
内网部署 部署在学校内部服务器,数据不外传
单机部署 一台服务器即可运行所有服务
Docker 容器化 一键部署,环境隔离
数据备份 支持自动备份到本地存储
离线运行 无需互联网连接
支持 HTTPS 可配置 SSL 证书

3. 用户角色与多端设计

3.1 角色定义

角色 标识 说明 使用端 典型用户
超级管理员 super_admin 系统全部权限 体育老师端 信息技术老师
体育老师 teacher 编排、成绩、报表 体育老师端 体育教研组
班主任 class_teacher 本班报名、查看成绩 班主任端 各班班主任
学生 student 查看个人赛程和成绩 学生端(可选) 参赛学生
查看者 viewer 仅查看(预留) Web 其他教职工

3.2 多端功能对比

功能模块 体育老师端 班主任端 学生端
系统配置
基础规则配置
项目管理(自定义) ❌(只读) ❌(只读)
号码簿规则配置
编排规则配置
Excel别名配置
积分规则配置
用户管理
班级/运动员管理
班级管理
运动员管理(全校)
运动员管理(本班)
报名管理
查看可报项目
本班学生报名
个人报名 ❌(可选)
报名审核
导出班级报名表
导出各项目报名表
编排管理
执行编排
查看道次表
手动调整
导出道次表
成绩管理
成绩录入
查看本班成绩
查看个人成绩
排名积分
查看排名榜
查看团体总分
统计报表
报名统计 ✅(本班)
成绩统计 ✅(本班) ✅(个人)
导出报表 ✅(本班)

3.3 登录界面设计

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│                           🏃 运动会智能编排系统                               │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                                                                      │    │
│  │    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐            │    │
│  │    │  体育老师端  │    │  班主任端   │    │  学生端     │            │    │
│  │    │  (管理端)   │    │  (报名端)   │    │  (查看端)   │            │    │
│  │    └─────────────┘    └─────────────┘    └─────────────┘            │    │
│  │                                                                      │    │
│  │    账号:[________________]                                          │    │
│  │    密码:[________________]                                          │    │
│  │                                                                      │    │
│  │    [登录]   [忘记密码]                                               │    │
│  │                                                                      │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  私有部署版本 v1.0  |  学校内部系统                                           │
└─────────────────────────────────────────────────────────────────────────────┘

4. 功能需求详细

4.1 系统配置模块

4.1.1 基础规则配置

需求描述

配置运动会的基础参数,这些参数将影响整个系统的运行逻辑。

配置项详细
配置项 类型 默认值 说明 取值范围
全局跑道数 整数 8 项目默认跑道数 1-12
每班每项目最大人数 整数 3 报名人数上限 0-10
每名运动员最大报项数 整数 3 个人报项上限 1-8
短跑距离阈值 整数 400 小于此值为短跑 100-800
成绩小数位数 整数 2 成绩显示精度 0-3
报名截止时间 日期时间 - 班主任报名截止 -
是否允许学生自行报名 布尔 学生端报名开关 是/否
是否开放学生端 布尔 学生端功能开关 是/否
短跑成绩格式 字符串 ss.xx 秒.毫秒 -
长跑成绩格式 字符串 mm:ss.xx 分:秒.毫秒 -
是否自动编排 布尔 报名完成后自动编排 是/否
是否允许手动调整编排 布尔 编排后允许手动调整 是/否
默认日期 日期 当前日期 运动会举办日期 -
运动会名称 字符串 第X届运动会 显示在报表上 -
配置界面原型
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 系统基础配置                                                        [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 基本信息                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │ 运动会名称:   [第35届秋季田径运动会________________]               │     │
│ │ 举办日期:     [2026-10-15    ] 至 [2026-10-17    ]                │     │
│ │ 举办地点:     [学校田径场______________________]                   │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 全局配置                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │ 全局默认跑道数:   [8] ▼         道                                  │     │
│ │ 每班每项目最多人数:[3] ▼         人                                  │     │
│ │ 每名运动员最多报项数:[3] ▼       项                                  │     │
│ │ 短跑距离阈值:     [400] ▼        米(含以下为短跑)                  │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 成绩格式配置                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │ 成绩小数位数:   [2] ▼             位                                │     │
│ │ 短跑成绩示例:   12.34 秒                                           │     │
│ │ 长跑成绩示例:   2:35.67(分:秒.毫秒)                               │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 报名与编排配置                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │ 报名截止时间:   [2026-10-01 23:59:59]                              │     │
│ │ ☑ 是否允许学生自行报名(需开启学生端)                               │     │
│ │ ☑ 是否开放学生端                                                    │     │
│ │ ☐ 报名完成后自动编排                                                │     │
│ │ ☑ 允许手动调整编排结果                                              │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [恢复默认] [保存配置]                                                         │
└─────────────────────────────────────────────────────────────────────────────┘

4.1.2 号码簿规则配置

需求描述

支持自定义号码簿生成规则,系统自动为运动员生成唯一号码簿。号码簿是运动员的唯一标识,在整个运动会中不可重复。

规则变量定义
变量 说明 示例值 格式选项
{grade} 年级编号 1, 2, 3 可映射(一年级→1)
{grade_name} 年级名称 一年级, 二年级 直接使用名称
{class} 班级编号 1, 2, 3 可映射
{class_name} 班级名称 1班, 2班 直接使用名称
{seq} 序号 1, 2, 3 可格式化
{seq:02d} 2位序号 01, 02, 03 补零
{seq:03d} 3位序号 001, 002 补零
{seq:04d} 4位序号 0001, 0002 补零
{gender} 性别代码 M, F 映射
{gender_cn} 性别中文 男, 女 中文
{year} 年份后两位 24, 25, 26 当前年份
{school_code} 学校代码 001 固定前缀
规则模板库
模板名称 规则公式 示例输出 适用场景
年级+班级+序号 {grade}{class}{seq:02d} 1101 最常用
班级+序号 {class}{seq:02d} 101 不分年级
年级+序号 {grade}{seq:03d} 1001 班级不区分
纯序号 {seq:04d} 0001 全校统一编号
年级+班级+性别+序号 {grade}{class}{gender}{seq:02d} 11M01 区分性别
年份+班级+序号 {year}{class}{seq:02d} 24101 按年份区分
学校码+年级+班级+序号 {school_code}{grade}{class}{seq:02d} 0011101 多学校联合
年级/班级映射配置

用户可以自定义年级和班级到数字的映射关系:

原始值 映射值 说明
一年级 1 映射为数字1
二年级 2 映射为数字2
三年级 3 映射为数字3
四年级 4 映射为数字4
五年级 5 映射为数字5
六年级 6 映射为数字6
初一 1 映射为数字1
初二 2 映射为数字2
初三 3 映射为数字3
高一 1 映射为数字1
高二 2 映射为数字2
高三 3 映射为数字3

班级映射:用户可自定义,如"1班"→"01","2班"→"02"

规则配置界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 号码簿规则配置                                                      [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 规则模板选择                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │     │
│ │ │年级+班级+序号│ │ 班级+序号   │ │ 年级+序号   │ │  纯序号    │    │     │
│ │ │   (推荐)    │ │            │ │            │ │            │    │     │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘    │     │
│ │                                                                      │     │
│ │ ● 自定义公式                                                        │     │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │     │
│ │ │ {grade}{class}{seq:02d}                                          │ │     │
│ │ └─────────────────────────────────────────────────────────────────┘ │     │
│ │                                                                      │     │
│ │ 可用变量:                                                          │     │
│ │ [grade] [grade_name] [class] [class_name] [seq] [gender] [year]     │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 预览示例                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 年级:一年级 → 1    班级:1班 → 1    序号:1 → 01                    │     │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │     │
│ │ │ 生成号码簿:1101                                                 │ │     │
│ │ └─────────────────────────────────────────────────────────────────┘ │     │
│ │                                                                      │     │
│ │ 更多预览:                                                          │     │
│ │ 一年级1班 张三 → 1101                                               │     │
│ │ 一年级1班 李四 → 1102                                               │     │
│ │ 一年级2班 王五 → 1201                                               │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 年级映射配置                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ┌──────────────┬──────────┬─────────────────────────────┐          │     │
│ │ │ 年级名称      │ 映射值    │ 操作                         │          │     │
│ │ ├──────────────┼──────────┼─────────────────────────────┤          │     │
│ │ │ 一年级        │ [1    ]  │ 编辑                         │          │     │
│ │ │ 二年级        │ [2    ]  │ 编辑                         │          │     │
│ │ │ 三年级        │ [3    ]  │ 编辑                         │          │     │
│ │ │ 四年级        │ [4    ]  │ 编辑                         │          │     │
│ │ │ 五年级        │ [5    ]  │ 编辑                         │          │     │
│ │ │ 六年级        │ [6    ]  │ 编辑                         │          │     │
│ │ │ 初一年级       │ [7    ]  │ 编辑                         │          │     │
│ │ │ 初二年级       │ [8    ]  │ 编辑                         │          │     │
│ │ │ 初三年级       │ [9    ]  │ 编辑                         │          │     │
│ │ │ 高一年级       │ [10   ]  │ 编辑                         │          │     │
│ │ │ 高二年级       │ [11   ]  │ 编辑                         │          │     │
│ │ │ 高三年级       │ [12   ]  │ 编辑                         │          │     │
│ │ └──────────────┴──────────┴─────────────────────────────┘          │     │
│ │                                                                      │     │
│ │ [+ 添加自定义年级]                                                   │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 班级映射配置                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 规则类型:                                                          │     │
│ │ ● 自动提取数字(1班 → 1,2班 → 2)                                   │     │
│ │ ○ 自定义映射                                                        │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 高级选项                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ☑ 自动补零(序号不足位数时补零)                                     │     │
│ │ ☑ 全局唯一性校验(号码簿不可重复)                                   │     │
│ │ ☑ 生成后允许手动修改                                                │     │
│ │ ☑ 导入时自动生成缺失的号码簿                                        │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [重置默认] [测试生成] [保存配置]                                             │
└─────────────────────────────────────────────────────────────────────────────┘
号码簿生成算法
python 复制代码
def generate_number(grade, class_name, seq, config):
    """
    根据配置规则生成号码簿
    
    Args:
        grade: 年级名称,如"一年级"
        class_name: 班级名称,如"1班"
        seq: 序号,如1
        config: 规则配置 {
            "template": "{grade}{class}{seq:02d}",
            "grade_mapping": {"一年级": 1, "二年级": 2, ...},
            "class_mapping": {"1班": 1, "2班": 2, ...}  # 可选
        }
    
    Returns:
        号码簿字符串
    """
    # 获取年级映射值
    grade_value = config['grade_mapping'].get(grade, grade)
    
    # 获取班级映射值
    if config.get('class_mapping'):
        class_value = config['class_mapping'].get(class_name, class_name)
    else:
        # 自动提取数字
        import re
        numbers = re.findall(r'\d+', class_name)
        class_value = numbers[0] if numbers else class_name
    
    # 格式化序号
    seq_formatted = seq
    
    # 应用模板
    template = config['template']
    result = template.format(
        grade=grade_value,
        class=class_value,
        seq=seq_formatted
    )
    
    return result

4.1.3 编排规则配置

需求描述

配置编排算法的约束条件,包括硬约束(必须遵守)和软约束(可配置开关)。

硬约束(不可违反)
规则 说明 违反时处理
禁止跨年级 同一组/跑道的运动员必须同年级 编排失败,提示用户
性别分离 男女生项目完全分开编排 编排失败,提示用户
软约束(可配置开关)
规则 选项 默认值 优先级 说明
同班是否允许同跑道 禁止/允许 禁止 禁止:同班不得在同一跑道;允许:优先尝试不同道,无法满足时允许
同班是否尽量不同组 尽量/无限制 尽量 尽量:同一班级运动员分散到不同轮次/组别
同班是否尽量不同道次 尽量/无限制 尽量 尽量:同一班级运动员分配不同道次
同年级是否打散 是/否 同年级不同班尽量分散
成绩优秀者是否居中 是/否 按历史成绩安排中间道次
编排规则配置界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 编排规则配置                                                        [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 硬约束(不可违反,系统强制执行)                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ☑ 禁止跨年级编排                                                    │     │
│ │   (同一组/跑道的运动员必须同年级)                                  │     │
│ │                                                                      │     │
│ │ ☑ 性别分离                                                          │     │
│ │   (男子项目和女子项目完全分开编排)                                 │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 软约束(可配置,系统优先尝试满足)                                    │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 1. 同班跑道规则:                                                   │     │
│ │                                                                      │     │
│ │    ● 禁止同班同跑道(推荐)                                         │     │
│ │       说明:同一班级的运动员不得出现在同一跑道                        │     │
│ │                                                                      │     │
│ │    ○ 允许同班同跑道                                                 │     │
│ │       说明:优先尝试不同跑道,无法满足时允许同跑道                    │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 2. 同班组别规则:                                                   │     │
│ │                                                                      │     │
│ │    ● 尽量分散到不同组                                               │     │
│ │       说明:同一班级运动员优先安排到不同轮次/组别                     │     │
│ │                                                                      │     │
│ │    ○ 无限制                                                         │     │
│ │       说明:不主动分散同一班级运动员                                 │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 3. 同班道次规则:                                                   │     │
│ │                                                                      │     │
│ │    ● 尽量分配不同道次                                               │     │
│ │       说明:同一班级运动员尽量不在同一道次                            │     │
│ │                                                                      │     │
│ │    ○ 无限制                                                         │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 4. 同年级打散:                                                     │     │
│ │                                                                      │     │
│ │    ○ 是  ● 否                                                       │     │
│ │       说明:是否将同年级不同班级的运动员打散                          │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 5. 优秀运动员居中(需历史成绩):                                    │     │
│ │                                                                      │     │
│ │    ○ 是  ● 否                                                       │     │
│ │       说明:成绩优秀的运动员安排在中间道次(3-6道)                   │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 编排参数                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 最大尝试次数:   [1000] 次                                           │     │
│ │ (当无法满足约束时,算法尝试的最大次数)                              │     │
│ │                                                                      │     │
│ │ 编排超时时间:   [30] 秒                                             │     │
│ │ (编排算法最大运行时间)                                             │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [恢复默认] [测试编排] [保存配置]                                              │
└─────────────────────────────────────────────────────────────────────────────┘
编排规则数据结构
python 复制代码
# 编排规则配置数据结构
ARRANGE_RULE_CONFIG = {
    # 硬约束
    "hard_constraints": {
        "ban_cross_grade": True,      # 禁止跨年级
        "gender_separate": True,       # 性别分离
    },
    # 软约束
    "soft_constraints": {
        "ban_same_class_same_lane": True,   # 禁止同班同跑道
        "prefer_diff_heat": True,           # 尽量不同组
        "prefer_diff_lane": True,           # 尽量不同道次
        "scramble_across_classes": False,   # 同年级打散
        "center_best_athletes": False,      # 优秀运动员居中
    },
    # 算法参数
    "algorithm_params": {
        "max_attempts": 1000,        # 最大尝试次数
        "timeout_seconds": 30,       # 超时时间(秒)
    }
}

4.1.4 Excel 列别名配置

需求描述

用户上传的 Excel 文件列名可能与系统标准字段不一致,支持用户配置列名映射,无需修改模板即可导入。

标准字段与默认别名
标准字段 必填 数据类型 默认别名(逗号分隔)
姓名 字符串 姓名, 名字, name, 运动员, 运动员名称
性别 枚举 性别, sex, gender, 男女
年级 字符串 年级, grade, 年段
班级 字符串 班级, class, 班别, 班级名称, 班
号码簿 字符串 号码簿, 号码布, 号码, number, 参赛号, 编号, 运动员编号
参赛项目 字符串 参赛项目, 项目, event, 报名项目, 项目名称
成绩 字符串 成绩, 时间, result, time, 比赛成绩, 用时
跑道 整数 跑道, lane, 道次
组别 整数 组别, heat, 组, 轮次
名次 整数 名次, rank, 排名
积分 整数 积分, score, point
班级编号 字符串 班级编号, class_code
学号 字符串 学号, student_id, 学籍号
联系电话 字符串 联系电话, 电话, phone, mobile
别名配置界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ Excel 列别名配置                                                    [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 列名映射配置                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ┌────────────┬────────────────────────────────────┬────────┐        │     │
│ │ │ 标准字段    │ 别名(多个用英文逗号分隔)          │ 必填   │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 姓名 *     │ [姓名, 名字, name, 运动员名称    ]  │ ✅     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 性别 *     │ [性别, sex, gender, 男女         ]  │ ✅     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 年级 *     │ [年级, grade, 年段               ]  │ ✅     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 班级 *     │ [班级, class, 班别, 班级名称, 班]  │ ✅     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 号码簿 *   │ [号码簿, 号码布, 号码, number, 参赛号]│ ✅   │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 参赛项目   │ [参赛项目, 项目, event, 报名项目]  │ ❌     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 成绩       │ [成绩, 时间, result, time, 比赛成绩]│ ❌     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 跑道       │ [跑道, lane, 道次                ]  │ ❌     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 组别       │ [组别, heat, 组, 轮次            ]  │ ❌     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 名次       │ [名次, rank, 排名                ]  │ ❌     │        │     │
│ │ ├────────────┼────────────────────────────────────┼────────┤        │     │
│ │ │ 积分       │ [积分, score, point              ]  │ ❌     │        │     │
│ │ └────────────┴────────────────────────────────────┴────────┘        │     │
│ │                                                                      │     │
│ │ [+ 添加自定义字段]                                                   │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 高级选项                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ☑ 启用智能列名匹配(自动识别常见别名)                               │     │
│ │ ☑ 导入时显示列名映射预览                                            │     │
│ │ ☑ 保存用户自定义映射(不同用户独立)                                 │     │
│ │ ☐ 区分大小写                                                        │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [重置默认] [测试导入] [保存配置]                                              │
└─────────────────────────────────────────────────────────────────────────────┘
列名映射算法
python 复制代码
class ColumnMapper:
    """Excel 列名映射器"""
    
    def __init__(self, alias_config: Dict[str, list]):
        self.alias_config = alias_config
        self.reverse_index = self._build_reverse_index()
    
    def _build_reverse_index(self) -> Dict[str, str]:
        """构建别名 → 标准字段 反向索引"""
        reverse = {}
        for standard_field, aliases in self.alias_config.items():
            for alias in aliases:
                # 标准化别名(小写,去空格)
                normalized = alias.lower().strip()
                reverse[normalized] = standard_field
        return reverse
    
    def detect_columns(self, excel_columns: list) -> Dict[str, str]:
        """
        检测 Excel 列名对应关系
        
        Args:
            excel_columns: Excel 文件中的列名列表
            
        Returns:
            {标准字段: 实际列名} 的映射字典
        """
        mapping = {}
        for col in excel_columns:
            normalized = col.lower().strip()
            if normalized in self.reverse_index:
                standard = self.reverse_index[normalized]
                mapping[standard] = col
        
        return mapping
    
    def map_dataframe(self, df: pd.DataFrame, user_mapping: Optional[Dict] = None) -> pd.DataFrame:
        """
        根据映射重命名 DataFrame 列
        
        Args:
            df: 原始 DataFrame
            user_mapping: 用户手动确认的映射 {标准字段: 实际列名}
            
        Returns:
            重命名后的 DataFrame
        """
        if user_mapping:
            # 使用用户手动确认的映射
            rename_dict = {actual: standard for standard, actual in user_mapping.items()}
            df = df.rename(columns=rename_dict)
        else:
            # 自动识别
            detected = self.detect_columns(df.columns.tolist())
            rename_dict = {actual: standard for standard, actual in detected.items()}
            df = df.rename(columns=rename_dict)
        
        return df
    
    def get_unmatched_columns(self, excel_columns: list) -> list:
        """获取未匹配的列名"""
        unmatched = []
        for col in excel_columns:
            normalized = col.lower().strip()
            if normalized not in self.reverse_index:
                unmatched.append(col)
        return unmatched
    
    def get_missing_required_fields(self, excel_columns: list, required_fields: list) -> list:
        """获取缺失的必填字段"""
        detected = self.detect_columns(excel_columns)
        missing = [field for field in required_fields if field not in detected]
        return missing
导入时列名识别流程
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 导入时列名识别流程                                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤1:用户上传 Excel 文件                                           │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤2:系统读取 Excel 列名                                           │    │
│  │ 实际列名:["姓名", "性别", "年级", "班级", "号码布", "项目"]          │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤3:智能匹配                                                      │    │
│  │                                                                      │    │
│  │   "姓名" → 匹配到 "姓名"                                             │    │
│  │   "性别" → 匹配到 "性别"                                             │    │
│  │   "年级" → 匹配到 "年级"                                             │    │
│  │   "班级" → 匹配到 "班级"                                             │    │
│  │   "号码布" → 匹配到 "号码簿"(别名包含"号码布")                      │    │
│  │   "项目" → 匹配到 "参赛项目"(别名包含"项目")                        │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤4:展示匹配结果预览(用户确认)                                   │    │
│  │                                                                      │    │
│  │ ┌────────────┬──────────────┬────────┐                             │    │
│  │ │ 标准字段    │ 识别到的列名   │ 状态   │                             │    │
│  │ ├────────────┼──────────────┼────────┤                             │    │
│  │ │ 姓名       │ 姓名          │ ✅ 已匹配 │                            │    │
│  │ │ 性别       │ 性别          │ ✅ 已匹配 │                            │    │
│  │ │ 年级       │ 年级          │ ✅ 已匹配 │                            │    │
│  │ │ 班级       │ 班级          │ ✅ 已匹配 │                            │    │
│  │ │ 号码簿     │ 号码布        │ ✅ 已匹配 │                            │    │
│  │ │ 参赛项目   │ 项目          │ ✅ 已匹配 │                            │    │
│  │ └────────────┴──────────────┴────────┘                             │    │
│  │                                                                      │    │
│  │ [确认导入] [修改映射] [取消]                                         │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

4.1.5 积分规则配置

需求描述

配置比赛项目的积分规则,用于计算运动员个人积分和班级团体总分。

默认积分规则
名次 积分 说明
第1名 9 冠军
第2名 7 亚军
第3名 6 季军
第4名 5 第四名
第5名 4 第五名
第6名 3 第六名
第7名 2 第七名
第8名 1 第八名
8名以后 0 无积分
积分规则配置界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 积分规则配置                                                        [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 名次积分对照表                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ┌──────────────┬──────────┬─────────────────────────────┐          │     │
│ │ │ 名次         │ 积分      │ 操作                         │          │     │
│ │ ├──────────────┼──────────┼─────────────────────────────┤          │     │
│ │ │ 第1名        │ [9    ]  │ [删除]                       │          │     │
│ │ │ 第2名        │ [7    ]  │ [删除]                       │          │     │
│ │ │ 第3名        │ [6    ]  │ [删除]                       │          │     │
│ │ │ 第4名        │ [5    ]  │ [删除]                       │          │     │
│ │ │ 第5名        │ [4    ]  │ [删除]                       │          │     │
│ │ │ 第6名        │ [3    ]  │ [删除]                       │          │     │
│ │ │ 第7名        │ [2    ]  │ [删除]                       │          │     │
│ │ │ 第8名        │ [1    ]  │ [删除]                       │          │     │
│ │ └──────────────┴──────────┴─────────────────────────────┘          │     │
│ │                                                                      │     │
│ │ [+ 添加名次积分]                                                     │     │
│ │                                                                      │     │
│ │ 💡 提示:名次支持范围输入,如 "1-8" 表示第1到第8名                    │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 特殊规则                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ☑ 成绩并列时名次相同,积分相同                                       │     │
│ │   (例如:两名并列第1名,都获得第1名积分)                            │     │
│ │                                                                      │     │
│ │ ☑ 成绩并列时后续名次顺延                                             │     │
│ │   (例如:1,1,3,4 - 两个第1名后下一个是第3名)                       │     │
│ │                                                                      │     │
│ │ ☐ 成绩并列时后续名次不顺延                                          │     │
│ │   (例如:1,1,2,3 - 两个第1名后下一个是第2名)                       │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 破纪录加分:                                                         │     │
│ │                                                                      │     │
│ │ ☑ 启用破纪录加分                                                    │     │
│ │ 破纪录加分分值:[10] 分                                              │     │
│ │ 破纪录标准:   ○ 打破校纪录  ○ 打破年级纪录  ● 打破运动会纪录        │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 参与分:                                                             │     │
│ │                                                                      │     │
│ │ ☐ 启用参与分                                                        │     │
│ │ 参与分分值:[1] 分                                                   │     │
│ │ 说明:所有参赛且未获奖的运动员获得基础分                             │     │
│ │                                                                      │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 接力项目规则:                                                       │     │
│ │                                                                      │     │
│ │ 接力项目积分倍数:[2] 倍                                             │     │
│ │ 说明:接力项目的积分按此倍数计算                                      │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 团体总分规则                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 团体总分计算方式:                                                   │     │
│ │ ● 按班级汇总                                                        │     │
│ │ ○ 按年级汇总                                                        │     │
│ │                                                                      │     │
│ │ 团体总分排序方式:                                                   │     │
│ │ ● 按总分降序(分高者在前)                                           │     │
│ │ ○ 按金牌数优先                                                      │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [恢复默认] [保存配置]                                                         │
└─────────────────────────────────────────────────────────────────────────────┘
积分计算算法
python 复制代码
def calculate_scores(results_df, scoring_rules, event_type='individual'):
    """
    计算运动员积分
    
    Args:
        results_df: 包含 athlete_id, rank, event_id 的 DataFrame
        scoring_rules: 积分规则配置
        event_type: 项目类型(individual/relay)
    
    Returns:
        添加了积分的 DataFrame
    """
    # 获取名次积分映射
    rank_scores = scoring_rules.get('rank_scores', {})
    # 默认积分映射
    default_scores = {1: 9, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1}
    rank_scores = {**default_scores, **rank_scores}
    
    # 计算积分
    scores = []
    for _, row in results_df.iterrows():
        rank = row['rank']
        score = rank_scores.get(rank, 0)
        
        # 接力项目倍数
        if event_type == 'relay':
            multiplier = scoring_rules.get('relay_multiplier', 2)
            score = score * multiplier
        
        # 参与分
        if scoring_rules.get('participation_score_enabled', False) and score == 0:
            score = scoring_rules.get('participation_score', 1)
        
        scores.append(score)
    
    results_df['score'] = scores
    return results_df


def calculate_team_scores(individual_scores_df, scoring_rules):
    """
    计算团体总分
    
    Args:
        individual_scores_df: 个人积分数据
        scoring_rules: 积分规则配置
    
    Returns:
        团体总分 DataFrame
    """
    # 按班级汇总
    team_scores = individual_scores_df.groupby(['grade', 'class'])['score'].sum().reset_index()
    team_scores = team_scores.sort_values('score', ascending=False)
    team_scores['rank'] = range(1, len(team_scores) + 1)
    
    return team_scores

4.2 班级管理模块

4.2.1 需求描述

班级是运动会组织的基本单位,班主任负责本班的报名工作。班级管理模块需要支持年级分组、班级排序、班主任账号关联等功能。

4.2.2 班级信息字段
字段名 类型 必填 唯一 说明 示例
班级名称 字符串 班级显示名称 高一1班
年级 字符串 所属年级 高一年级
年级排序 整数 年级显示顺序 10
班级排序 整数 班级内显示顺序 1
班级编号 字符串 用于号码簿生成 101
班主任姓名 字符串 班主任姓名 张三
班主任账号 外键 关联Django用户 zhangsan
联系电话 字符串 班主任电话 138****0000
学生人数 整数 班级总人数 45
是否参赛 布尔 是否参与本届运动会
备注 文本 其他说明 -
4.2.3 班级管理功能列表
功能 说明 权限 技术实现
班级列表 按年级分组展示,支持搜索和筛选 体育老师/班主任(仅本班) Vue3 + Element Plus Table
新增班级 单个添加班级 体育老师 Django REST API
批量导入 Excel批量导入班级 体育老师 pandas读取Excel
编辑班级 修改班级信息 体育老师 PUT请求
删除班级 删除班级(有运动员时禁止) 体育老师 DELETE请求
排序调整 拖拽调整班级显示顺序 体育老师 拖拽组件 + 批量更新
导出班级 导出班级列表为Excel 体育老师 pandas导出
班主任账号管理 创建/重置班主任登录账号 体育老师 Django用户管理
4.2.4 班级列表界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 班级管理                                              [+ 新增] [批量导入]   │
├─────────────────────────────────────────────────────────────────────────────┤
│ 搜索:[________________] [搜索] [导出]                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ▼ 高一年级(6个班)                                    [拖拽排序]           │
│ ┌─────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 排序 │ 班级名称  │ 班级编号  │ 班主任   │ 班主任账号│ 运动员数 │ 操作     │ │
│ ├─────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ 1   │ 高一1班  │ 101      │ 张三     │ zhangsan │ 12       │ 编辑 删除 │ │
│ │ 2   │ 高一2班  │ 102      │ 李四     │ lisi     │ 10       │ 编辑 删除 │ │
│ │ 3   │ 高一3班  │ 103      │ 王五     │ wangwu   │ 11       │ 编辑 删除 │ │
│ │ 4   │ 高一4班  │ 104      │ 赵六     │ zhaoliu  │ 9        │ 编辑 删除 │ │
│ │ 5   │ 高一5班  │ 105      │ 钱七     │ [创建]   │ 0        │ 编辑 删除 │ │
│ │ 6   │ 高一6班  │ 106      │ 孙八     │ [创建]   │ 0        │ 编辑 删除 │ │
│ └─────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ │
│                                                                              │
│ ▼ 高二年级(5个班)                                                          │
│ ┌─────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 1   │ 高二1班  │ 201      │ 周九     │ zhoujiu  │ 15       │ 编辑 删除 │ │
│ │ 2   │ 高二2班  │ 202      │ 吴十     │ wushi    │ 13       │ 编辑 删除 │ │
│ │ ...                                                                       │ │
│ └─────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ │
│                                                                              │
│ 共 11 条记录                                                                 │
└─────────────────────────────────────────────────────────────────────────────┘
4.2.5 新增/编辑班级表单
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 新增班级                                                            [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 班级基本信息                                                        │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 班级名称:   [高一7班________________] *                             │     │
│ │                                                                      │     │
│ │ 所属年级:   [高一年级 ▼] *                                          │     │
│ │                                                                      │     │
│ │ 班级编号:   [107________________]                                   │     │
│ │             用于号码簿生成,建议使用数字                              │     │
│ │                                                                      │     │
│ │ 年级排序:   [10________________]                                    │     │
│ │             数字越小越靠前                                           │     │
│ │                                                                      │     │
│ │ 班级排序:   [7_________________]                                    │     │
│ │             同年级内数字越小越靠前                                   │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 班主任信息                                                          │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 班主任姓名: [赵老师________________] *                              │     │
│ │                                                                      │     │
│ │ 联系电话:   [13812345678____________]                               │     │
│ │                                                                      │     │
│ │ 班主任账号: [zhaolaoshi_____________]                               │     │
│ │             ☑ 自动创建登录账号,密码默认:123456                     │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 其他设置                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ☑ 是否参赛                                                          │     │
│ │                                                                      │     │
│ │ 备注:       [________________________]                              │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│                                                    [取消] [保存]            │
└─────────────────────────────────────────────────────────────────────────────┘
4.2.6 班主任账号管理
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 班主任账号管理                                                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌────┬──────────┬──────────┬──────────┬──────────┬──────────┐             │
│ │ 序号 │ 班级名称  │ 班主任   │ 登录账号  │ 账号状态  │ 操作     │             │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┤             │
│ │ 1  │ 高一1班  │ 张三     │ zhangsan │ ✅ 已激活 │ 重置密码 │             │
│ │ 2  │ 高一2班  │ 李四     │ lisi     │ ✅ 已激活 │ 重置密码 │             │
│ │ 3  │ 高一3班  │ 王五     │ wangwu   │ ⚠ 未登录 │ 重置密码 │             │
│ │ 4  │ 高一4班  │ 赵六     │ zhaoliu  │ 🔒 已锁定 │ 解锁账号 │             │
│ └────┴──────────┴──────────┴──────────┴──────────┴──────────┘             │
│                                                                              │
│ [批量创建账号] [导出账号信息] [发送通知短信]                                  │
│                                                                              │
│ 💡 提示:班主任可使用账号登录班主任端进行本班报名                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.3 运动员管理模块

4.3.1 需求描述

运动员是运动会的参赛主体,每个运动员需要分配唯一的号码簿。体育老师可以管理全校运动员,班主任只能管理本班运动员。

4.3.2 运动员信息字段
字段名 类型 必填 唯一 说明 示例
姓名 字符串 运动员姓名 张三
性别 枚举 男/女
年级 字符串 所属年级 高一年级
班级 外键 所属班级ID 1
班级名称 字符串 班级显示名 高一1班
号码簿 字符串 参赛号码 1101
学号 字符串 学籍号(可选) 20240001
身份证号 字符串 身份标识 110101200001011234
出生日期 日期 用于年龄分组 2010-01-01
联系电话 字符串 家长电话 138****0000
紧急联系人 字符串 紧急情况联系人 李四
紧急联系电话 字符串 紧急联系电话 139****0000
健康状况 文本 特殊病史/过敏等 -
照片 图片URL 运动员照片 /uploads/photos/1101.jpg
状态 枚举 正常/受伤/退赛 正常
备注 文本 其他说明 -
4.3.3 运动员管理功能列表
功能 说明 体育老师 班主任
运动员列表 查看所有运动员 ✅(仅本班)
新增运动员 单个添加
批量导入 Excel批量导入
批量导出 导出运动员列表 ✅(仅本班)
编辑运动员 修改信息
删除运动员 删除记录
号码簿生成 批量生成号码簿
模板下载 下载导入模板
4.3.4 运动员列表界面(体育老师端)
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 运动员管理                                          [+ 新增] [导入] [导出]  │
├─────────────────────────────────────────────────────────────────────────────┤
│ 筛选:                                                                      │
│ 年级:[全部年级 ▼] 班级:[全部班级 ▼] 性别:[全部 ▼] 状态:[全部 ▼]        │
│ 搜索:[________________] [搜索] [重置]                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌───┬────────┬────────┬────┬──────────┬──────────┬──────────────┬──────┐ │
│ │ ☐ │ 号码簿  │ 姓名   │ 性别 │ 年级     │ 班级     │ 参赛项目     │ 操作 │ │
│ ├───┼────────┼────────┼────┼──────────┼──────────┼──────────────┼──────┤ │
│ │ ☐ │ 1101   │ 张三   │ 男 │ 高一年级 │ 高一1班  │ 100米,800米  │ 编辑 │ │
│ │   │        │        │    │          │          │              │ 删除 │ │
│ ├───┼────────┼────────┼────┼──────────┼──────────┼──────────────┼──────┤ │
│ │ ☐ │ 1102   │ 李四   │ 女 │ 高一年级 │ 高一1班  │ 50米         │ 编辑 │ │
│ │   │        │        │    │          │          │              │ 删除 │ │
│ ├───┼────────┼────────┼────┼──────────┼──────────┼──────────────┼──────┤ │
│ │ ☐ │ 1103   │ 王五   │ 男 │ 高一年级 │ 高一1班  │ 100米        │ 编辑 │ │
│ │   │        │        │    │          │          │              │ 删除 │ │
│ ├───┼────────┼────────┼────┼──────────┼──────────┼──────────────┼──────┤ │
│ │ ☐ │ 1201   │ 赵六   │ 男 │ 高一年级 │ 高一2班  │ 100米,跳远   │ 编辑 │ │
│ │   │        │        │    │          │          │              │ 删除 │ │
│ └───┴────────┴────────┴────┴──────────┴──────────┴──────────────┴──────┘ │
│                                                                              │
│ 已选择 0 条记录    [批量删除] [批量生成号码簿] [批量修改班级]                │
│                                                                              │
│ [<] 1 2 3 4 5 [>]  共 156 条记录,每页显示 20 条                            │
└─────────────────────────────────────────────────────────────────────────────┘
4.3.5 运动员列表界面(班主任端)
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 本班运动员 - 高一1班                                          [+ 新增]      │
├─────────────────────────────────────────────────────────────────────────────┤
│ 班级信息:班主任:张三  |  联系电话:138****0000  |  运动员数:12人         │
├─────────────────────────────────────────────────────────────────────────────┤
│ 搜索:[________________] [搜索] [导出本班]                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌────┬────────┬────────┬────┬──────────────┬──────────────┬──────┐        │
│ │ 序号 │ 号码簿  │ 姓名   │ 性别 │ 参赛项目     │ 状态        │ 操作 │        │
│ ├────┼────────┼────────┼────┼──────────────┼──────────────┼──────┤        │
│ │ 1  │ 1101   │ 张三   │ 男 │ 100米,800米  │ 正常         │ 编辑 │        │
│ │    │        │        │    │              │              │ 删除 │        │
│ ├────┼────────┼────────┼────┼──────────────┼──────────────┼──────┤        │
│ │ 2  │ 1102   │ 李四   │ 女 │ 50米         │ 正常         │ 编辑 │        │
│ │    │        │        │    │              │              │ 删除 │        │
│ ├────┼────────┼────────┼────┼──────────────┼──────────────┼──────┤        │
│ │ 3  │ 1103   │ 王五   │ 男 │ 100米        │ 受伤         │ 编辑 │        │
│ │    │        │        │    │              │              │ 删除 │        │
│ └────┴────────┴────────┴────┴──────────────┴──────────────┴──────┘        │
│                                                                              │
│ ⚠ 提示:运动员报名请到【报名管理】模块                                       │
└─────────────────────────────────────────────────────────────────────────────┘
4.3.6 新增/编辑运动员表单
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 新增运动员 - 高一1班                                               [关闭]   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 基本信息                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 姓名:       [张三________________] *                                │     │
│ │                                                                      │     │
│ │ 性别:       (●) 男  ( ) 女  ( ) 混合  *                            │     │
│ │                                                                      │     │
│ │ 年级/班级:  高一年级 ▼  高一1班 ▼  *                                │     │
│ │                                                                      │     │
│ │ 号码簿:     [1101______________] *                                  │     │
│ │             ☐ 自动生成(根据号码簿规则)                              │     │
│ │                                                                      │     │
│ │ 学号:       [20240001__________]                                    │     │
│ │                                                                      │     │
│ │ 身份证号:   [__________________]                                    │     │
│ │                                                                      │     │
│ │ 出生日期:   [2010-01-15______]                                      │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 联系方式                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 联系电话:   [13812345678________]                                   │     │
│ │ 紧急联系人: [李四________________]                                   │     │
│ │ 紧急电话:   [13912345678________]                                   │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 其他信息                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 健康状况:   [________________________]                              │     │
│ │             如有特殊病史或过敏请注明                                  │     │
│ │                                                                      │     │
│ │ 状态:       ● 正常  ○ 受伤  ○ 退赛                                  │     │
│ │                                                                      │     │
│ │ 备注:       [________________________]                              │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│                                                    [取消] [保存]            │
└─────────────────────────────────────────────────────────────────────────────┘

4.4 项目管理模块(可自定义)

4.4.1 需求描述

体育老师可以在管理端自定义任何比赛项目,包括项目名称、类别、性别限制、跑道数、计分规则等。支持预设模板和完全自定义。

4.4.2 项目信息字段
字段名 类型 必填 唯一 说明 示例
项目名称 字符串 比赛项目名称 100米
项目代码 字符串 唯一标识符 M100
项目类别 枚举 径赛/田赛/趣味 径赛
距离/类型 字符串 具体距离或类型 100米
性别限制 枚举 男子/女子/混合 男子
默认跑道数 整数 径赛专用 8
是否需要分组 布尔 是否分组预赛
每组最大人数 整数 分组时每组最多人数 8
晋级人数 整数 预赛晋级决赛人数 8
计分规则类型 枚举 全局/自定义 自定义
计分规则 JSON 自定义积分映射 {"1":9,"2":7}
排序顺序 整数 显示顺序 10
是否启用 布尔 是否在本届使用
报名开始时间 日期时间 报名起止 -
报名结束时间 日期时间 报名起止 -
项目纪录 字符串 当前纪录 11.23秒
备注 文本 其他说明 -
4.4.3 项目类别预设
类别 预设项目 可自定义
短跑 50米、100米、200米、400米
长跑 800米、1000米、1500米、3000米
接力 4×100米、4×400米
田赛 跳远、三级跳远、跳高、铅球、实心球、标枪、铁饼
趣味 袋鼠跳、两人三足、迎面接力、拔河
4.4.4 项目管理界面(体育老师端)
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 项目管理 - 体育老师端                                              [+ 新增] │
├─────────────────────────────────────────────────────────────────────────────┤
│ 搜索:[________________] [按类别:全部 ▼] [启用状态:全部 ▼]                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌────┬──────────┬──────────┬──────────┬──────┬──────┬──────┬──────────┐  │
│ │ 排序│ 项目名称  │ 项目代码 │ 类别     │ 性别 │ 跑道数│ 分组 │ 操作     │  │
│ ├────┼──────────┼──────────┼──────────┼──────┼──────┼──────┼──────────┤  │
│ │ 1  │ 50米     │ M50     │ 短跑     │ 男子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 2  │ 50米     │ W50     │ 短跑     │ 女子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 3  │ 100米    │ M100    │ 短跑     │ 男子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 4  │ 100米    │ W100    │ 短跑     │ 女子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 5  │ 200米    │ M200    │ 短跑     │ 男子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 6  │ 400米    │ M400    │ 短跑     │ 男子 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 7  │ 800米    │ M800    │ 长跑     │ 男子 │ 8    │ 否   │ 编辑 删除 │  │
│ │ 8  │ 1000米   │ M1000   │ 长跑     │ 男子 │ 8    │ 否   │ 编辑 删除 │  │
│ │ 9  │ 1500米   │ M1500   │ 长跑     │ 男子 │ 8    │ 否   │ 编辑 删除 │  │
│ │ 10 │ 4×100米  │ R4100   │ 接力     │ 混合 │ 8    │ 是   │ 编辑 删除 │  │
│ │ 11 │ 跳远     │ LJ      │ 田赛     │ 男子 │ -    │ 是   │ 编辑 删除 │  │
│ │ 12 │ 跳高     │ HJ      │ 田赛     │ 女子 │ -    │ 是   │ 编辑 删除 │  │
│ │ 13 │ 铅球     │ SP      │ 田赛     │ 男子 │ -    │ 是   │ 编辑 删除 │  │
│ └────┴──────────┴──────────┴──────────┴──────┴──────┴──────┴──────────┘  │
│                                                                              │
│ [+ 新增自定义项目]  [从模板导入] [批量启用] [批量禁用] [导出项目]            │
└─────────────────────────────────────────────────────────────────────────────┘
4.4.5 新增/编辑项目表单
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 新增项目                                                            [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 基本信息                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 项目名称:   [100米________________] *                               │     │
│ │                                                                      │     │
│ │ 项目代码:   [M100________________] *                                │     │
│ │             唯一标识,建议使用字母+数字                               │     │
│ │                                                                      │     │
│ │ 项目类别:   (●) 径赛  ( ) 田赛  ( ) 趣味                            │     │
│ │                                                                      │     │
│ │ 距离/类型:  [100米________________]                                 │     │
│ │             如:100米、跳远、4×100米接力                             │     │
│ │                                                                      │     │
│ │ 性别限制:   (●) 男子  ( ) 女子  ( ) 混合                            │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 径赛专用设置(田赛/趣味项目可跳过)                                  │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 距离类型:   (●) 短跑  ( ) 长跑                                     │     │
│ │                                                                      │     │
│ │ 默认跑道数: [8] ▼ 道                                               │     │
│ │                                                                      │     │
│ │ 是否需要分组: (●) 是  ( ) 否                                       │     │
│ │                                                                      │     │
│ │ 每组最大人数: [8] 人                                               │     │
│ │                                                                      │     │
│ │ 晋级人数:    [8] 人                                                │     │
│ │               (预赛→决赛,0表示不设决赛)                           │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 计分规则                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ ○ 使用全局计分规则                                                   │     │
│ │ ● 自定义计分规则                                                     │     │
│ │                                                                      │     │
│ │ ┌──────────────┬──────────┬─────────────────────────┐              │     │
│ │ │ 名次范围      │ 积分      │ 操作                     │              │     │
│ │ ├──────────────┼──────────┼─────────────────────────┤              │     │
│ │ │ 第1名        │ [9    ]  │ [删除]                   │              │     │
│ │ │ 第2名        │ [7    ]  │ [删除]                   │              │     │
│ │ │ 第3名        │ [6    ]  │ [删除]                   │              │     │
│ │ │ 第4-8名      │ [5-1]   │ [编辑]                   │              │     │
│ │ └──────────────┴──────────┴─────────────────────────┘              │     │
│ │                                                                      │     │
│ │ [+ 添加名次范围]                                                     │     │
│ │                                                                      │     │
│ │ 💡 接力项目积分倍数:[2] 倍                                          │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 其他设置                                                            │     │
│ │ ─────────────────────────────────────────────────────────────────── │     │
│ │                                                                      │     │
│ │ 排序顺序:   [14________________]                                    │     │
│ │             数字越小显示越靠前                                       │     │
│ │                                                                      │     │
│ │ 是否启用:   ● 是  ○ 否                                             │     │
│ │                                                                      │     │
│ │ 报名开始时间:[2026-03-01 00:00]                                     │     │
│ │ 报名结束时间:[2026-03-15 23:59]                                     │     │
│ │                                                                      │     │
│ │ 项目纪录:   [11.23秒______________]                                 │     │
│ │                                                                      │     │
│ │ 备注:       [________________________]                              │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│                                                    [取消] [保存]            │
└─────────────────────────────────────────────────────────────────────────────┘
4.4.6 项目模板库
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 从模板导入项目                                                      [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 选择模板类别:                                                               │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐                   │
│ │  径赛-短跑   │  径赛-长跑   │   田赛      │   接力      │                   │
│ │    模板     │    模板     │   模板      │   模板      │                   │
│ └─────────────┴─────────────┴─────────────┴─────────────┘                   │
│                                                                              │
│ 径赛-短跑模板(点击展开):                                                   │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ ☑ 50米(男子)    ☑ 50米(女子)    ☑ 100米(男子)  ☑ 100米(女子)│     │
│ │ ☑ 200米(男子)   ☑ 200米(女子)   ☑ 400米(男子)  ☑ 400米(女子)│     │
│ │ ☐ 60米(男子)    ☐ 60米(女子)                                    │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 径赛-长跑模板:                                                               │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ ☑ 800米(男子)   ☑ 800米(女子)   ☑ 1000米(男子) ☑ 1000米(女子)│     │
│ │ ☑ 1500米(男子)  ☑ 1500米(女子)  ☐ 3000米(男子) ☐ 3000米(女子)│     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 田赛模板:                                                                   │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ ☑ 跳远(男子)    ☑ 跳远(女子)    ☑ 跳高(男子)    ☑ 跳高(女子)│     │
│ │ ☑ 铅球(男子)    ☑ 铅球(女子)    ☐ 实心球(男子)  ☐ 实心球(女子)│     │
│ │ ☐ 标枪(男子)    ☐ 标枪(女子)    ☐ 铁饼(男子)    ☐ 铁饼(女子)│     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 接力模板:                                                                   │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ ☑ 4×100米接力(混合) ☑ 4×400米接力(混合)                         │     │
│ │ ☐ 4×100米接力(男子) ☐ 4×100米接力(女子)                         │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [全选] [清空] [导入选中项目]                                                  │
└─────────────────────────────────────────────────────────────────────────────┘

4.5 报名管理模块

4.5.1 需求描述

班主任为本班学生报名参赛项目,体育老师可以审核和汇总报名信息。支持批量报名、个人报名、导出报名表等功能。

4.5.2 报名信息字段
字段名 类型 必填 说明
运动员 外键 关联运动员
项目 外键 关联比赛项目
报名状态 枚举 已报名/已参赛/弃权
报名时间 日期时间 自动记录
报名人 字符串 谁报的名
备注 文本 特殊说明
4.5.3 报名管理功能列表
功能 说明 体育老师 班主任
查看可报项目 查看当前开放报名的项目
批量报名 按班级/项目批量报名
个人报名 为单个运动员报名
报名列表 按项目/班级查看报名
导出班级报名表 导出本班报名表
导出各项目报名表 按项目导出报名表
取消报名 删除报名记录
报名审核 审核班主任提交的报名
人数统计 实时统计各项目报名人数
4.5.4 班主任端-报名界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 本班报名 - 高一1班                                                   [帮助] │
├─────────────────────────────────────────────────────────────────────────────┤
│ 班级信息:班主任:张三  |  运动员数:12人  |  已报名人次:18人次            │
│ 报名时间:2026-03-01 00:00 至 2026-03-15 23:59                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📋 报名规则提醒:                                                           │
│ • 每班每项目最多报名 3 人                                                   │
│ • 每名运动员最多报名 3 个项目                                                │
│ • 请确认运动员资格和健康状况                                                  │
│                                                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│ 快速报名:[选择运动员 ▼] → [选择项目 ▼] → [报名]                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ▼ 运动员报名表                                                              │
│ ┌────┬────────┬────────┬────┬────────────────────────────────┬──────┐    │
│ │ ☐  │ 号码簿  │ 姓名   │ 性别 │ 报名项目                        │ 操作 │    │
│ ├────┼────────┼────────┼────┼────────────────────────────────┼──────┤    │
│ │ ☐  │ 1101   │ 张三   │ 男 │ ☑100米 ☑800米 ☐跳远 [+ 添加]   │ 编辑 │    │
│ ├────┼────────┼────────┼────┼────────────────────────────────┼──────┤    │
│ │ ☐  │ 1102   │ 李四   │ 女 │ ☑50米  ☐100米 ☐跳高 [+ 添加]   │ 编辑 │    │
│ ├────┼────────┼────────┼────┼────────────────────────────────┼──────┤    │
│ │ ☐  │ 1103   │ 王五   │ 男 │ ☑100米 ☐800米 ☐跳远 [+ 添加]   │ 编辑 │    │
│ ├────┼────────┼────────┼────┼────────────────────────────────┼──────┤    │
│ │ ☐  │ 1104   │ 赵六   │ 男 │ ☐100米 ☑4×100米 ☐铅球 [+ 添加] │ 编辑 │    │
│ └────┴────────┴────────┴────┴────────────────────────────────┴──────┘    │
│                                                                              │
│ 已选择 0 条记录    [批量取消报名] [提交报名] [保存草稿]                       │
│                                                                              │
│ 💡 提示:请确认报名信息无误后点击"提交报名",提交后需体育老师审核              │
└─────────────────────────────────────────────────────────────────────────────┘
4.5.5 体育老师端-报名汇总界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 报名汇总 - 体育老师端                                              [导出]   │
├─────────────────────────────────────────────────────────────────────────────┤
│ 项目筛选:[全部项目 ▼]  班级筛选:[全部班级 ▼]  状态:[全部 ▼]             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📊 报名统计看板                                                              │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐                   │
│ │ 总报名人次   │ 已审核人次   │ 待审核人次   │ 超限班级数   │                   │
│ │    186      │    156      │     30      │     3       │                   │
│ └─────────────┴─────────────┴─────────────┴─────────────┘                   │
│                                                                              │
│ ▼ 各项目报名情况                                                            │
│ ┌────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 序号 │ 项目名称  │ 性别    │ 报名人数 │ 班级数   │ 超限情况  │ 操作     │ │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ 1  │ 100米    │ 男子    │ 32      │ 8        │ ⚠ 高一1班│ 查看详情 │ │
│ │    │          │          │         │          │   超1人   │ 导出名单 │ │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ 2  │ 100米    │ 女子    │ 28      │ 7        │ ✅ 正常   │ 查看详情 │ │
│ │    │          │          │         │          │          │ 导出名单 │ │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ 3  │ 800米    │ 男子    │ 16      │ 6        │ ✅ 正常   │ 查看详情 │ │
│ │    │          │          │         │          │          │ 导出名单 │ │
│ └────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ │
│                                                                              │
│ ▼ 待审核报名列表(30条)                                                     │
│ ┌────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ ☐  │ 班级     │ 运动员   │ 项目     │ 报名时间  │ 状态     │ 操作     │ │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ ☐  │ 高一1班  │ 张三     │ 100米    │ 03-10    │ 待审核   │ [通过]   │ │
│ │    │          │          │          │          │          │ [拒绝]   │ │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ ☐  │ 高一1班  │ 李四     │ 50米     │ 03-10    │ 待审核   │ [通过]   │ │
│ │    │          │          │          │          │          │ [拒绝]   │ │
│ └────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ │
│                                                                              │
│ [批量通过] [批量拒绝] [导出报名总表] [导出各项目表]                           │
└─────────────────────────────────────────────────────────────────────────────┘
4.5.6 导出各项目报名表功能
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 导出报名表                                                          [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 导出选项:                                                                   │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 导出格式:                                                          │     │
│ │ (●) Excel (.xlsx)                                                   │     │
│ │ ( ) CSV (.csv)                                                      │     │
│ │ ( ) PDF (.pdf)                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 导出内容:                                                          │     │
│ │                                                                      │     │
│ │ ☑ 项目报名表(按项目分Sheet)                                       │     │
│ │ ☑ 班级报名表(按班级分Sheet)                                       │     │
│ │ ☑ 报名统计汇总表                                                    │     │
│ │ ☐ 运动员信息表                                                      │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 项目筛选(选择要导出的项目):                                       │     │
│ │                                                                      │     │
│ │ ☑ 全选  ☐ 清空                                                      │     │
│ │ ☑ 100米(男子)  ☑ 100米(女子)  ☑ 800米(男子)                   │     │
│ │ ☑ 800米(女子)  ☑ 跳远(男子)    ☑ 跳远(女子)                   │     │
│ │ ☐ 4×100米接力    ☐ 铅球(男子)                                     │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 导出文件预览:                                                               │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 运动会报名表_20260315.xlsx                                          │     │
│ │ ├── 报名统计汇总                                                    │     │
│ │ ├── 100米(男子)                                                   │     │
│ │ ├── 100米(女子)                                                   │     │
│ │ ├── 800米(男子)                                                   │     │
│ │ ├── 高一1班报名表                                                   │     │
│ │ ├── 高一2班报名表                                                   │     │
│ │ └── ...                                                             │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│                                                    [取消] [确认导出]        │
└─────────────────────────────────────────────────────────────────────────────┘
4.5.7 导出的各项目报名表格式

Sheet: 100米(男子)

序号 号码簿 姓名 班级 年级 报名时间 状态
1 1101 张三 高一1班 高一年级 2026-03-10 已审核
2 1103 王五 高一1班 高一年级 2026-03-10 已审核
3 1201 赵六 高一2班 高一年级 2026-03-11 已审核
4 1203 钱七 高一2班 高一年级 2026-03-11 已审核
... ... ... ... ... ... ...

统计行:报名总人数:32人 | 涉及班级:8个 | 超限班级:1个


4.6 编排算法模块

4.6.1 需求描述

编排算法是本系统的核心功能,需要根据报名情况自动分配运动员的道次和组别,满足禁止跨年级、同班尽量不同道等约束条件。

4.6.2 编排输入
输入项 类型 说明
项目ID 整数 要编排的比赛项目
年级 字符串 指定年级(不跨年级)
性别 枚举 男子/女子
报名运动员列表 列表 该项目的所有报名运动员
规则配置 对象 编排规则配置
4.6.3 编排输出
输出项 类型 说明
组别列表 列表 每个组的信息(组号、跑道分配)
道次分配 二维数组 组×跑道的运动员分配
编排统计 对象 总人数、组数、违规情况
警告信息 列表 无法满足的约束警告
4.6.4 编排算法流程图
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        编排算法主流程                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 1: 数据准备                                                            │
│ - 获取该项目报名运动员列表                                                   │
│ - 按班级分组                                                                │
│ - 验证硬约束(同年级、同性别)                                               │
│ - 如果验证失败,返回错误信息                                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 2: 计算组数                                                            │
│ - 最大班级人数 = max(各班报名人数)                                          │
│ - 需要组数 = ceil(最大班级人数 / 跑道数)                                    │
│ - 如果组数 == 0: 组数 = 1                                                   │
│ - 如果组数 > 最大组数限制: 返回警告                                          │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 3: 初始化组结构                                                        │
│ - 创建 heats 数组,长度为组数                                               │
│ - 每个组包含 lanes 数组,长度为跑道数                                        │
│ - 所有位置初始化为 None                                                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 4: 按班级人数降序排序                                                   │
│ - 将班级按报名人数从多到少排序                                               │
│ - 人数多的班级优先分配,便于满足约束                                         │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 5: 贪心分配运动员                                                       │
│ For each 班级:                                                              │
│   For each 运动员:                                                          │
│     1. 选择最优组(同班人数最少的组)                                        │
│     2. 选择最优跑道(同班未占用的跑道)                                      │
│     3. 如果约束无法满足,根据配置决定是警告还是退化为任意位置                │
│     4. 分配运动员到该位置                                                    │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 6: 局部优化(可选)                                                     │
│ - 交换运动员位置,优化同班分散度                                             │
│ - 平衡各组的班级分布                                                        │
│ - 优化跑道使用率                                                            │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 7: 输出结果                                                            │
│ - 返回编排结果(组别、跑道、运动员)                                         │
│ - 生成警告列表(无法完全满足的约束)                                         │
│ - 记录编排日志                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
4.6.5 核心算法伪代码
python 复制代码
def arrange_event(event_id, grade, gender, rule_config):
    """
    编排算法核心函数
    
    Args:
        event_id: 项目ID
        grade: 年级
        gender: 性别
        rule_config: 规则配置 {
            'ban_same_class_same_lane': True,  # 禁止同班同跑道
            'prefer_diff_heat': True,          # 尽量不同组
            'prefer_diff_lane': True,          # 尽量不同道次
            'max_attempts': 1000,              # 最大尝试次数
        }
    
    Returns:
        heats: 编排结果
        warnings: 警告信息
    """
    
    # 1. 获取运动员数据
    athletes = get_athletes_by_event(event_id, grade, gender)
    if not athletes:
        return None, "无报名运动员"
    
    # 2. 验证硬约束
    if not validate_hard_constraints(athletes, grade, gender):
        return None, "硬约束验证失败"
    
    # 3. 按班级分组
    classes = group_by_class(athletes)
    max_class_size = max(len(c) for c in classes.values())
    
    # 4. 计算需要的组数
    lanes_count = get_event_lanes(event_id)
    heats_needed = max(1, (max_class_size + lanes_count - 1) // lanes_count)
    
    # 5. 初始化组结构
    heats = [Heat(heat_id=i, lanes=[None] * lanes_count) 
             for i in range(heats_needed)]
    
    # 6. 记录每个班级已分配的组和跑道
    class_assigned = {}  # {class_id: {'heats': set, 'lanes': set}}
    
    # 7. 按班级人数降序排序
    sorted_classes = sorted(classes.items(), 
                           key=lambda x: len(x[1]), 
                           reverse=True)
    
    warnings = []
    
    for class_id, members in sorted_classes:
        class_assigned[class_id] = {'heats': set(), 'lanes': set()}
        
        for athlete in members:
            # 选择最优组
            best_heat = select_best_heat(
                heats, 
                class_id, 
                rule_config.get('prefer_diff_heat', True)
            )
            
            # 选择最优跑道
            best_lane = select_best_lane(
                heats[best_heat],
                class_id,
                class_assigned,
                rule_config.get('ban_same_class_same_lane', True)
            )
            
            if best_lane is None:
                # 无法满足约束,记录警告
                warnings.append(f"运动员 {athlete.name} 无法满足同班不同道约束")
                # 退化为任意可用跑道
                best_lane = find_any_free_lane(heats[best_heat])
            
            # 分配
            heats[best_heat].lanes[best_lane] = athlete
            class_assigned[class_id]['heats'].add(best_heat)
            class_assigned[class_id]['lanes'].add(best_lane)
    
    # 8. 局部优化(可选)
    if rule_config.get('enable_optimization', True):
        heats = optimize_arrangement(heats, rule_config)
    
    return heats, warnings


def select_best_heat(heats, class_id, prefer_diff_heat):
    """选择最优的组(尽量不同组)"""
    if not prefer_diff_heat:
        # 选择运动员最少的组
        return min(range(len(heats)), 
                   key=lambda i: count_athletes_in_heat(heats[i]))
    
    # 优先选择班级人数最少的组
    heat_scores = []
    for i, heat in enumerate(heats):
        class_count = count_class_in_heat(heat, class_id)
        heat_scores.append((class_count, count_athletes_in_heat(heat), i))
    
    # 按班级人数升序,总人数升序排序
    heat_scores.sort()
    return heat_scores[0][2]
4.6.6 编排结果展示界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 编排结果 - 100米(男子)                                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│ 项目信息:                                                                   │
│ 年级:高一年级  |  性别:男子  |  跑道数:8道  |  报名人数:24人             │
│ 编排时间:2026-03-16 10:30:00  |  状态:✅ 编排完成                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ▼ 第1组                                                                    │
│ ┌─────────┬──────────┬──────────┬──────────┬──────────┬──────────┐        │
│ │ 跑道    │ 号码簿   │ 姓名     │ 班级     │ 成绩     │ 状态     │        │
│ ├─────────┼──────────┼──────────┼──────────┼──────────┼──────────┤        │
│ │ 道次1   │ 1101     │ 张三     │ 高一1班  │ -        │ 待比赛   │        │
│ │ 道次2   │ 1201     │ 赵六     │ 高一2班  │ -        │ 待比赛   │        │
│ │ 道次3   │ 1301     │ 王七     │ 高一3班  │ -        │ 待比赛   │        │
│ │ 道次4   │ 1401     │ 李八     │ 高一4班  │ -        │ 待比赛   │        │
│ │ 道次5   │ 1102     │ 李四     │ 高一1班  │ -        │ 待比赛   │        │
│ │ 道次6   │ 1202     │ 钱九     │ 高一2班  │ -        │ 待比赛   │        │
│ │ 道次7   │ 1302     │ 孙十     │ 高一3班  │ -        │ 待比赛   │        │
│ │ 道次8   │ 1402     │ 周一     │ 高一4班  │ -        │ 待比赛   │        │
│ └─────────┴──────────┴──────────┴──────────┴──────────┴──────────┘        │
│                                                                              │
│ ▼ 第2组                                                                    │
│ ┌─────────┬──────────┬──────────┬──────────┬──────────┬──────────┐        │
│ │ 道次1   │ 1103     │ 王五     │ 高一1班  │ -        │ 待比赛   │        │
│ │ 道次2   │ 1203     │ 郑十     │ 高一2班  │ -        │ 待比赛   │        │
│ │ 道次3   │ 1303     │ 陈一     │ 高一3班  │ -        │ 待比赛   │        │
│ │ 道次4   │ 1403     │ 刘二     │ 高一4班  │ -        │ 待比赛   │        │
│ │ 道次5   │ 1104     │ 林三     │ 高一1班  │ -        │ 待比赛   │        │
│ │ 道次6   │ 1204     │ 郭四     │ 高一2班  │ -        │ 待比赛   │        │
│ │ 道次7   │ 1304     │ 唐五     │ 高一3班  │ -        │ 待比赛   │        │
│ │ 道次8   │ 1404     │ 宋六     │ 高一4班  │ -        │ 待比赛   │        │
│ └─────────┴──────────┴──────────┴──────────┴──────────┴──────────┘        │
│                                                                              │
│ ▼ 第3组                                                                    │
│ ┌─────────┬──────────┬──────────┬──────────┬──────────┬──────────┐        │
│ │ 道次1   │ 1105     │ 吴七     │ 高一1班  │ -        │ 待比赛   │        │
│ │ 道次2   │ 1205     │ 郑八     │ 高一2班  │ -        │ 待比赛   │        │
│ │ 道次3   │ 1305     │ 王九     │ 高一3班  │ -        │ 待比赛   │        │
│ │ 道次4   │ 1405     │ 李十     │ 高一4班  │ -        │ 待比赛   │        │
│ │ 道次5   │ -        │ 空位     │ -        │ -        │ 空位     │        │
│ │ 道次6   │ -        │ 空位     │ -        │ -        │ 空位     │        │
│ │ 道次7   │ -        │ 空位     │ -        │ -        │ 空位     │        │
│ │ 道次8   │ -        │ 空位     │ -        │ -        │ 空位     │        │
│ └─────────┴──────────┴──────────┴──────────┴──────────┴──────────┘        │
│                                                                              │
│ 📊 编排统计:                                                               │
│ 总运动员:24人  |  总组数:3组  |  平均每组:8人  |  空跑道:8个            │
│                                                                              │
│ ⚠ 警告信息:                                                               │
│ • 高一1班有5人报名,无法完全满足"同班不同组"约束,已尽量分散                 │
│                                                                              │
│ [导出Excel] [导出PDF] [打印道次表] [手动调整] [重新编排]                     │
└─────────────────────────────────────────────────────────────────────────────┘
4.6.7 手动调整功能
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 手动调整编排 - 100米(男子)                                        [保存]  │
├─────────────────────────────────────────────────────────────────────────────┤
│ 调整说明:                                                                   │
│ • 拖拽运动员卡片可以移动到其他跑道或组别                                      │
│ • 系统会实时检查是否违反硬约束                                                │
│ • 违反约束时会显示红色警告                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 第1组                         第2组                         第3组   │     │
│ │ ┌─────┐ ┌─────┐ ┌─────┐      ┌─────┐ ┌─────┐ ┌─────┐              │     │
│ │ │道次1│ │道次2│ │道次3│      │道次1│ │道次2│ │道次3│              │     │
│ │ │张三 │ │赵六 │ │王七 │      │王五 │ │郑十 │ │陈一 │              │     │
│ │ │1101 │ │1201 │ │1301 │      │1103 │ │1203 │ │1303 │              │     │
│ │ │高一1│ │高一2│ │高一3│      │高一1│ │高一2│ │高一3│              │     │
│ │ └─────┘ └─────┘ └─────┘      └─────┘ └─────┘ └─────┘              │     │
│ │ ┌─────┐ ┌─────┐ ┌─────┐      ┌─────┐ ┌─────┐ ┌─────┐              │     │
│ │ │道次4│ │道次5│ │道次6│      │道次4│ │道次5│ │道次6│              │     │
│ │ │李八 │ │李四 │ │钱九 │      │刘二 │ │林三 │ │郭四 │              │     │
│ │ │1401 │ │1102 │ │1202 │      │1403 │ │1104 │ │1204 │              │     │
│ │ │高一4│ │高一1│ │高一2│      │高一4│ │高一1│ │高一2│              │     │
│ │ └─────┘ └─────┘ └─────┘      └─────┘ └─────┘ └─────┘              │     │
│ │ ┌─────┐ ┌─────┐                ┌─────┐ ┌─────┐                      │     │
│ │ │道次7│ │道次8│                │道次7│ │道次8│                      │     │
│ │ │孙十 │ │周一 │                │唐五 │ │宋六 │                      │     │
│ │ │1302 │ │1402 │                │1304 │ │1404 │                      │     │
│ │ │高一3│ │高一4│                │高一3│ │高一4│                      │     │
│ │ └─────┘ └─────┘                └─────┘ └─────┘                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 约束检查:                                                                   │
│ ✅ 禁止跨年级:通过                                                          │
│ ✅ 性别分离:通过                                                            │
│ ⚠ 同班不同道:高一1班有2人在同一组但不同跑道(允许)                         │
│                                                                              │
│ [撤销] [重做] [重置] [保存调整]                                               │
└─────────────────────────────────────────────────────────────────────────────┘

4.7 成绩管理模块

4.7.1 需求描述

成绩管理是运动会核心环节,支持成绩录入、批量导入、修改、审核等功能。成绩录入后自动触发排名和积分计算。

4.7.2 成绩信息字段
字段名 类型 必填 说明 示例
运动员 外键 关联运动员 -
项目 外键 关联项目 -
组别 整数 第几组 1
跑道 整数 跑道号 3
成绩原始值 字符串 原始输入成绩 12.34 或 2:35.67
成绩秒数 浮点数 转换为秒便于排序 12.34 或 155.67
名次 整数 组内名次 1
总名次 整数 项目总名次 3
积分 整数 所得积分 9
是否破纪录 布尔 是否打破纪录
风速 浮点数 径赛风速(可选) +1.2
成绩状态 枚举 有效/无效/弃权/DQ 有效
录入时间 日期时间 自动记录 -
录入人 字符串 谁录入的 admin
审核状态 枚举 待审核/已审核 已审核
备注 文本 特殊说明 -
4.7.3 成绩管理功能列表
功能 说明 体育老师 班主任 学生
成绩录入 手动输入成绩
批量导入 Excel批量导入成绩
成绩修改 修改已录入成绩
成绩审核 确认成绩有效
成绩查询 按项目/班级/运动员查询
成绩对比 同一项目不同组成绩对比
导出成绩单 导出成绩表
4.7.4 成绩录入界面(体育老师端)
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 成绩录入 - 100米(男子)                                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│ 项目信息:高一年级 男子 100米  |  赛道数:8道  |  总组数:3组              │
│ 已录入:0/24人  |  录入进度:0%                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│ 选择组别:[第1组 ▼]  成绩格式:秒.毫秒(如:12.34)  [批量录入] [导入Excel] │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌────┬──────┬──────────┬──────────┬──────────┬──────────┬──────────┬────┐ │
│ │ 跑道 │ 号码簿 │ 姓名     │ 班级     │ 成绩     │ 风速     │ 状态     │操作│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 1  │ 1101 │ 张三     │ 高一1班  │ [12.34]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 2  │ 1201 │ 赵六     │ 高一2班  │ [12.56]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 3  │ 1301 │ 王七     │ 高一3班  │ [12.89]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 4  │ 1401 │ 李八     │ 高一4班  │ [13.12]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 5  │ 1102 │ 李四     │ 高一1班  │ [12.45]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 6  │ 1202 │ 钱九     │ 高一2班  │ [12.78]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 7  │ 1302 │ 孙十     │ 高一3班  │ [13.01]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ ├────┼──────┼──────────┼──────────┼──────────┼──────────┼──────────┼────┤ │
│ │ 8  │ 1402 │ 周一     │ 高一4班  │ [13.23]  │ [+1.2]   │ 有效 ▼   │保存│ │
│ └────┴──────┴──────────┴──────────┴──────────┴──────────┴──────────┴────┘ │
│                                                                              │
│ [一键保存全部] [自动计算排名] [查看排名]                                      │
│                                                                              │
│ 💡 快捷键:Enter 保存当前成绩,Tab 切换到下一个输入框                         │
└─────────────────────────────────────────────────────────────────────────────┘
4.7.5 长跑成绩录入界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 成绩录入 - 1000米(男子)                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│ 项目信息:高一年级 男子 1000米  |  赛道数:8道  |  总组数:2组              │
├─────────────────────────────────────────────────────────────────────────────┤
│ 选择组别:[第1组 ▼]  成绩格式:分:秒.毫秒(如:2:35.67)                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌────┬──────┬──────────┬──────────┬──────────────────┬──────────┬────┐    │
│ │ 跑道 │ 号码簿 │ 姓名     │ 班级     │ 成绩             │ 状态     │操作│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 1  │ 1105 │ 吴七     │ 高一1班  │ [2:35.67______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 2  │ 1205 │ 郑八     │ 高一2班  │ [2:38.23______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 3  │ 1305 │ 王九     │ 高一3班  │ [2:42.15______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 4  │ 1405 │ 李十     │ 高一4班  │ [2:45.89______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 5  │ 1106 │ 周十一   │ 高一1班  │ [2:39.01______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 6  │ 1206 │ 吴十二   │ 高一2班  │ [2:41.33______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 7  │ 1306 │ 郑十三   │ 高一3班  │ [2:44.67______]  │ 有效 ▼   │保存│    │
│ ├────┼──────┼──────────┼──────────┼──────────────────┼──────────┼────┤    │
│ │ 8  │ 1406 │ 王十四   │ 高一4班  │ [2:48.22______]  │ 有效 ▼   │保存│    │
│ └────┴──────┴──────────┴──────────┴──────────────────┴──────────┴────┘    │
│                                                                              │
│ 💡 提示:支持输入分:秒.毫秒格式,如 2:35.67 表示 2分35秒67毫秒               │
└─────────────────────────────────────────────────────────────────────────────┘
4.7.6 成绩批量导入功能(支持别名映射)
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 批量导入成绩                                                        [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 步骤1:上传文件                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │  ┌─────────────────────────────────────────────────────────────┐   │     │
│ │  │                                                              │   │     │
│ │  │              📁 拖拽或点击上传 Excel 文件                     │   │     │
│ │  │                                                              │   │     │
│ │  │              支持格式:.xlsx, .xls                           │   │     │
│ │  │              文件大小限制:10MB                              │   │     │
│ │  │                                                              │   │     │
│ │  └─────────────────────────────────────────────────────────────┘   │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 步骤2:列名映射(系统自动识别)                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │ ┌────────────────┬────────────────┬──────────────────┬──────────┐  │     │
│ │ │ 标准字段        │ 识别的列名      │ Excel列名示例     │ 状态     │  │     │
│ │ ├────────────────┼────────────────┼──────────────────┼──────────┤  │     │
│ │ │ 号码簿 *       │ 号码布          │ 1101,1102,...    │ ✅ 已匹配 │  │     │
│ │ │ 项目名称 *     │ 比赛项目        │ 100米,800米      │ ✅ 已匹配 │  │     │
│ │ │ 成绩 *         │ 成绩            │ 12.34,2:35.67    │ ✅ 已匹配 │  │     │
│ │ │ 组别           │ 组别            │ 1,2,3            │ ✅ 已匹配 │  │     │
│ │ │ 跑道           │ 跑道            │ 1,2,3,...        │ ✅ 已匹配 │  │     │
│ │ │ 风速           │ [未匹配]        │ -                │ ⚠ 手动映射│  │     │
│ │ └────────────────┴────────────────┴──────────────────┴──────────┘  │     │
│ │                                                                      │     │
│ │ [修改映射]                                                           │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 步骤3:数据预览                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │ ┌────────┬──────────┬──────────┬──────┬──────┬──────────┬──────┐  │     │
│ │ │ 号码簿 │ 姓名     │ 项目     │ 组别 │ 跑道 │ 成绩     │ 状态 │  │     │
│ │ ├────────┼──────────┼──────────┼──────┼──────┼──────────┼──────┤  │     │
│ │ │ 1101   │ 张三     │ 100米    │ 1    │ 1    │ 12.34    │ ✅   │  │     │
│ │ │ 1201   │ 赵六     │ 100米    │ 1    │ 2    │ 12.56    │ ✅   │  │     │
│ │ │ 1301   │ 王七     │ 100米    │ 1    │ 3    │ 12.89    │ ✅   │  │     │
│ │ │ 1102   │ 李四     │ 100米    │ 1    │ 5    │ 12.45    │ ✅   │  │     │
│ │ │ 1105   │ 吴七     │ 1000米   │ 1    │ 1    │ 2:35.67  │ ✅   │  │     │
│ │ │ 1205   │ 郑八     │ 1000米   │ 1    │ 2    │ 2:38.23  │ ⚠  │  │     │
│ │ │        │          │          │      │      │          │格式异常│ │     │
│ │ └────────┴──────────┴──────────┴──────┴──────┴──────────┴──────┘  │     │
│ │                                                                      │     │
│ │ 共发现 2 条格式异常记录,建议检查后再导入                             │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 步骤4:导入选项                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │ ☑ 遇到重复成绩时覆盖                                                 │     │
│ │ ☐ 跳过格式错误的记录                                                 │     │
│ │ ☑ 导入后自动计算排名                                                 │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│                                                    [取消] [确认导入]        │
└─────────────────────────────────────────────────────────────────────────────┘
4.7.7 成绩导入模板格式

成绩导入模板(Excel)

号码簿 姓名 项目 组别 跑道 成绩 风速 备注
1101 张三 100米 1 1 12.34 +1.2
1102 李四 100米 1 5 12.45 +1.2
1105 吴七 1000米 1 1 2:35.67
1205 郑八 1000米 1 2 2:38.23

模板说明

  • 必填列:号码簿、项目、成绩
  • 项目名称必须与系统中定义的项目名称完全一致
  • 成绩格式:短跑用"秒.毫秒",长跑用"分:秒.毫秒"

4.8 排名与积分模块

4.8.1 需求描述

根据录入的成绩自动计算组内排名、项目总排名,并根据积分规则计算个人积分和团体总分。

4.8.2 排名功能
功能 说明 算法
组内排名 同组内按成绩排序 成绩越好排名越靠前(数值越小)
项目总排名 所有组合并排序 成绩越好排名越靠前
并列处理 成绩相同时的处理 可配置:同名次/名次顺延
晋级排名 预赛晋级决赛排名 取前N名
按班级排名 班级内运动员排名 按成绩排序
按年级排名 年级内运动员排名 按成绩排序
4.8.3 积分功能
功能 说明
个人积分 根据项目名次计算个人积分
团体总分 按班级/年级汇总积分
破纪录加分 破纪录额外加分
参与分 参赛未获奖的基础分
积分榜 实时更新的积分排名
多项目积分汇总 运动员所有项目积分累加
4.8.4 排名计算算法
python 复制代码
def calculate_ranking_and_scores(event_id, scoring_rules):
    """
    计算项目排名和积分
    
    Args:
        event_id: 项目ID
        scoring_rules: 积分规则配置
    
    Returns:
        rankings: 排名结果列表
        team_scores: 团体总分
    """
    
    # 1. 获取所有成绩记录
    results = get_results_by_event(event_id)
    
    # 2. 转换成绩为秒数
    for result in results:
        result['time_seconds'] = convert_to_seconds(result['raw_time'])
    
    # 3. 按组成绩排序,计算组内名次
    for heat in results.groupby('heat'):
        heat['heat_rank'] = heat.sort_values('time_seconds').reset_index(drop=True).index + 1
    
    # 4. 合并所有组成绩,计算总名次
    all_results = results.sort_values('time_seconds').reset_index(drop=True)
    
    # 5. 处理并列名次
    if scoring_rules.get('tie_handling') == 'same_rank':
        # 成绩并列时名次相同
        all_results['total_rank'] = all_results['time_seconds'].rank(method='min').astype(int)
    else:
        # 成绩并列时名次顺延
        all_results['total_rank'] = all_results['time_seconds'].rank(method='dense').astype(int)
    
    # 6. 计算积分
    rank_scores = scoring_rules.get('rank_scores', {})
    default_scores = {1: 9, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1}
    rank_scores = {**default_scores, **rank_scores}
    
    all_results['score'] = all_results['total_rank'].map(rank_scores).fillna(0)
    
    # 7. 破纪录加分
    if scoring_rules.get('record_bonus_enabled', False):
        record = get_event_record(event_id)
        for idx, row in all_results.iterrows():
            if row['time_seconds'] < record['time_seconds']:
                all_results.loc[idx, 'score'] += scoring_rules.get('record_bonus', 10)
                all_results.loc[idx, 'is_record'] = True
    
    # 8. 参与分
    if scoring_rules.get('participation_score_enabled', False):
        participation_score = scoring_rules.get('participation_score', 1)
        all_results['score'] = all_results['score'].apply(
            lambda x: x if x > 0 else participation_score
        )
    
    # 9. 计算团体总分
    team_scores = all_results.groupby(['grade', 'class'])['score'].sum().reset_index()
    team_scores = team_scores.sort_values('score', ascending=False)
    team_scores['rank'] = range(1, len(team_scores) + 1)
    
    return all_results, team_scores


def convert_to_seconds(time_str: str) -> float:
    """
    将成绩字符串转换为秒数(浮点数)
    
    支持格式:
    - 短跑:12.34 -> 12.34
    - 长跑:2:35.67 -> 155.67
    - 分:秒:毫秒:1:23:45.67 -> 5025.67
    """
    if not time_str:
        return None
    
    time_str = time_str.strip()
    
    # 判断是否包含冒号(长跑格式)
    if ':' in time_str:
        parts = time_str.split(':')
        if len(parts) == 2:  # 分:秒.毫秒
            minutes = float(parts[0])
            seconds = float(parts[1])
            return minutes * 60 + seconds
        elif len(parts) == 3:  # 时:分:秒.毫秒
            hours = float(parts[0])
            minutes = float(parts[1])
            seconds = float(parts[2])
            return hours * 3600 + minutes * 60 + seconds
    else:
        # 短跑格式
        return float(time_str)
    
    return None
4.8.5 成绩排名展示界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 成绩排名 - 100米(男子)                                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│ 项目信息:高一年级 男子 100米                                               │
│ 总参赛人数:24人  |  已录入成绩:24人  |  状态:✅ 排名已计算               │
├─────────────────────────────────────────────────────────────────────────────┤
│ 视图切换:[总排名 ▼]  [组内排名]  [班级排名]  [导出成绩单]                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 🏆 前三名                                                                   │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │    🥇 金牌                     🥈 银牌                     🥉 铜牌    │     │
│ │    ┌─────────┐                ┌─────────┐                ┌─────────┐ │     │
│ │    │         │                │         │                │         │ │     │
│ │    │  1101   │                │  1102   │                │  1201   │ │     │
│ │    │  张三   │                │  李四   │                │  赵六   │ │     │
│ │    │ 12.34秒 │                │ 12.45秒 │                │ 12.56秒 │ │     │
│ │    │  9分    │                │  7分    │                │  6分    │ │     │
│ │    │         │                │         │                │         │ │     │
│ │    └─────────┘                └─────────┘                └─────────┘ │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 📊 完整排名列表                                                              │
│ ┌────┬────────┬──────────┬──────────┬──────────┬──────┬──────┬──────────┐ │
│ │ 名次 │ 号码簿  │ 姓名     │ 班级     │ 成绩     │ 组别 │ 跑道 │ 积分     │ │
│ ├────┼────────┼──────────┼──────────┼──────────┼──────┼──────┼──────────┤ │
│ │ 🥇1 │ 1101   │ 张三     │ 高一1班  │ 12.34    │ 1    │ 1    │ 9        │ │
│ │ 🥈2 │ 1102   │ 李四     │ 高一1班  │ 12.45    │ 1    │ 5    │ 7        │ │
│ │ 🥉3 │ 1201   │ 赵六     │ 高一2班  │ 12.56    │ 1    │ 2    │ 6        │ │
│ │ 4  │ 1202   │ 钱九     │ 高一2班  │ 12.78    │ 1    │ 6    │ 5        │ │
│ │ 5  │ 1301   │ 王七     │ 高一3班  │ 12.89    │ 1    │ 3    │ 4        │ │
│ │ 6  │ 1103   │ 王五     │ 高一1班  │ 13.01    │ 2    │ 1    │ 3        │ │
│ │ 7  │ 1302   │ 孙十     │ 高一3班  │ 13.12    │ 1    │ 7    │ 2        │ │
│ │ 8  │ 1401   │ 李八     │ 高一4班  │ 13.23    │ 1    │ 4    │ 1        │ │
│ │ 9  │ 1402   │ 周一     │ 高一4班  │ 13.45    │ 1    │ 8    │ 0        │ │
│ │ ...│ ...    │ ...      │ ...      │ ...      │ ...  │ ...  │ ...      │ │
│ └────┴────────┴──────────┴──────────┴──────────┴──────┴──────┴──────────┘ │
│                                                                              │
│ [导出排名] [打印奖状] [查看详情]                                              │
└─────────────────────────────────────────────────────────────────────────────┘
4.8.6 团体总分榜界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 团体总分榜                                                          [导出]  │
├─────────────────────────────────────────────────────────────────────────────┤
│ 统计范围:全部项目  |  统计时间:2026-03-18  |  状态:✅ 实时更新           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 🏆 团体总分前三名                                                           │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │    🥇 冠军                     🥈 亚军                     🥉 季军    │     │
│ │    ┌─────────┐                ┌─────────┐                ┌─────────┐ │     │
│ │    │         │                │         │                │         │ │     │
│ │    │ 高一1班 │                │ 高一2班 │                │ 高一3班 │ │     │
│ │    │  156分  │                │  142分  │                │  128分  │ │     │
│ │    │  🥇×5   │                │  🥇×3   │                │  🥇×2   │ │     │
│ │    │  🥈×4   │                │  🥈×5   │                │  🥈×3   │ │     │
│ │    │  🥉×3   │                │  🥉×2   │                │  🥉×4   │ │     │
│ │    │         │                │         │                │         │ │     │
│ │    └─────────┘                └─────────┘                └─────────┘ │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 📊 完整团体总分榜                                                            │
│ ┌────┬──────────┬──────────┬──────────┬──────┬──────┬──────┬──────────┐   │
│ │ 排名 │ 班级     │ 年级     │ 总分     │ 金牌 │ 银牌 │ 铜牌 │ 详情     │   │
│ ├────┼──────────┼──────────┼──────────┼──────┼──────┼──────┼──────────┤   │
│ │ 1  │ 高一1班  │ 高一年级  │ 156      │ 5    │ 4    │ 3    │ 查看详情 │   │
│ │ 2  │ 高一2班  │ 高一年级  │ 142      │ 3    │ 5    │ 2    │ 查看详情 │   │
│ │ 3  │ 高一3班  │ 高一年级  │ 128      │ 2    │ 3    │ 4    │ 查看详情 │   │
│ │ 4  │ 高一4班  │ 高一年级  │ 98       │ 1    │ 2    │ 3    │ 查看详情 │   │
│ │ 5  │ 高二1班  │ 高二年级  │ 87       │ 1    │ 2    │ 1    │ 查看详情 │   │
│ │ 6  │ 高二2班  │ 高二年级  │ 76       │ 0    │ 2    │ 3    │ 查看详情 │   │
│ │ 7  │ 高二3班  │ 高二年级  │ 65       │ 0    │ 1    │ 2    │ 查看详情 │   │
│ │ 8  │ 高二4班  │ 高二年级  │ 54       │ 0    │ 1    │ 1    │ 查看详情 │   │
│ └────┴──────────┴──────────┴──────────┴──────┴──────┴──────┴──────────┘   │
│                                                                              │
│ [导出总分榜] [按年级筛选] [按项目筛选] [打印]                                  │
└─────────────────────────────────────────────────────────────────────────────┘

4.9 统计报表模块

4.9.1 需求描述

提供多维度的统计报表,支持报名统计、成绩统计、积分统计等,并支持导出为 Excel/PDF。

4.9.2 统计报表类型
报表类型 说明 体育老师 班主任 学生
报名统计表 各项目报名人数统计
参赛人数统计 各班/年级参赛人数
成绩统计表 各项目成绩汇总
积分统计表 个人/班级积分汇总
破纪录统计 破纪录运动员列表
道次表 各项目道次安排
秩序册 完整秩序册
成绩册 完整成绩册
4.9.3 统计报表界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 统计报表                                                            [导出]  │
├─────────────────────────────────────────────────────────────────────────────┤
│ 报表类型:[报名统计 ▼]  统计范围:[全部项目 ▼]  时间:[2026运动会 ▼]       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📊 各项目报名人数统计                                                        │
│                                                                              │
│ ┌────┬──────────┬──────────┬──────────┬──────────┬──────────┐             │
│ │ 序号 │ 项目名称  │ 性别    │ 报名人数 │ 班级数   │ 超限情况 │             │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┤             │
│ │ 1  │ 50米     │ 男子    │ 24      │ 6        │ ✅       │             │
│ │ 2  │ 50米     │ 女子    │ 20      │ 5        │ ✅       │             │
│ │ 3  │ 100米    │ 男子    │ 32      │ 8        │ ⚠ 高一1班│             │
│ │ 4  │ 100米    │ 女子    │ 28      │ 7        │ ✅       │             │
│ │ 5  │ 800米    │ 男子    │ 16      │ 4        │ ✅       │             │
│ │ 6  │ 800米    │ 女子    │ 14      │ 4        │ ✅       │             │
│ │ 7  │ 1000米   │ 男子    │ 12      │ 3        │ ✅       │             │
│ │ 8  │ 跳远     │ 男子    │ 18      │ 5        │ ✅       │             │
│ │ 9  │ 跳远     │ 女子    │ 15      │ 4        │ ✅       │             │
│ │ 10 │ 4×100米  │ 混合    │ 8队     │ 8        │ ✅       │             │
│ └────┴──────────┴──────────┴──────────┴──────────┴──────────┘             │
│                                                                              │
│ 📊 报名趋势图(柱状图)                                                      │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │ 报名人数                                                            │     │
│ │ 40 ┤                                                                 │     │
│ │ 30 ┤    ████                                                         │     │
│ │ 20 ┤    ████    ████                                                 │     │
│ │ 10 ┤    ████    ████    ████                                         │     │
│ │  0 └────┴────┴────┴────┴────┴────                                    │     │
│ │      50米  100米  800米 1000米  跳远                                 │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [导出统计表] [生成图表] [打印]                                                │
└─────────────────────────────────────────────────────────────────────────────┘
4.9.4 秩序册导出功能
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 导出秩序册                                                          [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 秩序册内容:                                                                │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │ ☑ 封面(运动会名称、日期、地点)                                     │     │
│ │ ☑ 目录                                                            │     │
│ │ ☑ 竞赛规程                                                        │     │
│ │ ☑ 组织委员会名单                                                    │     │
│ │ ☑ 裁判员名单                                                        │     │
│ │ ☑ 代表队名单(按班级)                                              │     │
│ │ ☑ 运动员名单(按班级,含号码簿)                                     │     │
│ │ ☑ 竞赛日程表                                                        │     │
│ │ ☑ 各项目道次表(按项目)                                            │     │
│ │ ☑ 田径纪录表                                                        │     │
│ │ ☐ 场地示意图                                                        │     │
│ │ ☐ 注意事项                                                          │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 导出格式:                                                                   │
│ (●) Excel (.xlsx)  ( ) PDF (.pdf)  ( ) Word (.docx)                        │
│                                                                              │
│ 页面设置:                                                                   │
│ 纸张大小:[A4 ▼]  方向:[纵向 ▼]  页边距:[普通 ▼]                         │
│                                                                              │
│ [预览] [取消] [确认导出]                                                     │
└─────────────────────────────────────────────────────────────────────────────┘

4.10 Excel 导入导出模块

4.10.1 需求描述

支持各类数据的 Excel 导入导出,使用 pandas + numpy 处理,支持列别名映射、数据校验、错误提示等功能。

4.10.2 导入导出功能总览
数据类型 导入 导出 支持别名 必填字段 模板下载
班级信息 班级名称、年级
运动员信息 姓名、性别、年级、班级、号码簿
项目信息 项目名称、项目代码
报名信息 号码簿、项目
成绩信息 号码簿、项目、成绩
编排结果 - - -
统计报表 - - -
4.10.3 导入流程详细
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                           Excel 导入流程                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤1:用户上传 Excel 文件                                           │    │
│  │ - 支持格式:.xlsx, .xls                                              │    │
│  │ - 文件大小限制:10MB                                                 │    │
│  │ - 编码:UTF-8                                                        │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤2:读取文件,检测列名                                            │    │
│  │ - 使用 pandas.read_excel() 读取                                      │    │
│  │ - 获取列名列表                                                       │    │
│  │ - 调用列名映射器进行智能匹配                                          │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤3:展示列名映射预览(用户确认)                                   │    │
│  │ - 显示标准字段与Excel列的对应关系                                     │    │
│  │ - 用户可手动调整映射                                                  │    │
│  │ - 检查必填字段是否都已匹配                                            │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤4:数据清洗和验证                                                │    │
│  │ - 处理空值(必填字段报错)                                            │    │
│  │ - 格式验证(性别、成绩格式等)                                        │    │
│  │ - 唯一性验证(号码簿)                                                │    │
│  │ - 业务规则验证(班级存在、项目存在等)                                 │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤5:展示验证结果和预览数据                                        │    │
│  │ - 成功记录数、失败记录数                                              │    │
│  │ - 错误详情列表(行号+错误原因)                                       │    │
│  │ - 预览前10条数据                                                     │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 步骤6:用户确认后执行导入                                            │    │
│  │ - 批量插入数据库                                                     │    │
│  │ - 记录导入日志                                                       │    │
│  │ - 返回导入结果(成功数、失败数)                                      │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
4.10.4 导入校验规则详细
数据类型 字段 校验规则 错误提示
运动员 姓名 不能为空,长度≤20 "第X行:姓名不能为空"
运动员 性别 只能是"男"/"女"/"M"/"F" "第X行:性别格式错误"
运动员 年级 必须在系统配置中存在 "第X行:年级不存在"
运动员 班级 必须在系统配置中存在 "第X行:班级不存在"
运动员 号码簿 不能为空,全局唯一 "第X行:号码簿重复或为空"
成绩 成绩 格式正确(秒.毫秒 或 分:秒.毫秒) "第X行:成绩格式错误"
报名 号码簿 必须在运动员表中存在 "第X行:号码簿不存在"
报名 项目 必须在项目表中存在 "第X行:项目不存在"
报名 报名人数 不超过班级每项目限制 "第X行:本班该项目报名已超限"
4.10.5 导入结果反馈界面
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 导入结果                                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                         ✅ 导入完成                                   │     │
│ │                                                                      │     │
│ │                    成功导入:156 条                                   │     │
│ │                    失败:5 条                                         │     │
│ │                    跳过:0 条                                         │     │
│ │                                                                      │     │
│ │                    总耗时:2.3 秒                                     │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 错误详情:                                                                   │
│ ┌────┬──────────┬────────────────────────────────────────────────────┐     │
│ │ 行号 │ 错误类型  │ 错误信息                                          │     │
│ ├────┼──────────┼────────────────────────────────────────────────────┤     │
│ │ 3  │ 格式错误  │ 性别格式错误,应为"男"或"女"                        │     │
│ │ 7  │ 必填缺失  │ 号码簿不能为空                                      │     │
│ │ 12 │ 重复     │ 号码簿 1101 已存在                                   │     │
│ │ 15 │ 业务规则  │ 高一1班 100米 报名人数超过限制(最多3人)            │     │
│ │ 18 │ 数据验证  │ 成绩格式错误,应为"秒.毫秒"或"分:秒.毫秒"           │     │
│ └────┴──────────┴────────────────────────────────────────────────────┘     │
│                                                                              │
│ [导出错误报告] [继续导入] [关闭]                                              │
└─────────────────────────────────────────────────────────────────────────────┘
4.10.6 导入模板下载
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 下载导入模板                                                        [关闭]  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 选择模板类型:                                                               │
│                                                                              │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │                                                                      │     │
│ │  ┌─────────────────────────────────────────────────────────────┐   │     │
│ │  │  📁 运动员导入模板                                           │   │     │
│ │  │     包含字段:姓名、性别、年级、班级、号码簿、学号、联系电话    │   │     │
│ │  │                                                              │   │     │
│ │  └─────────────────────────────────────────────────────────────┘   │     │
│ │                                                                      │     │
│ │  ┌─────────────────────────────────────────────────────────────┐   │     │
│ │  │  📁 成绩导入模板                                             │   │     │
│ │  │     包含字段:号码簿、项目、组别、跑道、成绩、风速              │   │     │
│ │  │                                                              │   │     │
│ │  └─────────────────────────────────────────────────────────────┘   │     │
│ │                                                                      │     │
│ │  ┌─────────────────────────────────────────────────────────────┐   │     │
│ │  │  📁 报名导入模板                                             │   │     │
│ │  │     包含字段:号码簿、项目                                    │   │     │
│ │  │                                                              │   │     │
│ │  └─────────────────────────────────────────────────────────────┘   │     │
│ │                                                                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 模板说明:                                                                   │
│ • 第一行为列名,请不要修改列名格式                                            │
│ • 红色*列为必填列                                                           │
│ • 性别请填写"男"或"女"                                                       │
│ • 成绩格式:短跑用"秒.毫秒",长跑用"分:秒.毫秒"                               │
│ • 项目名称必须与系统中定义的项目名称完全一致                                  │
│                                                                              │
│ [下载运动员模板] [下载成绩模板] [下载报名模板] [下载班级模板]                  │
└─────────────────────────────────────────────────────────────────────────────┘
4.10.7 Excel 处理核心代码
python 复制代码
import pandas as pd
import numpy as np
from io import BytesIO
from fastapi import UploadFile


class ExcelHandler:
    """Excel 文件处理器"""
    
    @staticmethod
    async def read_excel(file: UploadFile) -> pd.DataFrame:
        """读取 Excel 文件为 DataFrame"""
        contents = await file.read()
        df = pd.read_excel(BytesIO(contents), dtype=str)
        # 去除首尾空格
        df = df.apply(lambda x: x.str.strip() if x.dtype == "object" else x)
        return df
    
    @staticmethod
    def export_to_excel(dataframes: dict, filename: str) -> BytesIO:
        """
        导出多个 DataFrame 到 Excel 的不同 Sheet
        
        Args:
            dataframes: {"Sheet名称": DataFrame}
            filename: 文件名
        Returns:
            BytesIO 对象
        """
        output = BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            for sheet_name, df in dataframes.items():
                df.to_excel(writer, sheet_name=sheet_name, index=False)
        output.seek(0)
        return output
    
    @staticmethod
    def validate_required_columns(df: pd.DataFrame, required_cols: list) -> list:
        """验证必填列是否存在"""
        missing = [col for col in required_cols if col not in df.columns]
        return missing
    
    @staticmethod
    def validate_not_null(df: pd.DataFrame, column: str) -> pd.Series:
        """验证非空"""
        is_null = df[column].isna() | (df[column].astype(str).str.strip() == '')
        return is_null
    
    @staticmethod
    def validate_unique(df: pd.DataFrame, column: str) -> dict:
        """验证唯一性"""
        duplicates = df[df[column].duplicated(keep=False)]
        return {
            "is_valid": len(duplicates) == 0,
            "duplicates": duplicates[column].tolist()
        }
    
    @staticmethod
    def generate_template(template_type: str) -> BytesIO:
        """生成导入模板"""
        templates = {
            "athlete": pd.DataFrame({
                "姓名": ["张三"],
                "性别": ["男"],
                "年级": ["高一年级"],
                "班级": ["高一1班"],
                "号码簿": ["1101"],
                "学号": ["20240001"],
                "联系电话": ["13812345678"]
            }),
            "score": pd.DataFrame({
                "号码簿": ["1101"],
                "项目": ["100米"],
                "组别": [1],
                "跑道": [1],
                "成绩": ["12.34"],
                "风速": ["+1.2"]
            }),
            "registration": pd.DataFrame({
                "号码簿": ["1101"],
                "项目": ["100米"]
            }),
            "class": pd.DataFrame({
                "班级名称": ["高一1班"],
                "年级": ["高一年级"],
                "班主任": ["张三"],
                "联系电话": ["13812345678"]
            })
        }
        
        df = templates.get(template_type)
        output = BytesIO()
        df.to_excel(output, index=False)
        output.seek(0)
        return output

5. 多端功能详细

5.1 体育老师端

5.1.1 功能概述

体育老师端是系统的管理核心,拥有全部功能权限,负责系统配置、项目管理、编排执行、成绩录入、报表导出等。

5.1.2 菜单结构

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 体育老师端 - 菜单结构                                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📊 仪表盘                                                                    │
│ ├── 数据看板(报名人数、完成度、排名预警)                                    │
│ ├── 快捷操作(快速编排、快速录入)                                           │
│ └── 通知公告                                                                │
│                                                                              │
│ ⚙️ 系统配置                                                                 │
│ ├── 基础规则配置                                                            │
│ ├── 号码簿规则配置                                                          │
│ ├── 编排规则配置                                                            │
│ ├── Excel别名配置                                                           │
│ └── 积分规则配置                                                            │
│                                                                              │
│ 🏫 基础数据                                                                  │
│ ├── 班级管理                                                                │
│ ├── 运动员管理                                                              │
│ └── 项目管理(可自定义)                                                     │
│                                                                              │
│ 📝 报名管理                                                                  │
│ ├── 报名汇总                                                                │
│ ├── 报名审核                                                                │
│ ├── 导出报名表                                                              │
│ └── 报名统计                                                                │
│                                                                              │
│ 🎯 编排管理                                                                  │
│ ├── 执行编排                                                                │
│ ├── 编排结果查看                                                            │
│ ├── 手动调整                                                                │
│ └── 导出道次表                                                              │
│                                                                              │
│ 🏆 成绩管理                                                                  │
│ ├── 成绩录入                                                                │
│ ├── 成绩导入                                                                │
│ ├── 成绩查询                                                                │
│ └── 成绩审核                                                                │
│                                                                              │
│ 📈 排名积分                                                                  │
│ ├── 成绩排名                                                                │
│ ├── 团体总分榜                                                              │
│ ├── 个人积分榜                                                              │
│ └── 破纪录榜                                                                │
│                                                                              │
│ 📄 统计报表                                                                  │
│ ├── 报名统计表                                                              │
│ ├── 成绩统计表                                                              │
│ ├── 秩序册生成                                                              │
│ ├── 成绩册生成                                                              │
│ └── 自定义报表                                                              │
│                                                                              │
│ 👥 用户管理                                                                  │
│ ├── 班主任账号管理                                                          │
│ ├── 学生账号管理                                                            │
│ └── 角色权限管理                                                            │
│                                                                              │
│ 📋 系统日志                                                                  │
│ ├── 操作日志                                                                │
│ ├── 导入导出日志                                                            │
│ └── 登录日志                                                                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

5.1.3 仪表盘界面

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 仪表盘                                                          2026运动会  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📊 数据概览                                                                  │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐                   │
│ │ 参赛班级数   │ 运动员总数   │ 报名总人次   │ 已完成项目   │                   │
│ │     12      │    186      │    324      │    8/12     │                   │
│ │   ↑2 较上届  │   ↑15%      │   ↑8%       │   67%       │                   │
│ └─────────────┴─────────────┴─────────────┴─────────────┘                   │
│                                                                              │
│ ⚠️ 待办提醒                                                                  │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ • 3个班级未提交报名表(高一5班、高一6班、高二3班)                    │     │
│ │ • 4个项目未编排(100米女子、800米男子、跳远女子、铅球男子)            │     │
│ │ • 2个班级报名超限(高一1班100米超1人,高一2班跳远超2人)               │     │
│ │ • 6个项目的成绩未录入                                                  │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 🚀 快捷操作                                                                  │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ [执行编排] [成绩录入] [导入数据] [导出报表] [查看排名]                │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 📈 报名趋势图                                                                │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 100 ┤                                                                 │     │
│ │  80 ┤    ████                                                         │     │
│ │  60 ┤    ████    ████                                                 │     │
│ │  40 ┤    ████    ████    ████                                         │     │
│ │  20 ┤    ████    ████    ████    ████                                 │     │
│ │   0 └────┴────┴────┴────┴────┴────                                    │     │
│ │      第1周  第2周  第3周  第4周  第5周                                 │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

5.2 班主任端

5.2.1 功能概述

班主任端主要用于本班运动员管理和报名工作,可以查看本班信息、管理本班运动员、为本班学生报名参赛。

5.2.2 菜单结构

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 班主任端 - 菜单结构                                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📊 班级看板                                                                  │
│ ├── 本班概况(运动员数、报名人次)                                           │
│ ├── 报名进度                                                                │
│ └── 通知公告                                                                │
│                                                                              │
│ 👥 本班运动员                                                                │
│ ├── 运动员列表                                                              │
│ ├── 新增运动员                                                              │
│ ├── 编辑运动员                                                              │
│ └── 导出本班运动员                                                          │
│                                                                              │
│ 📝 本班报名                                                                  │
│ ├── 报名操作                                                                │
│ ├── 报名列表                                                                │
│ ├── 导出报名表                                                              │
│ └── 报名统计                                                                │
│                                                                              │
│ 🎯 赛程查看                                                                  │
│ ├── 本班道次表                                                              │
│ ├── 本班赛程日历                                                            │
│ └── 运动员个人赛程                                                          │
│                                                                              │
│ 🏆 成绩查看                                                                  │
│ ├── 本班成绩榜                                                              │
│ ├── 个人成绩查询                                                            │
│ └── 本班积分榜                                                              │
│                                                                              │
│ 📄 报表查看                                                                  │
│ ├── 本班报名统计                                                            │
│ └── 本班成绩统计                                                            │
│                                                                              │
│ ⚙️ 班级设置                                                                  │
│ ├── 班级信息修改                                                            │
│ └── 修改密码                                                                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

5.2.3 班级看板界面(班主任端)

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 班级看板 - 高一1班                                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 🏫 班级信息                                                                  │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 班主任:张三        联系电话:138****0000                           │     │
│ │ 班级人数:45人      运动员数:12人                                  │     │
│ │ 报名状态:✅ 已提交  提交时间:2026-03-12 14:30                      │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 📊 报名统计                                                                  │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐                   │
│ │ 报名总人次   │ 男子项目     │ 女子项目     │ 人均项目数   │                   │
│ │     18      │     10      │     8       │    1.5      │                   │
│ └─────────────┴─────────────┴─────────────┴─────────────┘                   │
│                                                                              │
│ ⚠️ 提醒                                                                      │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ • 报名截止时间:2026-03-15 23:59,请及时提交                          │     │
│ │ • 张三(1101)已报2个项目,还可报1个                                  │     │
│ │ • 王五(1103)未报名任何项目                                          │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 🏆 本班成绩快报(已有成绩的项目)                                             │
│ ┌────┬──────────┬──────────┬──────────┬──────────┬──────────┐             │
│ │ 序号 │ 运动员   │ 项目     │ 成绩     │ 名次     │ 积分     │             │
│ ├────┼──────────┼──────────┼──────────┼──────────┼──────────┤             │
│ │ 1  │ 张三     │ 100米    │ 12.34    │ 第1名    │ 9        │             │
│ │ 2  │ 李四     │ 50米     │ 7.89     │ 第2名    │ 7        │             │
│ │ 3  │ 赵六     │ 跳远     │ 5.23米   │ 第3名    │ 6        │             │
│ └────┴──────────┴──────────┴──────────┴──────────┴──────────┘             │
│                                                                              │
│ [前往报名] [查看赛程] [查看成绩榜]                                            │
└─────────────────────────────────────────────────────────────────────────────┘

5.2.4 本班赛程日历界面

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 本班赛程日历 - 高一1班                                               [导出]  │
├─────────────────────────────────────────────────────────────────────────────┤
│ 日期选择:[2026-10-15 ▼]  [2026-10-16]  [2026-10-17]                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 📅 2026年10月15日 上午                                                       │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 时间        │ 项目        │ 运动员    │ 组别 │ 跑道 │ 状态         │     │
│ ├─────────────┼────────────┼──────────┼──────┼──────┼──────────────┤     │
│ │ 09:00       │ 100米预赛   │ 张三     │ 1    │ 1    │ ✅ 已比赛     │     │
│ │ 09:08       │ 100米预赛   │ 王五     │ 2    │ 5    │ ⏰ 即将开始   │     │
│ │ 10:00       │ 跳远决赛    │ 赵六     │ 1    │ 3    │ ⏰ 即将开始   │     │
│ │ 10:30       │ 4×100米接力 │ 高一1班  │ 1    │ 2    │ 📋 待比赛     │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ 📅 2026年10月15日 下午                                                       │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 时间        │ 项目        │ 运动员    │ 组别 │ 跑道 │ 状态         │     │
│ ├─────────────┼────────────┼──────────┼──────┼──────┼──────────────┤     │
│ │ 14:00       │ 100米决赛   │ 张三     │ 1    │ 4    │ 📋 待比赛     │     │
│ │ 15:00       │ 800米决赛   │ 李四     │ 1    │ 2    │ 📋 待比赛     │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│ [打印赛程] [添加到日历]                                                       │
└─────────────────────────────────────────────────────────────────────────────┘

5.3 学生端(可选)

5.3.1 功能概述

学生端为可选模块,供学生查看个人赛程和成绩。支持移动端适配,使用 Vant UI 组件库。

5.3.2 菜单结构

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 学生端 - 菜单结构(移动端)                                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ 🏠 首页                                                                      │
│ ├── 我的赛程(今日比赛)                                                     │
│ ├── 成绩快报                                                                │
│ └── 通知公告                                                                │
│                                                                              │
│ 🎯 我的赛程                                                                  │
│ ├── 全部赛程                                                                │
│ ├── 按日期查看                                                              │
│ └── 比赛日历                                                                │
│                                                                              │
│ 🏆 我的成绩                                                                  │
│ ├── 个人成绩列表                                                            │
│ ├── 个人积分                                                                │
│ └── 获奖记录                                                                │
│                                                                              │
│ 📊 赛事信息                                                                  │
│ ├── 项目列表                                                                │
│ ├── 道次表查看                                                              │
│ ├── 成绩排名                                                                │
│ └── 团体总分                                                                │
│                                                                              │
│ 👤 个人中心                                                                  │
│ ├── 个人资料                                                                │
│ ├── 修改密码                                                                │
│ └── 退出登录                                                                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

5.3.3 学生端首页界面(移动端)

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                              📱 学生端                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  👋 你好,张三                                         🔔  📅       │    │
│  │  号码簿:1101                                          高一1班       │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  📊 我的数据                                                                  │
│  ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐               │
│  │     参赛项目     │ │     已完赛      │ │     获得积分     │               │
│  │       3        │ │       2        │ │      16        │               │
│  └─────────────────┘ └─────────────────┘ └─────────────────┘               │
│                                                                              │
│  🎯 今日赛程(2026-10-15)                                                   │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  ┌─────────────────────────────────────────────────────────────┐   │    │
│  │  │  🏃 100米决赛                                                │   │    │
│  │  │  时间:14:00                    状态:⏰ 即将开始              │   │    │
│  │  │  跑道:第4道                    组别:第1组                   │   │    │
│  │  └─────────────────────────────────────────────────────────────┘   │    │
│  │                                                                      │    │
│  │  ┌─────────────────────────────────────────────────────────────┐   │    │
│  │  │  🏃 4×100米接力                                             │   │    │
│  │  │  时间:15:30                    状态:📋 待比赛              │   │    │
│  │  │  跑道:第2道                    组别:第1组                   │   │    │
│  │  └─────────────────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  🏆 我的成绩                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  ┌─────────────────────────────────────────────────────────────┐   │    │
│  │  │  🥇 100米预赛                                               │   │    │
│  │  │  成绩:12.34秒              名次:第1名                      │   │    │
│  │  │  积分:9分                  状态:✅ 已完赛                  │   │    │
│  │  └─────────────────────────────────────────────────────────────┘   │    │
│  │                                                                      │    │
│  │  ┌─────────────────────────────────────────────────────────────┐   │    │
│  │  │  🥈 跳远决赛                                                 │   │    │
│  │  │  成绩:5.23米               名次:第2名                      │   │    │
│  │  │  积分:7分                  状态:✅ 已完赛                  │   │    │
│  │  └─────────────────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│ 底部导航栏: [🏠首页] [🎯赛程] [🏆成绩] [📊赛事] [👤我的]                     │
└─────────────────────────────────────────────────────────────────────────────┘

5.3.4 学生端赛程查看界面

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 我的赛程                                              📅 日历视图          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  十月 2026                                                                   │
│  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐                               │
│  │ 日  │ 一  │ 二  │ 三  │ 四  │ 五  │ 六  │                               │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤                               │
│  │     │     │     │     │ 1   │ 2   │ 3   │                               │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤                               │
│  │ 4   │ 5   │ 6   │ 7   │ 8   │ 9   │ 10  │                               │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤                               │
│  │ 11  │ 12  │ 13  │ 14  │ ●15 │ 16  │ 17  │  ● 有比赛                      │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤                               │
│  │ 18  │ 19  │ 20  │ 21  │ 22  │ 23  │ 24  │                               │
│  └─────┴─────┴─────┴─────┴─────┴─────┴─────┘                               │
│                                                                              │
│ 2026-10-15 的比赛:                                                          │
│ ┌─────────────────────────────────────────────────────────────────────┐     │
│ │ 时间        │ 项目        │ 组别 │ 跑道 │ 状态                        │     │
│ ├─────────────┼────────────┼──────┼──────┼────────────────────────────┤     │
│ │ 09:00       │ 100米预赛   │ 1    │ 1    │ ✅ 已完赛 12.34秒 第1名     │     │
│ │ 14:00       │ 100米决赛   │ 1    │ 4    │ ⏰ 即将开始                  │     │
│ │ 15:30       │ 4×100米接力 │ 1    │ 2    │ 📋 待比赛                    │     │
│ └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

6. 非功能需求

6.1 性能需求

指标 要求 测量方法
页面加载时间 < 2秒 浏览器开发者工具
API响应时间 < 500ms(95%请求) 后端日志统计
编排算法执行时间 < 30秒(1000人规模) 计时统计
Excel导入速度 > 1000条/秒 批量导入测试
并发用户数 支持50人同时在线 压力测试
数据库查询 带索引查询 < 100ms 慢查询日志

6.2 安全需求

需求 说明
身份认证 JWT Token,有效期24小时
密码安全 BCrypt加密存储,强制复杂度要求
权限控制 RBAC模型,接口级权限校验
SQL注入防护 Django ORM + 参数化查询
XSS防护 前端输出转义,CSP策略
CSRF防护 Django CSRF中间件
操作日志 记录关键操作(导入、编排、删除)
数据备份 每日自动备份,保留30天

6.3 可用性需求

需求 说明
界面响应式 支持1920x1080、1366x768分辨率
移动端适配 学生端支持手机浏览器
操作可撤销 编排结果支持回滚
错误提示 友好的错误提示,含解决方案
操作引导 首次使用新手引导
快捷键支持 成绩录入支持Enter/Tab快捷键

6.4 可扩展性需求

需求 说明
项目可自定义 支持添加任意比赛项目
规则可配置 编排规则、积分规则可配置
字段可扩展 运动员信息支持自定义字段
API版本管理 支持多版本API共存
插件机制 预留计时设备对接接口

6.5 可维护性需求

需求 说明
代码规范 遵循PEP8、ESLint规范
注释覆盖率 核心代码注释 > 30%
日志记录 分级日志(DEBUG/INFO/WARNING/ERROR)
监控告警 关键指标监控,异常告警
一键部署 Docker Compose一键启动

7. 数据模型详细

7.1 Django Models 设计

python 复制代码
# models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator


class User(AbstractUser):
    """用户表"""
    ROLE_CHOICES = (
        ('super_admin', '超级管理员'),
        ('teacher', '体育老师'),
        ('class_teacher', '班主任'),
        ('student', '学生'),
        ('viewer', '查看者'),
    )
    role = models.CharField('角色', max_length=20, choices=ROLE_CHOICES, default='viewer')
    phone = models.CharField('电话', max_length=20, blank=True)
    avatar = models.ImageField('头像', upload_to='avatars/', blank=True, null=True)
    school = models.CharField('学校', max_length=100, blank=True, default='')
    
    class Meta:
        db_table = 'users'
        verbose_name = '用户'
        verbose_name_plural = '用户'


class Class(models.Model):
    """班级表"""
    name = models.CharField('班级名称', max_length=50, unique=True)
    grade = models.CharField('年级', max_length=20, db_index=True)
    grade_order = models.IntegerField('年级排序', default=0)
    class_order = models.IntegerField('班级排序', default=0)
    code = models.CharField('班级编号', max_length=20, blank=True, unique=True)
    teacher_name = models.CharField('班主任姓名', max_length=50)
    teacher_user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='class_managed')
    phone = models.CharField('联系电话', max_length=20, blank=True)
    student_count = models.IntegerField('学生人数', default=0)
    is_participating = models.BooleanField('是否参赛', default=True)
    remark = models.TextField('备注', blank=True)
    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        db_table = 'classes'
        verbose_name = '班级'
        verbose_name_plural = '班级'
        ordering = ['grade_order', 'class_order']
    
    def __str__(self):
        return self.name


class Athlete(models.Model):
    """运动员表"""
    GENDER_CHOICES = (
        ('M', '男'),
        ('F', '女'),
    )
    STATUS_CHOICES = (
        ('normal', '正常'),
        ('injured', '受伤'),
        ('withdrawn', '退赛'),
    )
    
    name = models.CharField('姓名', max_length=50, db_index=True)
    gender = models.CharField('性别', max_length=1, choices=GENDER_CHOICES)
    grade = models.CharField('年级', max_length=20, db_index=True)
    class_obj = models.ForeignKey(Class, on_delete=models.CASCADE, related_name='athletes', verbose_name='班级')
    number = models.CharField('号码簿', max_length=20, unique=True, db_index=True)
    student_id = models.CharField('学号', max_length=50, blank=True, unique=True)
    id_card = models.CharField('身份证号', max_length=18, blank=True, unique=True)
    birth_date = models.DateField('出生日期', null=True, blank=True)
    phone = models.CharField('联系电话', max_length=20, blank=True)
    emergency_contact = models.CharField('紧急联系人', max_length=50, blank=True)
    emergency_phone = models.CharField('紧急联系电话', max_length=20, blank=True)
    health_status = models.TextField('健康状况', blank=True)
    photo = models.ImageField('照片', upload_to='athletes/', blank=True, null=True)
    status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='normal')
    remark = models.TextField('备注', blank=True)
    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        db_table = 'athletes'
        verbose_name = '运动员'
        verbose_name_plural = '运动员'
        indexes = [
            models.Index(fields=['grade', 'class_obj']),
        ]
    
    def __str__(self):
        return f"{self.number} - {self.name}"


class Event(models.Model):
    """项目表"""
    CATEGORY_CHOICES = (
        ('track_sprint', '径赛-短跑'),
        ('track_distance', '径赛-长跑'),
        ('track_relay', '径赛-接力'),
        ('field_jump', '田赛-跳跃'),
        ('field_throw', '田赛-投掷'),
        ('fun', '趣味项目'),
    )
    GENDER_CHOICES = (
        ('M', '男子'),
        ('F', '女子'),
        ('mixed', '混合'),
    )
    
    name = models.CharField('项目名称', max_length=50, db_index=True)
    code = models.CharField('项目代码', max_length=20, unique=True)
    category = models.CharField('项目类别', max_length=20, choices=CATEGORY_CHOICES)
    distance_type = models.CharField('距离类型', max_length=20, blank=True, help_text='短跑/长跑')
    gender_limit = models.CharField('性别限制', max_length=10, choices=GENDER_CHOICES)
    default_lanes = models.IntegerField('默认跑道数', default=8, validators=[MinValueValidator(1), MaxValueValidator(12)])
    need_heats = models.BooleanField('是否需要分组', default=True)
    max_per_heat = models.IntegerField('每组最大人数', default=8)
   晋级人数 = models.IntegerField('晋级人数', default=8, help_text='预赛晋级决赛人数,0表示不设决赛')
    scoring_type = models.CharField('计分规则类型', max_length=20, default='global', choices=(('global', '全局'), ('custom', '自定义')))
    scoring_rules = models.JSONField('计分规则', default=dict, blank=True)
    sort_order = models.IntegerField('排序顺序', default=0)
    is_enabled = models.BooleanField('是否启用', default=True)
    registration_start = models.DateTimeField('报名开始时间', null=True, blank=True)
    registration_end = models.DateTimeField('报名结束时间', null=True, blank=True)
    record = models.CharField('项目纪录', max_length=50, blank=True)
    remark = models.TextField('备注', blank=True)
    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        db_table = 'events'
        verbose_name = '项目'
        verbose_name_plural = '项目'
        ordering = ['sort_order']
    
    def __str__(self):
        return f"{self.name} - {self.get_gender_limit_display()}"


class Registration(models.Model):
    """报名表"""
    STATUS_CHOICES = (
        ('pending', '待审核'),
        ('approved', '已审核'),
        ('rejected', '已拒绝'),
        ('withdrawn', '已弃权'),
    )
    
    athlete = models.ForeignKey(Athlete, on_delete=models.CASCADE, related_name='registrations')
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='registrations')
    status = models.CharField('报名状态', max_length=20, choices=STATUS_CHOICES, default='pending')
    registered_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='registrations')
    registered_at = models.DateTimeField('报名时间', auto_now_add=True)
    remark = models.TextField('备注', blank=True)
    
    class Meta:
        db_table = 'registrations'
        verbose_name = '报名'
        verbose_name_plural = '报名'
        unique_together = [['athlete', 'event']]
        indexes = [
            models.Index(fields=['event', 'status']),
            models.Index(fields=['athlete']),
        ]
    
    def __str__(self):
        return f"{self.athlete.name} - {self.event.name}"


class Arrangement(models.Model):
    """编排结果表"""
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='arrangements')
    grade = models.CharField('年级', max_length=20, db_index=True)
    gender = models.CharField('性别', max_length=1, choices=Athlete.GENDER_CHOICES)
    heat = models.IntegerField('组别', db_index=True)
    lane = models.IntegerField('跑道')
    athlete = models.ForeignKey(Athlete, on_delete=models.CASCADE, related_name='arrangements')
    created_at = models.DateTimeField('编排时间', auto_now_add=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    
    class Meta:
        db_table = 'arrangements'
        verbose_name = '编排结果'
        verbose_name_plural = '编排结果'
        unique_together = [['event', 'grade', 'gender', 'heat', 'lane']]
        indexes = [
            models.Index(fields=['event', 'grade', 'gender']),
        ]
    
    def __str__(self):
        return f"{self.event.name} - {self.grade} - 第{self.heat}组 - 第{self.lane}道"


class Result(models.Model):
    """成绩表"""
    STATUS_CHOICES = (
        ('valid', '有效'),
        ('invalid', '无效'),
        ('dq', '取消成绩'),
        ('dns', '未参赛'),
        ('dnf', '未完赛'),
    )
    
    athlete = models.ForeignKey(Athlete, on_delete=models.CASCADE, related_name='results')
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='results')
    arrangement = models.ForeignKey(Arrangement, on_delete=models.SET_NULL, null=True, blank=True)
    heat = models.IntegerField('组别')
    lane = models.IntegerField('跑道')
    raw_time = models.CharField('原始成绩', max_length=50)
    time_seconds = models.FloatField('成绩秒数', null=True, blank=True, db_index=True)
    heat_rank = models.IntegerField('组内名次', null=True, blank=True)
    total_rank = models.IntegerField('总名次', null=True, blank=True, db_index=True)
    score = models.IntegerField('积分', default=0)
    wind_speed = models.FloatField('风速', null=True, blank=True)
    is_record = models.BooleanField('是否破纪录', default=False)
    status = models.CharField('成绩状态', max_length=20, choices=STATUS_CHOICES, default='valid')
    entered_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='entered_results')
    entered_at = models.DateTimeField('录入时间', auto_now_add=True)
    reviewed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='reviewed_results')
    reviewed_at = models.DateTimeField('审核时间', null=True, blank=True)
    remark = models.TextField('备注', blank=True)
    
    class Meta:
        db_table = 'results'
        verbose_name = '成绩'
        verbose_name_plural = '成绩'
        indexes = [
            models.Index(fields=['event', 'total_rank']),
            models.Index(fields=['athlete', 'event']),
        ]
    
    def __str__(self):
        return f"{self.athlete.name} - {self.event.name} - {self.raw_time}"


class SystemConfig(models.Model):
    """系统配置表"""
    key = models.CharField('配置键', max_length=100, unique=True, db_index=True)
    value = models.JSONField('配置值')
    description = models.TextField('描述', blank=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        db_table = 'system_configs'
        verbose_name = '系统配置'
        verbose_name_plural = '系统配置'
    
    def __str__(self):
        return self.key


class OperationLog(models.Model):
    """操作日志表"""
    ACTION_CHOICES = (
        ('create', '创建'),
        ('update', '更新'),
        ('delete', '删除'),
        ('import', '导入'),
        ('export', '导出'),
        ('arrange', '编排'),
        ('login', '登录'),
        ('logout', '登出'),
    )
    
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='logs')
    action = models.CharField('操作类型', max_length=20, choices=ACTION_CHOICES)
    module = models.CharField('模块', max_length=50)
    description = models.TextField('描述')
    ip_address = models.GenericIPAddressField('IP地址', null=True, blank=True)
    user_agent = models.TextField('User Agent', blank=True)
    created_at = models.DateTimeField('操作时间', auto_now_add=True)
    
    class Meta:
        db_table = 'operation_logs'
        verbose_name = '操作日志'
        verbose_name_plural = '操作日志'
        indexes = [
            models.Index(fields=['user', 'created_at']),
            models.Index(fields=['action', 'created_at']),
        ]

7.2 预置系统配置

python 复制代码
# 预置配置数据
PRESET_CONFIGS = {
    # 基础配置
    "basic_config": {
        "global_lanes": 8,
        "max_per_class_per_event": 3,
        "max_events_per_athlete": 3,
        "sprint_threshold": 400,
        "score_decimal_places": 2,
        "registration_deadline": None,
        "allow_self_registration": False,
        "student_portal_enabled": False,
        "auto_arrange": False,
        "allow_manual_adjust": True,
        "sports_name": "第X届田径运动会",
        "start_date": None,
        "end_date": None,
        "location": "",
    },
    
    # 号码簿规则
    "number_rule": {
        "template": "{grade}{class}{seq:02d}",
        "grade_mapping": {
            "一年级": 1, "二年级": 2, "三年级": 3,
            "四年级": 4, "五年级": 5, "六年级": 6,
            "初一": 7, "初二": 8, "初三": 9,
            "高一": 10, "高二": 11, "高三": 12,
        },
        "auto_extract_class_number": True,
        "auto_pad_zero": True,
        "unique_global": True,
        "allow_manual_edit": True,
    },
    
    # 编排规则
    "arrange_rule": {
        "hard_constraints": {
            "ban_cross_grade": True,
            "gender_separate": True,
        },
        "soft_constraints": {
            "ban_same_class_same_lane": True,
            "prefer_diff_heat": True,
            "prefer_diff_lane": True,
            "scramble_across_classes": False,
            "center_best_athletes": False,
        },
        "algorithm_params": {
            "max_attempts": 1000,
            "timeout_seconds": 30,
        },
    },
    
    # Excel列别名
    "column_alias": {
        "name": ["姓名", "名字", "name", "运动员名称"],
        "gender": ["性别", "sex", "gender", "男女"],
        "grade": ["年级", "grade", "年段"],
        "class": ["班级", "class", "班别", "班级名称", "班"],
        "number": ["号码簿", "号码布", "号码", "number", "参赛号", "编号"],
        "events": ["参赛项目", "项目", "event", "报名项目"],
        "result": ["成绩", "时间", "result", "time", "比赛成绩"],
        "lane": ["跑道", "lane", "道次"],
        "heat": ["组别", "heat", "组", "轮次"],
        "rank": ["名次", "rank", "排名"],
        "score": ["积分", "score", "point"],
    },
    
    # 积分规则
    "scoring_rule": {
        "rank_scores": {1: 9, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1},
        "tie_handling": "same_rank",  # same_rank / sequential
        "record_bonus_enabled": False,
        "record_bonus": 10,
        "participation_score_enabled": False,
        "participation_score": 1,
        "relay_multiplier": 2,
        "team_score_type": "class",  # class / grade
        "team_score_sort": "total_score",  # total_score / gold_first
    },
}

8. API 接口详细

8.1 接口设计规范

8.1.1 通用规范

规范项 说明
基础路径 /api/v1/
请求格式 application/json
响应格式 application/json
字符编码 UTF-8
时间格式 ISO 8601 (YYYY-MM-DDTHH:mm:ssZ)
分页参数 page(页码,默认1)、page_size(每页条数,默认20)

8.1.2 统一响应格式

成功响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {},
    "timestamp": "2026-01-20T10:30:00Z"
}

分页响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "items": [],
        "total": 100,
        "page": 1,
        "page_size": 20,
        "total_pages": 5
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

错误响应

json 复制代码
{
    "code": 400,
    "message": "参数错误",
    "errors": [
        {
            "field": "name",
            "message": "姓名不能为空"
        }
    ],
    "timestamp": "2026-01-20T10:30:00Z"
}

8.1.3 HTTP状态码

状态码 说明
200 成功
201 创建成功
400 请求参数错误
401 未认证
403 无权限
404 资源不存在
409 数据冲突
422 验证失败
500 服务器内部错误

8.2 认证接口(Django + JWT)

方法 端点 功能 权限
POST /api/auth/login/ 用户登录 公开
POST /api/auth/logout/ 用户登出 已认证
POST /api/auth/refresh/ 刷新Token 已认证
POST /api/auth/change-password/ 修改密码 已认证
POST /api/auth/reset-password/ 重置密码(申请) 公开
GET /api/auth/profile/ 获取当前用户信息 已认证
PUT /api/auth/profile/ 更新用户信息 已认证

8.2.1 登录接口

请求

http 复制代码
POST /api/auth/login/
Content-Type: application/json

{
    "username": "zhangsan",
    "password": "123456",
    "role_type": "teacher"  // teacher / class_teacher / student
}

响应

json 复制代码
{
    "code": 200,
    "message": "登录成功",
    "data": {
        "access_token": "eyJhbGciOiJIUzI1NiIs...",
        "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
        "expires_in": 86400,
        "user": {
            "id": 1,
            "username": "zhangsan",
            "name": "张三",
            "role": "teacher",
            "avatar": "/media/avatars/zhangsan.jpg",
            "class_id": null,
            "class_name": null,
            "permissions": ["arrange_event", "import_athletes", "enter_scores"]
        }
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.3 班级管理接口

方法 端点 功能 权限
GET /api/classes/ 获取班级列表 所有角色
GET /api/classes/{id}/ 获取班级详情 所有角色
POST /api/classes/ 创建班级 体育老师
PUT /api/classes/{id}/ 更新班级 体育老师
DELETE /api/classes/{id}/ 删除班级 体育老师
POST /api/classes/import/ 批量导入班级 体育老师
GET /api/classes/export/ 导出班级列表 体育老师
POST /api/classes/{id}/create-teacher-account/ 创建班主任账号 体育老师

8.3.1 获取班级列表

请求

http 复制代码
GET /api/classes/?grade=高一年级&page=1&page_size=20

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "items": [
            {
                "id": 1,
                "name": "高一1班",
                "grade": "高一年级",
                "grade_order": 10,
                "class_order": 1,
                "code": "101",
                "teacher_name": "张三",
                "teacher_user": {
                    "id": 10,
                    "username": "zhangsan",
                    "last_login": null
                },
                "phone": "13812345678",
                "student_count": 12,
                "athlete_count": 12,
                "is_participating": true,
                "remark": "",
                "created_at": "2026-03-01T00:00:00Z"
            }
        ],
        "total": 12,
        "page": 1,
        "page_size": 20,
        "total_pages": 1
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.4 运动员管理接口

方法 端点 功能 权限
GET /api/athletes/ 获取运动员列表 所有角色
GET /api/athletes/{id}/ 获取运动员详情 所有角色
POST /api/athletes/ 创建运动员 体育老师/班主任
PUT /api/athletes/{id}/ 更新运动员 体育老师/班主任(本班)
DELETE /api/athletes/{id}/ 删除运动员 体育老师/班主任(本班)
POST /api/athletes/import/ 批量导入运动员 体育老师
GET /api/athletes/export/ 导出运动员列表 体育老师
POST /api/athletes/batch-generate-numbers/ 批量生成号码簿 体育老师
GET /api/athletes/template/ 下载导入模板 所有角色

8.4.1 获取运动员列表(带筛选)

请求

http 复制代码
GET /api/athletes/?grade=高一年级&class_id=1&gender=M&status=normal&search=张三&page=1&page_size=20

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "items": [
            {
                "id": 1,
                "name": "张三",
                "gender": "M",
                "gender_display": "男",
                "grade": "高一年级",
                "class_id": 1,
                "class_name": "高一1班",
                "number": "1101",
                "student_id": "20240001",
                "phone": "13812345678",
                "status": "normal",
                "status_display": "正常",
                "events": [
                    {
                        "id": 3,
                        "name": "100米"
                    },
                    {
                        "id": 7,
                        "name": "800米"
                    }
                ],
                "created_at": "2026-03-01T00:00:00Z"
            }
        ],
        "total": 156,
        "page": 1,
        "page_size": 20,
        "total_pages": 8
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.4.2 批量生成号码簿

请求

http 复制代码
POST /api/athletes/batch-generate-numbers/
Content-Type: application/json

{
    "athlete_ids": [1, 2, 3, 4, 5],
    "dry_run": false  // 是否仅预览不保存
}

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "generated": 5,
        "failed": 0,
        "results": [
            {
                "athlete_id": 1,
                "name": "张三",
                "old_number": null,
                "new_number": "1101",
                "status": "success"
            }
        ]
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.5 项目管理接口

方法 端点 功能 权限
GET /api/events/ 获取项目列表 所有角色
GET /api/events/{id}/ 获取项目详情 所有角色
POST /api/events/ 创建项目 体育老师
PUT /api/events/{id}/ 更新项目 体育老师
DELETE /api/events/{id}/ 删除项目 体育老师
POST /api/events/import/ 批量导入项目 体育老师
GET /api/events/export/ 导出项目列表 体育老师
POST /api/events/{id}/toggle/ 启用/禁用项目 体育老师
GET /api/events/templates/ 获取项目模板 体育老师

8.5.1 获取项目列表

请求

http 复制代码
GET /api/events/?category=track_sprint&gender=M&is_enabled=true

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "items": [
            {
                "id": 1,
                "name": "100米",
                "code": "M100",
                "category": "track_sprint",
                "category_display": "径赛-短跑",
                "distance_type": "短跑",
                "gender_limit": "M",
                "gender_display": "男子",
                "default_lanes": 8,
                "need_heats": true,
                "max_per_heat": 8,
                "晋级人数": 8,
                "scoring_type": "global",
                "scoring_rules": {},
                "sort_order": 10,
                "is_enabled": true,
                "registration_start": "2026-03-01T00:00:00Z",
                "registration_end": "2026-03-15T23:59:59Z",
                "record": "11.23秒",
                "registration_count": 32,
                "arranged": true
            }
        ],
        "total": 15
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.6 报名管理接口

方法 端点 功能 权限
GET /api/registrations/ 获取报名列表 所有角色
POST /api/registrations/ 添加报名 体育老师/班主任
DELETE /api/registrations/{id}/ 取消报名 体育老师/班主任
POST /api/registrations/batch/ 批量报名 体育老师/班主任
PUT /api/registrations/{id}/approve/ 审核报名 体育老师
GET /api/registrations/statistics/ 报名统计 所有角色
POST /api/registrations/import/ 批量导入报名 体育老师
GET /api/registrations/export/ 导出报名表 体育老师/班主任

8.6.1 批量报名

请求

http 复制代码
POST /api/registrations/batch/
Content-Type: application/json

{
    "class_id": 1,
    "athlete_ids": [1, 2, 3, 4],
    "event_ids": [3, 7]
}

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "success": 8,
        "failed": 0,
        "warnings": [],
        "details": [
            {
                "athlete_id": 1,
                "athlete_name": "张三",
                "event_id": 3,
                "event_name": "100米",
                "status": "success"
            }
        ]
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.6.2 报名统计

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "total_registrations": 324,
        "total_athletes": 186,
        "total_events": 15,
        "pending_review": 30,
        "approved": 156,
        "rejected": 0,
        "by_event": [
            {
                "event_id": 3,
                "event_name": "100米",
                "gender": "男子",
                "count": 32,
                "class_count": 8,
                "violations": [
                    {
                        "class_name": "高一1班",
                        "count": 4,
                        "limit": 3,
                        "exceed": 1
                    }
                ]
            }
        ],
        "by_class": [
            {
                "class_id": 1,
                "class_name": "高一1班",
                "athlete_count": 12,
                "registration_count": 18,
                "avg_per_athlete": 1.5
            }
        ]
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.7 编排算法接口(FastAPI)

方法 端点 功能 权限
POST /api/arrange/{event_id}/ 执行编排 体育老师
POST /api/arrange/preview/ 预览编排(不保存) 体育老师
GET /api/arrange/{event_id}/ 获取编排结果 所有角色
PUT /api/arrange/{event_id}/ 更新编排结果(手动调整) 体育老师
DELETE /api/arrange/{event_id}/ 清空编排结果 体育老师
GET /api/arrange/export/{event_id}/ 导出道次表 体育老师
POST /api/arrange/batch/ 批量编排多个项目 体育老师

8.7.1 执行编排

请求

http 复制代码
POST /api/arrange/3/
Content-Type: application/json

{
    "grade": "高一年级",
    "gender": "M",
    "rule_config": {
        "ban_same_class_same_lane": true,
        "prefer_diff_heat": true,
        "prefer_diff_lane": true
    },
    "force": false  // 是否覆盖已有编排结果
}

响应

json 复制代码
{
    "code": 200,
    "message": "编排成功",
    "data": {
        "event_id": 3,
        "event_name": "100米",
        "grade": "高一年级",
        "gender": "M",
        "heats": [
            {
                "heat_no": 1,
                "lanes": [
                    {
                        "lane": 1,
                        "athlete": {
                            "id": 1,
                            "name": "张三",
                            "number": "1101",
                            "class_name": "高一1班"
                        }
                    },
                    {
                        "lane": 2,
                        "athlete": null
                    }
                ]
            }
        ],
        "statistics": {
            "total_athletes": 24,
            "total_heats": 3,
            "avg_per_heat": 8,
            "empty_lanes": 0
        },
        "warnings": [
            "高一1班有5人报名,无法完全满足'同班不同组'约束,已尽量分散"
        ],
        "execution_time_ms": 234
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.7.2 批量编排

请求

http 复制代码
POST /api/arrange/batch/
Content-Type: application/json

{
    "event_ids": [1, 2, 3, 4, 5],
    "rule_config": {
        "ban_same_class_same_lane": true,
        "prefer_diff_heat": true
    }
}

响应

json 复制代码
{
    "code": 200,
    "message": "批量编排完成",
    "data": {
        "total": 5,
        "success": 4,
        "failed": 1,
        "results": [
            {
                "event_id": 1,
                "event_name": "50米",
                "status": "success",
                "message": "编排成功"
            },
            {
                "event_id": 2,
                "event_name": "50米",
                "status": "skipped",
                "message": "无报名运动员"
            }
        ]
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.8 成绩管理接口

方法 端点 功能 权限
GET /api/results/ 获取成绩列表 所有角色
POST /api/results/ 录入成绩 体育老师
PUT /api/results/{id}/ 修改成绩 体育老师
DELETE /api/results/{id}/ 删除成绩 体育老师
POST /api/results/import/ 批量导入成绩 体育老师
GET /api/results/export/{event_id}/ 导出成绩单 体育老师
POST /api/results/calculate-ranking/{event_id}/ 计算排名 体育老师
GET /api/results/ranking/{event_id}/ 获取排名 所有角色

8.8.1 录入成绩

请求

http 复制代码
POST /api/results/
Content-Type: application/json

{
    "athlete_id": 1,
    "event_id": 3,
    "heat": 1,
    "lane": 1,
    "raw_time": "12.34",
    "wind_speed": 1.2,
    "status": "valid"
}

响应

json 复制代码
{
    "code": 200,
    "message": "成绩录入成功",
    "data": {
        "id": 1,
        "athlete_id": 1,
        "athlete_name": "张三",
        "event_id": 3,
        "event_name": "100米",
        "heat": 1,
        "lane": 1,
        "raw_time": "12.34",
        "time_seconds": 12.34,
        "status": "valid",
        "entered_at": "2026-01-20T10:30:00Z"
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.8.2 获取成绩排名

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "event_id": 3,
        "event_name": "100米",
        "grade": "高一年级",
        "gender": "男子",
        "rankings": [
            {
                "rank": 1,
                "athlete_id": 1,
                "name": "张三",
                "number": "1101",
                "class_name": "高一1班",
                "raw_time": "12.34",
                "time_seconds": 12.34,
                "heat": 1,
                "lane": 1,
                "score": 9,
                "is_record": false
            },
            {
                "rank": 2,
                "athlete_id": 2,
                "name": "李四",
                "number": "1102",
                "class_name": "高一1班",
                "raw_time": "12.45",
                "time_seconds": 12.45,
                "heat": 1,
                "lane": 5,
                "score": 7,
                "is_record": false
            }
        ],
        "statistics": {
            "total": 24,
            "with_scores": 24,
            "average_time": 13.02,
            "best_time": 12.34,
            "record_broken": false
        }
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.9 报表统计接口

方法 端点 功能 权限
GET /api/statistics/team-score/ 团体总分榜 所有角色
GET /api/statistics/individual-score/ 个人积分榜 所有角色
GET /api/statistics/registration/ 报名统计 所有角色
GET /api/statistics/record/ 破纪录统计 所有角色
POST /api/statistics/export/ 导出统计报表 体育老师
GET /api/statistics/order-book/ 生成秩序册 体育老师
GET /api/statistics/result-book/ 生成成绩册 体育老师

8.9.1 团体总分榜

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "items": [
            {
                "rank": 1,
                "class_id": 1,
                "class_name": "高一1班",
                "grade": "高一年级",
                "total_score": 156,
                "gold": 5,
                "silver": 4,
                "bronze": 3,
                "details": {
                    "by_event": [
                        {
                            "event_name": "100米",
                            "score": 25,
                            "gold": 1,
                            "silver": 1,
                            "bronze": 0
                        }
                    ]
                }
            }
        ],
        "updated_at": "2026-01-20T10:30:00Z"
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.10 Excel 导入导出接口(FastAPI)

方法 端点 功能 权限
POST /api/excel/import/athletes/ 导入运动员 体育老师
POST /api/excel/import/scores/ 导入成绩 体育老师
POST /api/excel/import/registrations/ 导入报名 体育老师
POST /api/excel/import/preview/ 预览导入(列名检测) 体育老师
GET /api/excel/template/{type}/ 下载导入模板 所有角色
POST /api/excel/export/arrangement/ 导出道次表 体育老师
POST /api/excel/export/order-book/ 导出秩序册 体育老师
POST /api/excel/export/result-book/ 导出成绩册 体育老师

8.10.1 预览导入(列名检测)

请求

http 复制代码
POST /api/excel/import/preview/
Content-Type: multipart/form-data

file: athletes.xlsx
import_type: athletes

响应

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "excel_columns": ["姓名", "性别", "年级", "班级", "号码布", "参赛项目"],
        "detected_mapping": {
            "name": "姓名",
            "gender": "性别",
            "grade": "年级",
            "class": "班级",
            "number": "号码布",
            "events": "参赛项目"
        },
        "missing_required": [],
        "unmatched_columns": [],
        "preview_data": [
            {
                "姓名": "张三",
                "性别": "男",
                "年级": "高一年级",
                "班级": "高一1班",
                "号码布": "1101",
                "参赛项目": "100米,800米"
            }
        ],
        "total_rows": 156,
        "validation_summary": {
            "valid": true,
            "errors": [],
            "warnings": []
        }
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

8.10.2 确认导入

请求

http 复制代码
POST /api/excel/import/athletes/
Content-Type: application/json

{
    "file_id": "temp_123456",
    "mapping": {
        "name": "姓名",
        "gender": "性别",
        "grade": "年级",
        "class": "班级",
        "number": "号码布",
        "events": "参赛项目"
    },
    "options": {
        "skip_on_error": false,
        "auto_generate_number": true,
        "validate_unique": true
    }
}

响应

json 复制代码
{
    "code": 200,
    "message": "导入完成",
    "data": {
        "total": 156,
        "success": 151,
        "failed": 5,
        "errors": [
            {
                "row": 3,
                "field": "性别",
                "message": "性别格式错误,应为'男'或'女'"
            },
            {
                "row": 7,
                "field": "号码簿",
                "message": "号码簿不能为空"
            }
        ],
        "import_id": "imp_20260120_001",
        "duration_ms": 2345
    },
    "timestamp": "2026-01-20T10:30:00Z"
}

9. 前端界面详细

9.1 路由配置(懒加载)

javascript 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
    // 体育老师端路由
    {
        path: '/teacher',
        component: () => import('@/layouts/TeacherLayout.vue'),
        meta: { role: ['super_admin', 'teacher'], title: '体育老师端' },
        children: [
            {
                path: 'dashboard',
                name: 'TeacherDashboard',
                component: () => import('@/views/teacher/Dashboard.vue'),
                meta: { title: '仪表盘' }
            },
            {
                path: 'classes',
                name: 'ClassManagement',
                component: () => import('@/views/teacher/ClassManagement.vue'),
                meta: { title: '班级管理' }
            },
            {
                path: 'athletes',
                name: 'AthleteManagement',
                component: () => import('@/views/teacher/AthleteManagement.vue'),
                meta: { title: '运动员管理' }
            },
            {
                path: 'events',
                name: 'EventManagement',
                component: () => import('@/views/teacher/EventManagement.vue'),
                meta: { title: '项目管理' }
            },
            {
                path: 'registrations',
                name: 'RegistrationManagement',
                component: () => import('@/views/teacher/RegistrationManagement.vue'),
                meta: { title: '报名管理' }
            },
            {
                path: 'arrange',
                name: 'ArrangeCenter',
                component: () => import('@/views/teacher/ArrangeCenter.vue'),
                meta: { title: '编排中心' }
            },
            {
                path: 'scores',
                name: 'ScoreEntry',
                component: () => import('@/views/teacher/ScoreEntry.vue'),
                meta: { title: '成绩录入' }
            },
            {
                path: 'ranking',
                name: 'RankingView',
                component: () => import('@/views/teacher/RankingView.vue'),
                meta: { title: '成绩排名' }
            },
            {
                path: 'statistics',
                name: 'StatisticsReport',
                component: () => import('@/views/teacher/StatisticsReport.vue'),
                meta: { title: '统计报表' }
            },
            {
                path: 'settings',
                name: 'SystemSettings',
                component: () => import('@/views/teacher/SystemSettings.vue'),
                meta: { title: '系统设置' }
            }
        ]
    },
    
    // 班主任端路由
    {
        path: '/class-teacher',
        component: () => import('@/layouts/ClassTeacherLayout.vue'),
        meta: { role: ['class_teacher'], title: '班主任端' },
        children: [
            {
                path: 'dashboard',
                name: 'ClassDashboard',
                component: () => import('@/views/class-teacher/Dashboard.vue'),
                meta: { title: '班级看板' }
            },
            {
                path: 'athletes',
                name: 'ClassAthletes',
                component: () => import('@/views/class-teacher/Athletes.vue'),
                meta: { title: '本班运动员' }
            },
            {
                path: 'registration',
                name: 'ClassRegistration',
                component: () => import('@/views/class-teacher/Registration.vue'),
                meta: { title: '本班报名' }
            },
            {
                path: 'schedule',
                name: 'ClassSchedule',
                component: () => import('@/views/class-teacher/Schedule.vue'),
                meta: { title: '本班赛程' }
            },
            {
                path: 'results',
                name: 'ClassResults',
                component: () => import('@/views/class-teacher/Results.vue'),
                meta: { title: '本班成绩' }
            }
        ]
    },
    
    // 学生端路由(移动端)
    {
        path: '/student',
        component: () => import('@/layouts/StudentLayout.vue'),
        meta: { role: ['student'], title: '学生端' },
        children: [
            {
                path: 'home',
                name: 'StudentHome',
                component: () => import('@/views/student/Home.vue'),
                meta: { title: '首页' }
            },
            {
                path: 'schedule',
                name: 'StudentSchedule',
                component: () => import('@/views/student/Schedule.vue'),
                meta: { title: '我的赛程' }
            },
            {
                path: 'results',
                name: 'StudentResults',
                component: () => import('@/views/student/Results.vue'),
                meta: { title: '我的成绩' }
            },
            {
                path: 'events',
                name: 'StudentEvents',
                component: () => import('@/views/student/Events.vue'),
                meta: { title: '赛事信息' }
            },
            {
                path: 'profile',
                name: 'StudentProfile',
                component: () => import('@/views/student/Profile.vue'),
                meta: { title: '个人中心' }
            }
        ]
    },
    
    // 公共路由
    {
        path: '/login',
        name: 'Login',
        component: () => import('@/views/Login.vue'),
        meta: { title: '登录' }
    },
    {
        path: '/404',
        name: 'NotFound',
        component: () => import('@/views/NotFound.vue'),
        meta: { title: '页面不存在' }
    },
    {
        path: '/:pathMatch(.*)*',
        redirect: '/404'
    }
]

const router = createRouter({
    history: createWebHistory(),
    routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
    const token = localStorage.getItem('access_token')
    const userRole = localStorage.getItem('user_role')
    
    if (to.path !== '/login' && !token) {
        next('/login')
    } else if (to.meta.role && !to.meta.role.includes(userRole)) {
        next('/404')
    } else {
        next()
    }
})

export default router

9.2 全局组件注册

javascript 复制代码
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

import App from './App.vue'
import router from './router'

const app = createApp(App)

// 注册Element Plus图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

app.use(createPinia())
app.use(router)
app.use(ElementPlus, { locale: zhCn })

app.mount('#app')

9.3 主要UI组件设计

9.3.1 数据表格组件

vue 复制代码
<!-- components/DataTable.vue -->
<template>
  <div class="data-table">
    <!-- 工具栏 -->
    <div class="table-toolbar">
      <div class="toolbar-left">
        <slot name="toolbar-left"></slot>
      </div>
      <div class="toolbar-right">
        <el-input
          v-model="searchKeyword"
          placeholder="搜索"
          prefix-icon="Search"
          clearable
          @clear="handleSearch"
          @keyup.enter="handleSearch"
        />
        <el-button @click="handleRefresh">
          <el-icon><Refresh /></el-icon>
          刷新
        </el-button>
        <slot name="toolbar-right"></slot>
      </div>
    </div>

    <!-- 表格 -->
    <el-table
      :data="tableData"
      v-loading="loading"
      border
      stripe
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" v-if="showSelection" />
      <el-table-column
        v-for="col in columns"
        :key="col.prop"
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
        :fixed="col.fixed"
        :sortable="col.sortable"
      >
        <template #default="{ row }">
          <slot :name="`col-${col.prop}`" :row="row">
            {{ row[col.prop] }}
          </slot>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150" fixed="right" v-if="showActions">
        <template #default="{ row }">
          <slot name="actions" :row="row"></slot>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div class="table-pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handlePageChange"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const props = defineProps({
  columns: { type: Array, required: true },
  fetchData: { type: Function, required: true },
  showSelection: { type: Boolean, default: false },
  showActions: { type: Boolean, default: true }
})

const emit = defineEmits(['selection-change'])

const tableData = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const searchKeyword = ref('')
const selectedRows = ref([])

const loadData = async () => {
  loading.value = true
  try {
    const res = await props.fetchData({
      page: currentPage.value,
      page_size: pageSize.value,
      search: searchKeyword.value
    })
    tableData.value = res.data.items
    total.value = res.data.total
  } catch (error) {
    console.error('加载数据失败:', error)
  } finally {
    loading.value = false
  }
}

const handleSearch = () => {
  currentPage.value = 1
  loadData()
}

const handleRefresh = () => {
  loadData()
}

const handlePageChange = () => {
  loadData()
}

const handleSizeChange = () => {
  currentPage.value = 1
  loadData()
}

const handleSelectionChange = (val) => {
  selectedRows.value = val
  emit('selection-change', val)
}

watch(() => props.fetchData, () => {
  loadData()
}, { immediate: true })
</script>

<style scoped>
.data-table {
  background: #fff;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}

.table-toolbar {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
}

.toolbar-right {
  display: flex;
  gap: 12px;
  align-items: center;
}

.table-pagination {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
</style>

10. 私有部署方案

10.1 部署架构图

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                           学校内网服务器                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        Docker Host                                   │    │
│  │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐           │    │
│  │  │   Nginx       │  │   Django      │  │   FastAPI     │           │    │
│  │  │   Container   │  │   Container   │  │   Container   │           │    │
│  │  │   Port:80/443 │  │   Port:8000   │  │   Port:8001   │           │    │
│  │  └───────────────┘  └───────────────┘  └───────────────┘           │    │
│  │         │                  │                  │                     │    │
│  │         └──────────────────┼──────────────────┘                     │    │
│  │                            │                                        │    │
│  │                   ┌────────┴────────┐                               │    │
│  │                   │                 │                               │    │
│  │            ┌──────┴──────┐   ┌──────┴──────┐                        │    │
│  │            │ PostgreSQL  │   │   Redis     │                        │    │
│  │            │  Container  │   │  Container  │                        │    │
│  │            └─────────────┘   └─────────────┘                        │    │
│  │                                                                      │    │
│  │  ┌─────────────────────────────────────────────────────────────┐    │    │
│  │  │                    数据卷(持久化)                          │    │    │
│  │  │  ./data/postgres  - 数据库文件                              │    │    │
│  │  │  ./data/uploads   - 上传文件                                │    │    │
│  │  │  ./data/exports   - 导出文件                                │    │    │
│  │  │  ./data/logs      - 日志文件                                │    │    │
│  │  └─────────────────────────────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                      │                                       │
│                                      ▼                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                         学校内网客户端                               │    │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                 │    │
│  │  │ 体育老师电脑 │  │ 班主任电脑  │  │ 学生手机    │                 │    │
│  │  └─────────────┘  └─────────────┘  └─────────────┘                 │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘

10.2 硬件配置要求

部署规模 CPU 内存 硬盘 带宽 适用场景
小型(<500人) 2核 4GB 50GB 10Mbps 小学、初中
中型(500-1500人) 4核 8GB 100GB 20Mbps 高中、完中
大型(>1500人) 8核 16GB 200GB 50Mbps 大型学校、区级

10.3 软件环境要求

软件 版本 说明
Docker 20.10+ 容器运行时
Docker Compose 2.0+ 容器编排
Git 2.0+ 代码拉取
操作系统 Ubuntu 20.04/22.04 或 CentOS 7+ 推荐Ubuntu
操作系统* Windows 临时运行+Python

10.4 Docker Compose 配置

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # PostgreSQL 数据库
  postgres:
    image: postgres:15
    container_name: sports_postgres
    environment:
      POSTGRES_DB: sports_meet
      POSTGRES_USER: sports_admin
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
      - ./backups:/backups
    ports:
      - "5432:5432"
    restart: unless-stopped
    networks:
      - sports_network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sports_admin"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: sports_redis
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - ./data/redis:/data
    ports:
      - "6379:6379"
    restart: unless-stopped
    networks:
      - sports_network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Django 后端
  django:
    build:
      context: ./backend/django
      dockerfile: Dockerfile
    container_name: sports_django
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.production
      - DATABASE_URL=postgresql://sports_admin:${DB_PASSWORD}@postgres:5432/sports_meet
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - SECRET_KEY=${DJANGO_SECRET_KEY}
      - DEBUG=False
      - ALLOWED_HOSTS=${ALLOWED_HOSTS}
    volumes:
      - ./backend/django:/app
      - ./data/uploads:/app/media/uploads
      - ./data/exports:/app/media/exports
      - ./data/logs:/app/logs
    ports:
      - "8000:8000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - sports_network
    command: >
      sh -c "
        python manage.py migrate &&
        python manage.py collectstatic --noinput &&
        gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --threads 2
      "

  # FastAPI 算法网关
  fastapi:
    build:
      context: ./backend/fastapi
      dockerfile: Dockerfile
    container_name: sports_fastapi
    environment:
      - DATABASE_URL=postgresql://sports_admin:${DB_PASSWORD}@postgres:5432/sports_meet
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - SECRET_KEY=${FASTAPI_SECRET_KEY}
    volumes:
      - ./backend/fastapi:/app
      - ./data/exports:/app/exports
    ports:
      - "8001:8000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - sports_network
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2

  # Nginx 反向代理
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    container_name: sports_nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./frontend/dist:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
      - ./data/uploads:/usr/share/nginx/html/uploads
      - ./data/logs/nginx:/var/log/nginx
    depends_on:
      - django
      - fastapi
    restart: unless-stopped
    networks:
      - sports_network

  # 备份服务(可选)
  backup:
    image: alpine:latest
    container_name: sports_backup
    volumes:
      - ./data/postgres:/source/postgres
      - ./data/uploads:/source/uploads
      - ./backups:/backups
    environment:
      - BACKUP_RETENTION_DAYS=30
    command: |
      sh -c "
        while true; do
          BACKUP_DATE=\$(date +%Y%m%d_%H%M%S)
          tar -czf /backups/backup_\${BACKUP_DATE}.tar.gz /source/postgres /source/uploads
          find /backups -name 'backup_*.tar.gz' -mtime +${BACKUP_RETENTION_DAYS} -delete
          sleep 86400
        done
      "
    restart: unless-stopped
    networks:
      - sports_network

networks:
  sports_network:
    driver: bridge

volumes:
  postgres_data:
  redis_data:
  uploads_data:
  exports_data:
  logs_data:

10.5 Dockerfile 配置

10.5.1 Django Dockerfile

dockerfile 复制代码
# backend/django/Dockerfile
FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    libpq-dev \
    libjpeg-dev \
    zlib1g-dev \
    && rm -rf /var/lib/apt/lists/*

# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目文件
COPY . .

# 创建必要目录
RUN mkdir -p /app/media/uploads /app/media/exports /app/logs /app/static

# 收集静态文件
RUN python manage.py collectstatic --noinput

EXPOSE 8000

CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4", "--threads", "2"]

10.5.2 FastAPI Dockerfile

dockerfile 复制代码
# backend/fastapi/Dockerfile
FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目文件
COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

10.5.3 Nginx Dockerfile

dockerfile 复制代码
# nginx/Dockerfile
FROM nginx:alpine

# 复制配置文件
COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf

# 创建SSL目录
RUN mkdir -p /etc/nginx/ssl

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

10.6 Nginx 配置

nginx 复制代码
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 50M;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss;

    # 上游服务
    upstream django {
        server django:8000 max_fails=3 fail_timeout=30s;
    }

    upstream fastapi {
        server fastapi:8000 max_fails=3 fail_timeout=30s;
    }

    # 主配置
    include /etc/nginx/conf.d/*.conf;
}
nginx 复制代码
# nginx/conf.d/default.conf
server {
    listen 80;
    server_name _;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name sports.school.edu.cn;

    # SSL配置
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # 静态文件
    location /static/ {
        alias /usr/share/nginx/html/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 媒体文件
    location /media/ {
        alias /usr/share/nginx/html/uploads/;
        expires 7d;
        add_header Cache-Control "public";
    }

    # 前端页面
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
        expires 1h;
    }

    # Django API
    location /api/ {
        proxy_pass http://django;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # FastAPI 算法网关
    location /api/arrange/ {
        proxy_pass http://fastapi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 120s;
        proxy_send_timeout 120s;
        proxy_read_timeout 120s;
    }

    # 健康检查
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

10.7 环境变量配置

bash 复制代码
# .env 文件
# 数据库配置
DB_PASSWORD=StrongPassword123!
REDIS_PASSWORD=RedisPassword123!

# Django配置
DJANGO_SECRET_KEY=django-insecure-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ALLOWED_HOSTS=sports.school.edu.cn,localhost,127.0.0.1

# FastAPI配置
FASTAPI_SECRET_KEY=fastapi-secret-key-xxxxxxxxxxxxxxxx

# 管理员账号
ADMIN_USERNAME=admin
ADMIN_PASSWORD=Admin123!
ADMIN_EMAIL=admin@school.edu.cn

10.8 部署步骤

bash 复制代码
#!/bin/bash
# deploy.sh - 一键部署脚本

set -e

echo "=========================================="
echo "运动会智能编排系统 - 一键部署脚本"
echo "=========================================="

# 1. 检查Docker
if ! command -v docker &> /dev/null; then
    echo "错误: Docker未安装,请先安装Docker"
    exit 1
fi

# 2. 创建目录结构
echo "创建目录结构..."
mkdir -p data/{postgres,redis,uploads,exports,logs}
mkdir -p backend/{django,fastapi}
mkdir -p frontend/dist
mkdir -p nginx/ssl
mkdir -p backups

# 3. 复制配置文件
echo "复制配置文件..."
cp -r templates/backend/django/* backend/django/
cp -r templates/backend/fastapi/* backend/fastapi/
cp -r templates/frontend/dist/* frontend/dist/
cp templates/nginx/nginx.conf nginx/
cp templates/nginx/default.conf nginx/conf.d/

# 4. 生成SSL证书(自签名,生产环境请使用正式证书))(可选)
if [ ! -f nginx/ssl/server.crt ]; then
    echo "生成SSL证书..."
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
        -keyout nginx/ssl/server.key \
        -out nginx/ssl/server.crt \
        -subj "/C=CN/ST=Beijing/L=Beijing/O=School/CN=sports.school.edu.cn"
fi

# 5. 设置环境变量
if [ ! -f .env ]; then
    echo "创建环境变量文件..."
    cp .env.example .env
    echo "请修改.env文件中的密码配置"
fi

# 6. 拉取镜像并启动
echo "启动服务..."
docker-compose pull
docker-compose up -d

# 7. 等待服务启动
echo "等待服务启动..."
sleep 10

# 8. 创建超级管理员
echo "创建超级管理员..."
docker exec sports_django python manage.py createsuperuser --noinput \
    --username admin --email admin@school.edu.cn || true

# 9. 初始化数据
echo "初始化系统配置..."
docker exec sports_django python manage.py init_config

# 10. 检查服务状态
echo "检查服务状态..."
docker-compose ps

echo "=========================================="
echo "部署完成!"
echo "访问地址: https://sports.school.edu.cn"
echo "管理员账号: admin"
echo "=========================================="

10.9 数据备份与恢复

10.9.1 自动备份脚本

bash 复制代码
#!/bin/bash
# backup.sh - 数据备份脚本

BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# 备份数据库
docker exec sports_postgres pg_dump -U sports_admin sports_meet > ${BACKUP_DIR}/db_${DATE}.sql

# 备份上传文件
tar -czf ${BACKUP_DIR}/uploads_${DATE}.tar.gz -C /data/uploads .

# 备份导出文件
tar -czf ${BACKUP_DIR}/exports_${DATE}.tar.gz -C /data/exports .

# 删除旧备份
find ${BACKUP_DIR} -name "db_*.sql" -mtime +${RETENTION_DAYS} -delete
find ${BACKUP_DIR} -name "uploads_*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find ${BACKUP_DIR} -name "exports_*.tar.gz" -mtime +${RETENTION_DAYS} -delete

echo "Backup completed at $(date)"

10.9.2 数据恢复脚本

bash 复制代码
#!/bin/bash
# restore.sh - 数据恢复脚本

BACKUP_FILE=$1

if [ -z "$BACKUP_FILE" ]; then
    echo "Usage: ./restore.sh <backup_file>"
    exit 1
fi

# 停止服务
docker-compose stop

# 恢复数据库
docker exec -i sports_postgres psql -U sports_admin sports_meet < ${BACKUP_FILE}

# 恢复上传文件
tar -xzf ${BACKUP_DIR}/${BACKUP_FILE} -C /

# 启动服务
docker-compose start

echo "Restore completed from ${BACKUP_FILE}"

10.10 监控与告警

10.10.1 健康检查端点

python 复制代码
# health.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "timestamp": datetime.now().isoformat(),
        "version": "1.0.0",
        "services": {
            "database": check_database(),
            "redis": check_redis(),
            "storage": check_storage()
        }
    }

10.10.2 日志收集配置

python 复制代码
# logging配置
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/app/logs/sports.log',
            'maxBytes': 1024 * 1024 * 100,  # 100MB
            'backupCount': 10,
            'formatter': 'verbose',
        },
        'error_file': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/app/logs/errors.log',
            'maxBytes': 1024 * 1024 * 50,  # 50MB
            'backupCount': 5,
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['file', 'error_file'],
        'level': 'INFO',
    },
}

11. 附录

11.1 术语表

术语 说明
道次 跑道上的位置编号
组别 比赛的轮次/分组
径赛 跑道上的跑步比赛
田赛 跳跃、投掷类比赛
号码簿 运动员的参赛编号
编排 分配运动员的道次和组别
秩序册 比赛秩序手册,包含赛程、道次等
成绩册 比赛成绩汇总手册
破纪录 打破原有比赛纪录
DQ Disqualified,取消成绩
DNS Did Not Start,未参赛
DNF Did Not Finish,未完赛

11.2 常见问题解答(FAQ)

Q1: 系统支持多少个项目同时编排?

A: 系统支持无限个项目,编排算法的时间复杂度为O(n²),建议每个项目单独编排。

Q2: 如何处理两个运动员成绩完全相同的情况?

A: 系统支持两种并列处理方式:

  • 同名次:两人并列第一,下一个是第三名
  • 名次顺延:两人并列第一,下一个是第二名

Q3: 数据迁移如何进行?

A: 系统提供Excel导入导出功能,可以通过模板进行数据迁移。同时也支持数据库级别的备份恢复。

Q4: 系统是否支持多学校使用?

A: 当前版本为单学校设计。如需多学校支持,可在数据库层面增加school_id字段进行隔离。

Q5: 如何自定义报表模板?

A: 系统提供报表模板配置功能,可以在后台自定义报表的样式和内容。高级定制需要修改前端代码。

相关推荐
白开水就盒饭2 小时前
《数据挖掘(主编:吕欣、王梦宁)》读书笔记总结
python·mysql·数据挖掘·知识图谱
软件开发技术深度爱好者2 小时前
轻量、安全、易用的HTML测试运行预览工具
前端·html
莫生灬灬2 小时前
NewEmoji 93个组件演示,支持emoji,支持易语言/火山/C#/Python
开发语言·python·c#
creaDelight2 小时前
Django 中间件钩子函数 & CBV vs FBV 实战验证
python·中间件·django
hnjzsyjyj3 小时前
洛谷 B4359:[GESP202506 三级] 分糖果 ← 贪心算法
贪心算法
Maydaycxc3 小时前
企业内网 RPA 离线部署实践:从选型到落地的完整方案
运维·chrome·python·selenium·自动化·rpa
MY_TEUCK11 小时前
【2026最新Python+AI学习基础】Python 入门笔记篇
笔记·python·学习
赢乐12 小时前
大模型学习笔记:检索增强生成(RAG)架构
人工智能·python·深度学习·机器学习·智能体·幻觉·检索增强生成(rag)
浪里行舟14 小时前
你的品牌正在被AI“遗忘”?用BuildSOM找回搜索的下一个风口
人工智能·python·程序员