简介:面向Python课程设计及数据库初学者的田径运动会管理系统完整代码与数据库包。系统覆盖运动员信息管理、比赛项目配置、成绩录入与查询、排名展示等典型功能,将Python后端逻辑、SQLite数据库操作与GUI界面开发整合于一体,适合作为课程设计参考或实战练手项目。压缩包共35个文件,以4个Python源码文件、7个编译pyc、15张界面运行截图为核心,另含SQL建表脚本、3个Excel示例数据、可执行exe及项目配置文件,共11.24MB。已有280人学习浏览,对需要快速理解项目结构、复用界面设计或查阅数据库表关系的学习者较为友好。附带图形界面预览图可帮助比对实际运行效果,SQL脚本与Excel数据便于初始化测试环境,整体目录清晰,便于对照源码理解模块划分。
1. 拿到这份「Python课程设计田径运动会管理系统源码+数据库.zip」后,先搞懂它在交付什么
「Python课程设计田径运动会管理系统源码+数据库.zip」这类压缩包,是计算机专业课程设计里出现频率很高的题目。运动会管理系统本身业务不复杂:运动员报名、项目维护、成绩录入、按名次得分、出团体总分公示。但难就难在它把数据库增删改查、多表关联、聚合查询和界面事件全串在一条完整的业务链上,这正好是评委想看的课程设计工作量。压缩包里通常就是一份Python源码加一份数据库脚本,拿到手之后的第一件事不是解压就跑,而是先看懂它把业务拆成了哪几张表、几条链路,再决定哪些模块自己重写、哪些直接保留。这份拆解适合两类人:准备交作业的在校生,以及想用现成骨架练手改代码的初学者。
2. 运动会管理系统拆解:三个角色、六大模块和一条业务链
2.1 三个用户角色与权限边界
这类系统的权限设计,课程设计阶段通常不会上RBAC权限模型,而是登录后根据用户表里的 role 字段切换界面。管理员能进全部页面,管理班级、运动员、项目等基础数据;裁判也就是成绩录入员,只能进成绩录入和查询页面;游客和普通学生直接看公示页,不用登录。这样做的实际好处是,答辩时提到"权限控制"不至于被追问太深,但又确实展示了不同角色看到不同视图的机制。
常见做法是在 users 表里存 username、password、role 三个字段,登录成功后把 role 放到全局变量或主窗口的属性里,创建页面时按角色决定隐藏哪些按钮。这里有个被问到的惯性问题:密码是明文存储的。被评委问到就直接说,这个项目的业务重点是数据流正确性,密码加密属于安全增强项,后续可以接 hashlib 做 SHA256 再加盐,不用现场硬编。
提示:课程设计包的 users 表默认密码大多是 admin / 123456。拿到手先改一次密码,免得演示时被台下同学顺手登进去。
2.2 六大功能模块与流程串起来
把运动会系统拆成模块,核心就是下面六个,对应关系如下表:
| 功能模块 | 核心操作 | 对应数据表 | 界面表现 |
|---|---|---|---|
| 班级管理 | 班级增删改查 | classes | 表格 + 表单 |
| 运动员管理 | 运动员增删改查、按班级筛选 | athletes | 表格 + 搜索框 |
| 项目管理 | 项目维护,区分田赛与径赛 | events | 下拉框 + 表格 |
| 报名管理 | 运动员选项目,限制重复报名 | signups | 双下拉框联动 |
| 成绩录入 | 选项目、选运动员、填成绩 | results | 表格内编辑 |
| 排名公示 | 单项名次、团体总分 | results + 聚合SQL | 排行榜 |
整个系统的业务闭环是:赛前管理员建班级、建运动员、建项目,然后给运动员批量报名;赛中裁判按项目逐项录入成绩;赛后任何人查单项名次和团体总分。答辩演示的主线就是这个顺序,从赛前到赛后一条线走通,比零散点按钮更有说服力。
在这个闭环里,报名模块是第一个容易出问题的地方:同一个运动员重复报名同一个项目,在界面上只是二次提示,数据库层面必须用唯一约束兜底。我一般会在 signups 表上加 UNIQUE(athlete_id, event_id),这样就算界面漏判,数据库也会拒绝重复记录。下面的函数会给所有查询页面复用,在界面代码里可以直接拿它去初筛重复报名:
import pymysql def fetch_all(conn, sql, args=()): """所有查询页面共用的方法,返回字典列表""" with conn.cursor(pymysql.cursors.DictCursor) as cur: cur.execute(sql, args) return cur.fetchall()用 DictCursor 让每一行变成字典,界面代码不用按数字下标取值,可读性高很多;with 块会自动关闭游标,避免连接被耗尽。这个函数是全项目最值得保留的骨架代码。
2.3 为什么运动会管理系统适合做课程设计
同样都是CRUD管理系统,图书管理、学生选课这类题目拼的是界面布局,业务逻辑相对平。运动会管理系统的独特之处在于,成绩不是单一维度的数据:径赛比时间,数值越小排名越靠前;田赛比距离或高度,数值越大排名越靠前。这个差异直接导致排名逻辑不能写死成一句 ORDER BY,必须区分项目类型做反向排序,正好给了你展示代码设计能力的位置。
团体总分又是另一层:成绩表里存的是每个运动员单场的名次和得分,班级总分要通过 JOIN 运动员表再 GROUP BY 班级聚合出来。一次多表关联加聚合查询,恰好覆盖了数据库增删改查里最有含金量的部分。而且这类系统数据量天然不大,一个班几十个运动员、六个项目就能把演示做得非常完整,不需要像电商系统那样造几千条假数据,对课程设计来说性价比很高。
3. 数据库设计:五张表、外键约束和导入命令一次说清
3.1 表结构设计思路与关联关系
整套系统的核心数据表是五张:班级表、运动员表、项目表、报名表、成绩表。用户表单独算辅助表。设计上有一个关键取舍:报名关系单独拆成 signups 表,而不是把项目编号直接挂在运动员表里。因为一个运动员可以报多个项目,一个项目有多个运动员,这是典型的多对多关系,拆一张中间表是最规范的做法。
建库建表SQL常见写法是这样,注意字符集和存储引擎这两个参数:
CREATE DATABASE IF NOT EXISTS sports_meeting DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE sports_meeting; CREATE TABLE classes ( id INT AUTO_INCREMENT PRIMARY KEY, class_name VARCHAR(50) NOT NULL UNIQUE, remark VARCHAR(100) ) ENGINE=InnoDB; CREATE TABLE athletes ( id INT AUTO_INCREMENT PRIMARY KEY, athlete_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender ENUM('男','女') NOT NULL, class_id INT NOT NULL, CONSTRAINT fk_athlete_class FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE ) ENGINE=InnoDB; CREATE TABLE events ( id INT AUTO_INCREMENT PRIMARY KEY, event_name VARCHAR(50) NOT NULL, event_type ENUM('run','field') NOT NULL COMMENT 'run=径赛, field=田赛', gender ENUM('男','女') NOT NULL, unit VARCHAR(10) DEFAULT '秒' ) ENGINE=InnoDB; CREATE TABLE signups ( id INT AUTO_INCREMENT PRIMARY KEY, athlete_id INT NOT NULL, event_id INT NOT NULL, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_athlete_event (athlete_id, event_id), CONSTRAINT fk_signup_athlete FOREIGN KEY (athlete_id) REFERENCES athletes(id) ON DELETE CASCADE, CONSTRAINT fk_signup_event FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE ) ENGINE=InnoDB; CREATE TABLE results ( id INT AUTO_INCREMENT PRIMARY KEY, signup_id INT NOT NULL, score_value DECIMAL(8,2) NOT NULL, rank_no INT DEFAULT NULL, points INT DEFAULT 0, CONSTRAINT fk_result_signup FOREIGN KEY (signup_id) REFERENCES signups(id) ON DELETE CASCADE ) ENGINE=InnoDB;几个容易被问到的参数:字符集用 utf8mb4 而不是 utf8,是因为 utf8 在 MySQL 里最多存3字节,遇到生僻字和 Emoji 会报错,utf8mb4 是完整实现;ENGINE=InnoDB 是为了让外键约束真正生效,MyISAM 不支持外键;DECIMAL(8,2) 存成绩,8位有效数字、2位小数,100米跑11.35秒、跳远7.85米都够用。田赛和径赛共用 score_value 一个列,不拆两套成绩表,排序方向由项目表的 event_type 决定。
3.2 导入数据库命令与连接参数
拿到 .sql 文件后,打开命令行终端,进到文件所在目录,执行这条命令导入:
mysql -u root -p < sports_meeting.sql-u 指定用户名,-p 表示提示输入密码,< 把文件内容重定向给 mysql 客户端执行。如果这条命令报 1366 或其他编码错误,先看第5章的排查方法。导入完成之后,用 SHOW TABLES; 检查五张核心表是否齐全,缺哪张就单独再导哪张。
Python 代码里的数据库连接参数是第二个修改重点,一般在项目里会有一个 db_config.py 之类的文件集中管理:
DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "123456", "database": "sports_meeting", "charset": "utf8mb4", }实际要改的通常只有 password 和 database 两项。host 保持 127.0.0.1 而不是 localhost,能避开一部分 MySQL 客户端走 Unix socket 导致的服务找不到问题。如果你拿到的是 SQLite 版本的数据库,连接参数会变成 db_path 指向一个 .db 文件,但表结构设计思路是一样的。
3.3 预置数据与外键顺序:这是导入老翻车的点
导入完整SQL文件通常没问题,问题出在你往里面补测试数据的时候。比如要给班级加运动员,必须先插班级,拿到自增 id,再插运动员。外键约束要求父表先有记录,子表才能引用。下面这组预置数据就是标准的先后顺序:
INSERT INTO classes (class_name) VALUES ('计科2101'), ('软工2102'); INSERT INTO athletes (athlete_no, name, gender, class_id) VALUES ('2021001', '张三', '男', 1), ('2021002', '李四', '男', 1), ('2021101', '王五', '女', 2); INSERT INTO events (event_name, event_type, gender, unit) VALUES ('100米', 'run', '男', '秒'), ('跳远', 'field', '男', '米'), ('铅球', 'field', '女', '米');如果你想把某个班级的 id 固定成 1 或 2,可以显式写 id 列,不写让自增自己分配。但如果成绩表里已经有关联数据,改 classes 表的 id 会导致外键断裂,这是经验里最坑的一处。另一个常见操作是要用 mysql 数据库修改结构时,给现有表加个索引,比如成绩表经常按名次排序,就加一个普通索引:
ALTER TABLE results ADD INDEX idx_rank (rank_no);加索引的理由是后面排名公示的查询会大量按 rank_no 过滤和排序,数据量虽小但演示时不卡顿更保险。
4. 核心代码走读:从登录到团体总分,闭环怎么串起来的
4.1 主程序入口与数据库连接封装
课程设计包的主程序入口一般长这样,用 Tkinter 做界面,主窗口创建后直接摆登录页:
import tkinter as tk from ui_login import LoginFrame def main(): root = tk.Tk() root.title("田径运动会管理系统") root.geometry("1200x700") LoginFrame(root).pack(fill="both", expand=True) root.mainloop() if __name__ == "__main__": main()Tkinter 是 Python 标准库,不用 pip 装,但 Linux 系统上经常缺 tk 运行时,运行报 ModuleNotFoundError: No module named 'tkinter' 时,需要先安装 python3-tk。Windows 上一般没这个问题。这里的 pack(fill="both", expand=True) 是让登录页铺满主窗口,窗口拉大时组件跟着撑开。
数据库连接封装是第二个骨架,所有界面模块都调用它取连接。常见写法是一个 get_conn 函数加一个 fetch_all 函数搭配使用:
import pymysql from db_config import DB_CONFIG def get_conn(): try: return pymysql.connect(**DB_CONFIG) except pymysql.err.OperationalError as e: print("数据库连接失败,请确认 MySQL 服务已启动:", e) return Nonepymysql 不是标准库,首次运行前要执行 pip install pymysql。如果界面程序是反复开窗关窗的模式,连接不要每次都新建,最好在登录成功后建一次,存在主窗口对象上,子页面共用,程序退出时统一关闭,这样能避免把 MySQL 的连接数打满。
4.2 成绩录入与排名计算:田赛径赛两种排序规则
排名计算是整套代码里最像黑匣子的地方,也是答辩被问概率最高的函数。核心逻辑是:取出某个项目全部有效成绩,按项目类型排序,径赛时间小者排名靠前,田赛距离大者排名靠前,然后按名次写入 points 得分。前八名得分数组建议单独定义成常量,方便改得分规则:
EVENT_POINTS = [9, 7, 6, 5, 4, 3, 2, 1] def build_ranking(conn, event_id): """按项目生成成绩排名,并回写名次与得分""" rows = fetch_all(conn, """ SELECT a.athlete_no, a.name, c.class_name, r.score_value, r.id AS result_id FROM results r JOIN signups s ON r.signup_id = s.id JOIN athletes a ON s.athlete_id = a.id JOIN classes c ON a.class_id = c.id WHERE s.event_id = %s """, (event_id,)) ev = fetch_all(conn, "SELECT event_type FROM events WHERE id=%s", (event_id,))[0] if ev['event_type'] == 'run': rows.sort(key=lambda x: x['score_value']) else: rows.sort(key=lambda x: x['score_value'], reverse=True) for idx, row in enumerate(rows): row['rank_no'] = idx + 1 row['points'] = EVENT_POINTS[idx] if idx < len(EVENT_POINTS) else 0 conn.cursor().execute( "UPDATE results SET rank_no=%s, points=%s WHERE id=%s", (row['rank_no'], row['points'], row['result_id'])) conn.commit() return rows这里把排序放在内存里做而不是直接写 ORDER BY,是因为同样一个 score_value 列,径赛和田赛方向正好相反。写两套 SQL 还要拼接其他筛选条件,容易出错;内存排序代码量少,而且一个项目的参赛人数通常不超过几十人,性能完全不是问题。sort 的 key 参数取 score_value,reverse 由 event_type 决定,是这段逻辑里最需要讲清楚的两个参数。
成绩录入界面的逻辑则是先查报名关系、再插成绩,防止录入界面漏判。关键代码是:
def save_result(self): aid = self.athlete_var.get() eid = self.event_var.get() score = self.score_entry.get() sid = fetch_all(self.conn, "SELECT id FROM signups WHERE athlete_id=%s AND event_id=%s", (aid, eid)) if not sid: messagebox.showerror("录入失败", "该运动员没有报名这个项目") return insert_result(self.conn, "INSERT INTO results (signup_id, score_value) VALUES (%s, %s)", (sid[0]['id'], score))先查后插的顺序能兜住大部分误操作。录入界面上项目下拉框、运动员下拉框的数据应该来自同一个 fetch_all 结果集,保证运动员和项目都是库里真实存在的。
4.3 班级总分统计:一条SQL替代N次循环
统计团体总分时最容易写歪的写法,是在 Python 里循环每个班级,再各自查一次成绩表。数据少时看不出问题,但代码显得低级。一次 JOIN 加 GROUP BY 就能算出来,还顺带统计了金牌数:
SELECT c.id, c.class_name, SUM(r.points) AS total_points, COUNT(CASE WHEN r.rank_no = 1 THEN 1 END) AS golds FROM results r JOIN signups s ON r.signup_id = s.id JOIN athletes a ON s.athlete_id = a.id JOIN classes c ON a.class_id = c.id GROUP BY c.id, c.class_name ORDER BY total_points DESC, golds DESC;这条 SQL 的逻辑是:results 通过 signups 关联到运动员,运动员再关联到班级,然后用 GROUP BY 按班级分组。SUM(points) 算总分,COUNT(CASE WHEN rank_no = 1 THEN 1 END) 数金牌;ORDER BY 先按总分降序,总分一样时金牌多的排前面。把这段 SQL 在答辩时用口头拆解一遍,比讲十个界面代码更能体现 SQL 功底。
这个查询在界面里一般会再包一层函数,返回列表直接喂给排行榜表格。注意 GROUP BY 后面的字段必须和 SELECT 里的非聚合列一致,MySQL 的 ONLY_FULL_GROUP_BY 模式在 5.7 之后默认开启,少写一个分组列会直接报错。
5. 避坑清单:把这套系统跑起来的五个翻车现场
5.1 现象:import pymysql 直接报 ModuleNotFoundError,界面起不来
原因:Python 环境里没有装 pymysql。很多课程设计包是在别人机器上跑通的,直接打包成一个 zip 传出来,依赖却不跟着走。
解决:命令行执行pip install pymysql。装完之后再看代码里有没有额外 import,比如 openpyxl、matplotlib,根据报错逐个补齐。这里最常见的后续坑是 pip 下载慢或超时,用国内镜像源一次性解决:
pip install pymysql openpyxl matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple5.2 现象:mysql -u root -p < sports_meeting.sql 导入时报 ERROR 1366,中文全变成问号
原因:终端客户端字符集和 SQL 文件编码不一致。Windows 下 cmd 默认 GBK,而 .sql 文件是 UTF-8 写的,直接重定向导入就会乱码。
解决:先执行SET NAMES utf8mb4;再导入,或者把 .sql 文件用编辑器另存为 UTF-8 无 BOM 格式。更稳妥的方式是用 MySQL 客户端工具里的 source 命令:
mysql -u root -p mysql> SET NAMES utf8mb4; mysql> source /path/to/sports_meeting.sql;5.3 现象:程序启动后连接数据库报 OperationalError: (2003, ...) 或者 Can't connect to MySQL server
原因:MySQL 服务没有启动。这个报错和密码无关,纯粹是端口 3306 上没有进程在监听。
解决:Windows 上按 Win+R 输入 services.msc,找到 MySQL80 或对应的 MySQL 服务,右键启动,再把启动类型设为自动。如果你改过 MySQL 的端口,同步修改 DB_CONFIG 里的 port 值。这类问题十次有八次是服务没起来,剩下两次是端口被占用改了默认值,检查顺序也是这个顺序。
5.4 现象:成绩排序后 10:09 排在 9:58 前面
原因:score_value 里存的是 "10:09" 这种文本格式,Python 的 sort 按字符串逐字符比较,"1" 比 "9" 小,所以 10:09 反而排在前面。这是时间型成绩最典型的血泪经验。
解决:库里面不要存格式化文本,存纯数字秒数。如果数据已经按文本存了,排序前先把文本转成秒,转换函数如下:
def time_to_seconds(txt): parts = txt.strip().split(':') if len(parts) == 1: return float(parts[0]) if len(parts) == 2: return int(parts[0]) * 60 + float(parts[1]) return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])计算排名时用 time_to_seconds 处理,展示时再格式化回 "分:秒"。这个转换函数建议直接放进项目公共工具模块里,别写在界面类里面。
5.5 现象:MySQL 8 里连接报 Authentication plugin 'caching_sha2_password' cannot be loaded
原因:MySQL 8 默认的认证插件是 caching_sha2_password,而老版本 pymysql 不认识它,或者你本地 MySQL 用户用的是老插件。
解决:连接参数里显式指定认证插件:
DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "123456", "database": "sports_meeting", "charset": "utf8mb4", "auth_plugin": "mysql_native_password", }或者修改数据库用户的认证方式,执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';,改完需要刷新权限并重启服务。两种方案选连接参数的方式更省事,不动数据库配置。
6. 答辩演示脚本:三分钟讲完系统,加三个加分扩展
6.1 三分钟演示路线
演示顺序比什么都重要。第一步打开系统,登录管理员账号,讲权限角色,十秒内略过;第二步打开班级和运动员管理,点一次增删改查,突出 athlete_no 唯一约束;第三步打开项目管理,讲田赛径赛分类,然后演示报名,故意点一个重复报名,让界面弹出"已报名"提示,这会显得设计有预判;第四步进入成绩录入,录一组数据后调出排名页,按刚录入的数据生成名次和得分;第五步打开班级总分排行榜,把 SQL 里 GROUP BY 的思路带一句。全程控制在三分钟内,重点只讲数据从哪来到哪去。
6.2 三个加分扩展
第一个是给排行榜加一张柱状图。用 matplotlib 读取班级总分,画横向条形图,注意中文显示要设置字体:
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False不设这两行,中文标签会变成方块,这一项能直接扣掉印象分。第二个是导出成绩单,用 openpyxl 把成绩表写成 Excel 文件,答辩时导出一份"运动会成绩公示.xlsx",比口头说"可以导出"更有说服力。第三个是把程序打包成 exe,PyInstaller 打包 Tkinter 程序时常见做法是:
pip install pyinstaller pyinstaller -F -w main.py --hidden-import pymysql-F 打成单文件,-w 隐藏控制台窗口,pymysql 属于动态导入,必须加 hidden-import 否则打包后运行会报找不到模块。
6.3 环境的优先级高于代码
带过的课程设计小组里,交上来的包十个有九个卡在环境问题上:Python 版本不对、pymysql 没装、MySQL 服务没启动、SQL 文件导不进去。我自己的教训是,拿到这种源码包先按"数据库导入 → 连接测试 → 登录界面 → 业务操作"的顺序过一遍,环境通了再改功能,别一上来就对着界面代码改布局。这个顺序能救回大量时间,希望帮到你。
本文还有配套的精品资源,点击获取