本项目是一个基于 Vue 3 + FastAPI + PostgreSQL 的射箭赛事积分系统,当前实现覆盖赛事配置、报名导入与管理、成绩导入与管理、年度积分排名查询。
- 前端:Vue 3、Vite、Vue Router、Axios、XLSX
- 后端:FastAPI、SQLAlchemy、PostgreSQL
- 部署方式:Docker Compose
- 数据初始化:
database/init.sql
当前前端只有 3 个主页面:
-
积分排名- 路径:
/points-display - 文件:
frontend/src/views/PointsDisplay.vue - 功能:
- 选择年度与弓种查看年度积分排名
- 支持姓名、俱乐部筛选
- 支持导出 Excel
- 路径:
-
赛事配置- 路径:
/event-add - 文件:
frontend/src/views/EventAdd.vue - 功能:
- 选择赛年、赛季创建或更新赛事
- 导入报名表(仅支持 Excel)
- 查看与编辑已导入报名
- 根据报名数据同步赛事配置中的个人人数
- 配置团体和混双人数
- 路径:
-
导入成绩- 路径:
/score-import - 文件:
frontend/src/views/ScoreImport.vue - 功能:
- 选择赛事
- 导入成绩(仅支持 Excel)
- 导入时按报名表校验成绩是否合法
- 查看赛事配置预览
- 查看与编辑当前赛事已有成绩
- 路径:
- 赛事按
年度 + 赛季唯一 - 赛事配置按
性别分组 + 弓种 + 距离维护 - 个人人数来自报名表统计,不支持手工修改
- 团体人数支持
男子组 / 女子组 / 混合组 - 混双人数仅支持
混合组
- 报名表存储在
event_registrations - 报名导入仅支持 Excel
- 导入时支持:
- 距离模糊匹配
- 比赛弓种模糊匹配
- 分组按性别分组字典模糊匹配
- 报名唯一性规则:
同年度 + 同赛季 + 同姓名 + 同距离 + 同比赛弓种
- 当比赛弓种为
无瞄弓时:- 积分弓种只允许
光弓 / 美猎弓 / 传统弓
- 积分弓种只允许
- 当比赛弓种不是
无瞄弓时:- 积分弓种固定等于比赛弓种
- 报名导入、编辑、删除后会同步更新赛事配置中的个人人数
- 成绩表存储在
scores - 成绩唯一性规则:
同赛事 + 同姓名 + 同距离 + 同弓种 + 同赛制
- 批量导入时,如果唯一键重复,则覆盖更新
rank - 成绩导入仅支持 Excel
- 成绩导入要求列:
姓名 / 弓种 / 距离 / 赛制 / 排名
- 导入时按当前赛事对应赛季的报名表做校验:
- 用
姓名 + 距离 + 弓种匹配报名 - 未匹配到报名记录时,该行成绩标记为异常
- 用
- 积分页调用
GET /api/scores/annual-ranking/{year}/{bow_type} - 查询人群来自报名表:
- 当年
points_bow_type = 所选弓种的报名人员
- 当年
- 积分计算规则:
- 基础积分:
- 排位赛:
- 第 1-8 名分别为
25 / 22 / 19 / 15 / 10 / 8 / 6 / 4 - 超出前 8 名时基础积分按
1计
- 第 1-8 名分别为
- 淘汰赛:
- 第 1-4 名分别为
45 / 40 / 35 / 30 - 第 5-8 名统一为
20 - 第 9-12 名统一为
15 - 第 13-16 名统一为
10 - 超出前 16 名时基础积分按
1计
- 第 1-4 名分别为
- 团体赛、混双赛:
- 第 1-8 名分别为
20 / 15 / 10 / 8 / 5 / 4 / 3 / 2 - 超出前 8 名时基础积分按
1计
- 第 1-8 名分别为
- 排位赛:
- 人数/队伍数系数:
- 排位赛、淘汰赛使用“个人人数系数表”
8-15人:系数0.6,仅前4名保留原额基础积分16-31人:系数0.8,仅前8名保留原额基础积分32-63人:系数1.0,仅前16名保留原额基础积分64-127人:系数1.2,仅前16名保留原额基础积分128+人:系数1.4,仅前16名保留原额基础积分
- 团体赛、混双赛使用“队伍数系数表”
3-4队:系数0.6,仅前2名保留原额基础积分5-7队:系数0.8,仅前4名保留原额基础积分8-10队:系数1.0,仅前8名保留原额基础积分11-14队:系数1.2,仅前8名保留原额基础积分15+队:系数1.4,仅前8名保留原额基础积分
- 排位赛、淘汰赛使用“个人人数系数表”
- 组别系数:
- 根据
弓种 + 距离在competition_groups中匹配组别 S / A组:系数1.0B组:系数0.5C组:系数0.3- 未匹配组别时按
0
- 根据
- 各赛制人数来源:
- 排位赛、淘汰赛:
- 根据成绩所属
赛季 + 姓名 + 距离 + 弓种在报名表中匹配报名记录 - 再根据报名记录的性别分组匹配赛事配置中的
individual_participant_count - 若未匹配到有效人数或人数为
0,按8计
- 根据成绩所属
- 团体赛:
- 根据成绩的
event_id + 弓种 + 距离在赛事配置中取team_count - 姓名不含
*时,按男子组 -> 混合组顺序取第一个大于0的值 - 姓名含
*时,按女子组 -> 混合组顺序取第一个大于0的值 - 若都没有有效值,按
3计
- 根据成绩的
- 混双赛:
- 根据成绩的
event_id + mixed + 弓种 + 距离取mixed_doubles_team_count - 若未匹配到有效值或人数为
0,按3计
- 根据成绩的
- 排位赛、淘汰赛:
- 最终积分计算顺序:
- 先取基础积分
- 再按人数/队伍数确定系数和“可保留原额积分的排名上限”
- 若排名超出上限,则基础积分改为
1 - 最终积分 =
基础积分 × 系数 × 组别系数
- 基础积分:
- 俱乐部展示取该年度该姓名最早一次报名记录中的俱乐部
管理功能使用管理员密码认证:
- 登录接口:
POST /api/auth/login - 请求体:
{"password": "明文密码"} - 服务端校验逻辑:
- 对明文密码做
SHA-256 - 与
.env中的SECRET_KEY进行比对
- 对明文密码做
- 登录成功后返回 JWT
- 前端将 token 保存到
localStorage.admin_auth_token - 管理页面:
/event-add/score-import需要已登录管理员
GET /POST /api/auth/loginGET /api/events/yearsGET /api/dictionariesGET /api/scores/annual-ranking/{year}/{bow_type}
-
赛事
GET /api/eventsGET /api/events/{event_id}POST /api/events/with-configs
-
赛事配置
POST /api/event-configurationsPUT /api/event-configurations/{config_id}DELETE /api/event-configurations/{config_id}
-
报名
GET /api/event-registrationsPOST /api/event-registrations/batch/importPUT /api/event-registrations/{registration_id}DELETE /api/event-registrations/{registration_id}
-
成绩
GET /api/scoresPOST /api/scores/batch/importPUT /api/scores/{score_id}DELETE /api/scores/{score_id}
sin29-champion-points-system
├── backend/
│ ├── app/
│ │ ├── models/
│ │ ├── routers/
│ │ ├── schemas/
│ │ └── services/
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── api/
│ │ ├── router/
│ │ ├── styles/
│ │ ├── utils/
│ │ └── views/
│ └── package.json
├── database/
│ ├── init.sql
│ └── migrations/
├── docker-compose.yml
├── README.md
├── QUICK_START.md
└── DATABASE_DESIGN.md
- 启动与验收:
QUICK_START.md - 数据库结构:
DATABASE_DESIGN.md