简介:这是一份Python课程设计田径运动会管理系统源码与数据库打包,面向需要完成数据库或Python课程设计的高校学生,提供了一套可直接运行的运动会管理项目。压缩包共35个文件、约11.24MB,包含4个.py源码、1个.sql数据库脚本、3个.xlsx运动员及成绩数据表;另附可直接运行的exe客户端、pyc编译文件与15张功能界面截图,覆盖登录、信息查询、成绩录入、项目分组、名次排名等核心模块。目前已有280人学习下载。项目源码清晰分离了数据库操作、Tkinter图形界面与业务逻辑三个部分,可学习如何设计运动员表、项目表、成绩表和排名表之间的关联,以及成绩录入、排名计算等功能的实现;配合演示Excel与Visual Studio工程文件,便于快速运行、调试和二次开发,是课程设计展示和Python数据库实战入门的理想资料。
1. 从登录框到成绩单,这套课设代码最值钱的是数据链路
接手一套课程设计代码,最怕的不是界面丑,而是答辩时被问到"成绩排名怎么算出来的""删掉一个运动员之后成绩表会怎样"这类数据链路问题就卡壳。田径运动会管理系统这套源码,把最常见的业务场景完整走了一遍:登录需要账号表校验,运动员信息要支持编号和姓名两种查询方式,录入成绩要同时校验运动员表和项目表,名次不能手填而是由排序逻辑生成,最后还要按学院出汇总报表。从压缩包里的文件列表可以清晰看出这是个分层项目——DBclass.py 负责数据库操作、GUI.py 负责界面交互、client.py 是启动入口、createCD.sql 是建表脚本,旁边还配了 Excel 示例数据和已打包好的 client.exe。正在找 Python 课程设计参考的人,可以拿着它直接跑通流程;想把这套骨架改成仓库管理、图书管理等其他场景的开发者,也能从表结构和统计逻辑里直接复用经验。
2. 文件结构反推架构:DBclass 负责连接,GUI 负责交互
2.1 源码包里的文件到底在做什么
解压压缩包之后先别急着双击 exe,把文件和目录过一遍,架构图基本就出来了。这个项目的文件组织非常典型,是一个"入口 — 界面 — 数据层"三件套结构,我把关键文件整理成了下面的对应关系。
| 文件 | 职责推断 | 判断依据 |
|---|---|---|
| client.py | 程序启动入口 | 有同名 client.spec,说明打包入口是它 |
| GUI.py | 图形界面与事件绑定 | 截图里的登录界面、功能选择界面都对应这里的窗口类 |
| DBclass.py | 数据库连接与 SQL 封装 | 类名直接是 DB,提供查询和写入方法 |
| account_information_entry.py | 管理员账号信息入口 | 登录界面和账号表的校验逻辑会调用它 |
| createCD.sql | MySQL 建库建表脚本 | .sql 后缀,包含建库和 create table 语句 |
| 运动员信息.xlsx | 运动员基础数据示例 | 界面"添加运动员信息"和批量导入共用这个格式 |
| 运动员成绩.xlsx | 成绩数据导出模板 | 成绩单导出时按这个结构写 Excel |
| demo.xlsx | 演示用数据集 | 空库时导入它可以直接演示全部功能 |
| client.exe | 已打包的可执行文件 | PyInstaller 产物,运行不需要安装 Python |
| 登录失败.png / 查无此人.png | 异常分支截图 | 说明项目里做了失败提示,不是只画了主流程 |
这里有一个容易被忽略的细节:__pycache__里全部是.cpython-310.pyc,说明源码是在 Python 3.10 环境下编译运行的。如果你本机是 3.8 或者 3.11,直接跑 client.py 一般没问题,但如果重新打包 exe,建议统一到 3.10,避免第三方依赖在不同版本下行为不一致。
2.2 DBclass.py 里的数据库连接设计
数据层是整套系统的地基。虽然文件名只是简单的 DBclass,但它承担了连接管理、SQL 执行、事务提交三件事。常见做法是用 PyMySQL 连接 MySQL,连接参数集中写在类初始化方法里。
import pymysql class DB: def __init__(self, host="localhost", user="root", password="123456", database="track_meet"): self.conn = pymysql.connect( host=host, user=user, password=password, database=database, charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor ) def query(self, sql, args=None): with self.conn.cursor() as cursor: cursor.execute(sql, args) return cursor.fetchall() def execute(self, sql, args=None): with self.conn.cursor() as cursor: affected = cursor.execute(sql, args) self.conn.commit() return affected四个连接参数是最容易踩坑的地方:host 默认写 127.0.0.1 只能连本机 MySQL,客户机上如果数据库跑在另一台服务器,这里要改成对应 IP;password 要和本地 MySQL 实际密码一致,课设代码里默认的 123456 十有八九和你环境对不上;database 名称必须与 createCD.sql 里的建库语句同名,否则报Unknown database;charset 建议锁死 utf8mb4,否则导入 Excel 里的中文姓名会乱码。
query和execute两个方法的返回值设计值得学习:查询返回字典列表,写入返回受影响行数。界面层判断"到底插进去没有"时,直接看 execute 的返回值,大于 0 才弹"添加成功",比 try-except 硬扛要清晰得多。这套封装对课程设计来说刚好,既不臃肿又能应付增删改查。
2.3 GUI.py 与业务逻辑的耦合
GUI.py 用的是 Tkinter,这是 Python 自带的标准库,不需要额外安装第三方界面框架。它的逻辑是拿到 db 对象,把数据库操作直接嵌在按钮回调里,典型写法如下:
import tkinter as tk from tkinter import ttk, messagebox from DBclass import DB class MainWindow: def __init__(self, db): self.db = db self.root = tk.Tk() self.root.title("田径运动会管理系统") self.keyword = tk.StringVar() def search_athlete(self): keyword = self.keyword.get().strip() sql = ("SELECT no, name, college FROM athlete " "WHERE no LIKE %s OR name LIKE %s") rows = self.db.query(sql, (f"%{keyword}%", f"%{keyword}%")) if not rows: messagebox.showwarning("提示", "查无此人") else: self.show_result(rows)界面层直接用%s占位符做参数化查询,把用户输入当作参数传给 execute,而不是拼进 SQL 字符串,这个习惯挡住了 SQL 注入。截图里会看到"编号或姓名.png"和"姓名查询.png"两张图,对应的就是LIKE匹配的两条路径——输入纯数字时可以走编号精确匹配,输入中文时走姓名模糊匹配,界面共用一个输入框,底层 SQL 用 OR 连接。
需要注意 Tkinter 的 StringVar 要在变量绑定控件前创建,否则控件拿不到字符串变量的引用,输入框内容永远是空字符串。很多课设代码在输入框里怎么输都查不到结果,问题往往出在这一行。
3. createCD.sql 里的表关系与名次计算路径
3.1 四张表撑起一个运动会
打开 createCD.sql,核心是四张业务表的设计。我按字段职责把它们拆开看,关系非常清楚。
| 表名 | 关键字段 | 作用 |
|---|---|---|
| account | account_no, password | 管理员登录账号 |
| athlete | no, name, college, gender | 运动员基本信息 |
| event | event_no, event_name | 比赛项目字典表 |
| score | id, athlete_no, event_no, score, rank | 成绩记录与名次 |
运动员表和项目表是字典数据,成绩表是关系数据。一条成绩记录必须同时关联一个运动员和一个项目,所以 score 表里 athlete_no 和 event_no 都设置成外键。
CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, athlete_no VARCHAR(20) NOT NULL, event_no INT NOT NULL, score DECIMAL(8, 2) NOT NULL, rank INT DEFAULT NULL, FOREIGN KEY (athlete_no) REFERENCES athlete(no), FOREIGN KEY (event_no) REFERENCES event(event_no) );score 字段用 DECIMAL(8,2) 而不是 FLOAT,是因为田径成绩有大量精确到百分之一秒的径赛数据,FLOAT 在反复比较时会产生浮点误差,DECIMAL 是定点数,排序和相等判断都稳定。rank字段允许为空,在成绩尚未录入完时保留 NULL,只有排名计算跑过之后才填充。外键约束的意义在于:录入成绩时如果运动员编号不存在,数据库直接拒绝写入,这比在 Python 里先查一次再插入要可靠得多。
3.2 成绩录入为什么要做项目分组校验
界面截图里有一张"输入运动员参加的项目编号.png",这个设计就是为了校验运动员和项目的对应关系。一个运动员可以报名多个项目,但一个运动员在同一个项目里只能有一条成绩,否则排名统计会出现重复数据。所以插入成绩时不能只插 athlete_no 和 event_no,还要先查一遍运动员是否报名该项目。
SELECT COUNT(*) FROM registration WHERE athlete_no = %s AND event_no = %s;如果没有 registration 表,退一步的做法是在 score 表上建联合唯一索引,用数据库兜底保证同一运动员同一项目只能有一条成绩记录。这个索引在成绩录入场景下还有一个附带好处:按运动员查成绩单、按项目排名次都走这个索引,查询速度会明显快于全表扫描。
3.3 名次计算:一条 UPDATE 搞定窗口函数排序
名次是管理系统里最容易做成反面教材的功能。很多课程设计里是查到成绩后在 Python 里排序循环,再逐条 UPDATE 回数据库,数据量小的时候看不出问题,一旦同一项目有几百条成绩,循环更新既慢又容易在中间出错。正确做法是把排名计算放到数据库里,用一条 UPDATE 加上窗口函数完成。
UPDATE score s JOIN ( SELECT athlete_no, event_no, RANK() OVER (PARTITION BY event_no ORDER BY score ASC) AS rk FROM score ) t ON s.athlete_no = t.athlete_no AND s.event_no = t.event_no SET s.rank = t.rk;这段 SQL 的逻辑核心是RANK() OVER (PARTITION BY event_no ORDER BY score ASC)。PARTITION BY 表示按项目编号分组,每个项目内部独立排名;ORDER BY score ASC 表示成绩数值越小排名越靠前,适用于径赛短跑这类"用时少者胜"的项目。如果是跳远、铅球这类田赛项目,成绩越大越好,把 ORDER BY 改成 DESC 即可。
RANK()与DENSE_RANK()的差别在于并列名次:两个并列第一之后,RANK 会跳过第二名直接排第三,DENSE_RANK 则不会跳。对运动会场景来说 RANK 更合理,因为沉甸甸的奖牌榜里没有"第二名位置空缺"的说法。要注意的是窗口函数要求 MySQL 8.0 及以上版本,如果是 5.7 就得用用户变量来模拟分组排名,逻辑会绕一些。
3.4 学院成绩汇总的聚合查询
截图里有"学院成绩.png"和"输入学院信息.png",对应的是按学院统计总分的报表页面,SQL 落在两表 JOIN 加分组聚合上。
SELECT a.college, COUNT(DISTINCT s.athlete_no) AS athlete_count, SUM(s.score) AS total_score FROM athlete a JOIN score s ON a.no = s.athlete_no GROUP BY a.college ORDER BY total_score DESC;这里有两个容易出错点。第一,COUNT 必须加 DISTINCT 限定到 athlete_no,因为一个运动员可能参加多个项目产生多条成绩记录,直接 COUNT(*) 会把总人数统计成总参赛人次。第二,SUM(s.score) 只对已录入的成绩生效,如果某个运动员缺考没有成绩记录,JOIN 结果里根本不会出现他,学院总分自然不包含缺考项。业务上如果要求缺考按 0 分计算,就得先 LEFT JOIN 再对 NULL 做 IFNULL 处理。
4. 登录鉴权、信息查询、成绩录入的完整流程拆解
4.1 登录失败不是界面问题,是账号表没初始化
压缩包里有一张"登录失败.png"的截图,这是相见恨晚的一张图。登录失败最常见的原因不是密码输错,而是 account 表里根本没有数据。createCD.sql 建完表之后是空表,你要先插入一条管理员记录,登录界面才有账号可验证。
INSERT INTO account (account_no, password) VALUES ('admin', 'admin123');登录界面在真实的代码里要做两层判断:第一层是输入框非空校验,两个输入框任一个为空就直接提示"账号或密码不能为空",不发起数据库查询;第二层才查库比对账号和密码。比对时注意密码字段不要以明文形式展示在数据库中,课程设计级别可以用 hashlib 做一次 SHA-256 散列再存储。
import hashlib raw_password = "admin123" hashed = hashlib.sha256(raw_password.encode("utf-8")).hexdigest()账号密码校验通过之后,界面才会销毁登录窗口,创建功能选择主窗口。这个流程对应截图里"登录界面.png"到"功能选择界面.png"的跳转关系。
4.2 编号或姓名查询的两种检索路径
"编号或姓名.png"和"姓名查询.png"两张截图暴露了查询功能的两种入口,在实现上共用同一个查询方法,靠 SQL 参数不同区分路径。如果输入是纯数字,优先走编号精确匹配;包含中文就按姓名模糊匹配。实际上用一个 OR 条件就能同时覆盖两个入口。
def search_athlete(self, keyword): sql = ("SELECT no, name, college, gender " "FROM athlete WHERE no = %s OR name LIKE %s") rows = self.db.query(sql, (keyword, f"%{keyword}%")) return rows值得注意的细节是编号字段no在数据库里通常定义成 VARCHAR 而不是 INT,因为运动员编号可能带有前缀或前导零,用 INT 类型会把 "001" 存成 1,查询时前端传 "001" 反而匹配不上。这是一个典型的"看着能跑但数据不对"的坑,课程设计代码里保留字符串类型,说明作者踩过这个坑。
4.3 成绩录入界面与运动员成绩单的联动
"成绩录入界面.png"和"运动员成绩单.png"之间的关系,是录入和查询两条通路在数据层的交汇。录入成绩时,界面会提供运动员选择下拉框和项目选择下拉框,选完以后自动带入运动员编号,避免手工输入编号产生的格式错误。
实际代码里两个下拉框的数据源是两个独立的数据库查询:
athletes = self.db.query("SELECT no, name FROM athlete") events = self.db.query("SELECT event_no, event_name FROM event")下拉框显示中文名,存储值绑定编号,这是 GUI 开发里最常见的"显示值与存储值分离"模式。Tkinter 的 ttk.Combobox 通过一个字典实现这个映射,选中运动员名字时拿到的是 athlete_no 而不是姓名文本。成绩录入完点保存后,Treeview 表格马上刷新,新纪录出现在"运动员成绩单"列表的底部,这个即时反馈依赖插入成功后重新执行一次 SELECT,而不是手动拼接界面数据。
4.4 成绩修改与删除的级联约束策略
成绩录错了要改,运动员退赛要删,这两件事都涉及 score 表的级联处理。score 表的外键如果设置了ON DELETE CASCADE,删除运动员时数据库会把他的所有成绩一并删除,应用层不需要写任何历史清理代码。
FOREIGN KEY (athlete_no) REFERENCES athlete(no) ON DELETE CASCADE这对运动会管理系统是合理的策略:运动员信息删除后,保留他过去的成绩没有业务意义。但要注意报表页面最好对级联删除给用户一个二次确认,因为成绩一旦跟着删除,学院统计的总分也会立刻变化。界面上对应的做法是删除按钮回调里先弹 messagebox.askyesno,确认后才调用 DELETE 语句。
5. 用 PyInstaller 打包成 exe 与二次改造的三种做法
5.1 client.spec 透露的打包细节
压缩包里的 client.spec 是 PyInstaller 生成的打包配置文件,它决定了 exe 的入口、依赖和窗口属性。最简单的打包命令是下面这行,在 client.py 同级目录执行即可。
pyinstaller client.specspec 文件里的核心配置决定了产物形态:
a = Analysis( ['client.py'], pathex=['.'], hiddenimports=['pymysql', 'openpyxl'], noarchive=False, ) exe = EXE( pyz, a.scripts, exclude_binaries=True, name='田径运动会管理系统', console=False, )console=False 是最容易忽略的参数之一。课程设计演示时如果控制台窗口和 GUI 窗口一起弹出来,观感很差,设成 False 可以只保留 GUI。调试时反过来,把它临时改成 True,PyMySQL 连接报错、未捕获异常都会打印在黑窗口里,定位问题比瞎猜快得多。
hiddenimports 里列出 pymysql 和 openpyxl 很关键。PyInstaller 静态分析 import 语句时,对通过字符串动态导入的模块会漏掉。pymysql 的 connections 模块和 openpyxl 的 reader 模块都是典型的动态导入目标,不写进 hiddenimports,打包出的 exe 运行到一半会报 ModuleNotFoundError,而且只在实际触发数据库连接或 Excel 导出的那一刻才暴露。
5.2 运行时报错的排查路径
拿到源码先别急着改功能,按下面的顺序把环境跑通,报错概率降一半。
- 先看 MySQL 服务有没有启动,Windows 下用
net start mysql或服务管理器确认。 - 用命令行客户端测试连接:
mysql -u root -p,密码不对就先用 root 登录改掉 DBclass.py 里的 password 参数。 - 登录 MySQL 后执行
source createCD.sql建表,注意建库语句里的库名必须和 DBclass.py 的 database 参数严格一致。 - 插入初始管理员账号,否则登录界面永远弹"登录失败"。
- 双击 client.exe 前先在命令行运行
client.exe,Windows 下 GUI 程序的报错信息会被吞掉,命令行里跑能看到 traceback。
双击 exe 没反应是最常见的打包问题,八成是上面第二步或者第四步没做完。命令行跑一下,错误信息会直接告诉你连不上数据库还是账号表为空。
5.3 把课设改成仓库管理系统的换皮思路
这套骨架并不只属于田径运动会。它的表结构本质是"主表 + 记录表 + 统计表"的通用模式,改造成其他管理系统的操作路径是从数据层往上换皮。
第一层改库。把 athlete 表改成 student 表或者 goods 表,把 no、name、college 三个字段换成业务对应的编号、名称、所属分类;event 表对应物料分类或课程列表;score 表对应库存变动或选课记录。第二层改 SQL。排名窗口函数换成库存汇总的SUM(quantity) GROUP BY goods_no,学院成绩统计换成按部门汇总的金额统计。第三层改界面。把 GUI.py 里的 Tkinter Label 文本和 Treeview 列名替换成新业务名词,项目分组下拉框的查询语句把SELECT event_no FROM event改成SELECT category_id FROM category,其余绑定逻辑原样保留。
换完以后用 demo.xlsx 造 100 条假数据,从登录到统计完整走一遍。能稳定输出结果,说明这套骨架已经接住了新业务。课程的答辩重点是排名计算的窗口函数和 Excel 导入的联动逻辑,这两块是数据库课程设计里最能体现能力深度的位置,讲清楚比堆界面有意义得多。
本文还有配套的精品资源,点击获取