基于Python和Vue3的校园学科竞赛管理系统开发实践
2026/9/6 9:14:12 网站建设 项目流程

简介:面向高校教育信息化与学科竞赛管理场景的一份完整毕业设计文档,采用Python+Django+Vue+MySQL技术栈,基于B/S三层架构实现竞赛信息的即时发布、规范化报名、成绩统计与排名等核心功能,适合计算机相关专业学生借鉴系统设计与论文写作思路。文档详细覆盖学生模块、竞赛信息模块、报名竞赛模块、成绩排名模块及管理员审核流程,并对系统架构、数据库设计与实现方案进行了系统阐述;内容预览中还包含中英文摘要、关键词及完整论文结构,便于快速了解整体框架与关键技术细节。压缩包内为1个doc文件,整体大小约15.93MB,已有50人学习下载。对正在筹备竞赛管理类课题或需要参考Django与Vue前后端整合方案的研究者而言,是一份结构清晰、可直接用于论文撰写与项目起步的实用资料。 写这个管理系统的时候,我其实已经用光了本科四年攒下的所有“动手能力”。从最初只想在论文里画几张用例图糊弄过去,到后来认认真真把前后端拆开、部署上线、跑完整个报名评分流程,三个月过去,我对“校园学科竞赛管理系统”这九个字的理解完全不一样了。这篇不聊那些从需求分析抄到验收标准的套话,只讲我实际怎么做、为什么这么选、以及联调阶段真实踩过的坑,给准备用 Python 和 Vue3 做类似管理系统的同学一条可以复现的路。

1. 竞赛管理平台:需求痛点和系统定位

1.1 手工作坊式办赛的三大痛点

很多学校现在的学科竞赛管理还停留在“辅导员发Excel表、学生填完发回来、老师再手工汇总”的阶段。表面看能跑通,实操过就知道有多折磨:报名信息格式五花八门,有的学生把手机号写成“188-xxxx-xxxx”,有的把组员名单直接塞在备注里;评委打分靠纸质打分表,统计的时候得一个人对着计算器按半天;最头疼的是多轮比赛的状态追踪,复赛名单、决赛时间、获奖公示全靠群消息通知,漏一个学生就是事故。

所以这个系统的第一个核心定位就是:把报名、审核、比赛进程、评分、成绩公布这条链条搬到线上,让每个环节都有据可查、有迹可循。

1.2 系统边界:管理端、教师端、学生端三端划分

需求梳理阶段我做了很多轮减法。最开始也想过做移动端小程序、加消息推送、做在线答辩视频模块,后来全部砍掉了。毕设系统最重要的是逻辑闭环,而不是功能堆砌。我最终保留的是三个明确的角色域:

  • 学生端:查看竞赛公告、在线报名、提交作品、查看个人成绩和获奖信息
  • 教师端(评委):对分配到的作品打分、填写评语、查看自己参与评审的竞赛列表
  • 管理端(管理员):发布竞赛、审核报名、分配评委、管理作品、录入/审核成绩、发布获奖公示

三端共用同一套用户体系,后端通过角色字段控制接口权限。这样设计的好处是:权限模型简单清晰,论文里好画图,答辩时好讲,代码实现也省事——不需要引入复杂的 RBAC 框架,一个中间件判断角色就够了。

1.3 系统用例与核心流程

整个系统的核心流程可以压缩成一句话:管理员发布竞赛 -> 学生报名 -> 管理员审核报名 -> 学生提交作品 -> 管理员分配评委 -> 评委打分 -> 系统自动计算均分 -> 管理员发布成绩和获奖名单。

这条主链路决定了数据库的四张核心业务表——竞赛表、报名表、作品表、评分表。所有的设计决策,包括字段命名、外键关系、接口粒度,都是围绕这条链路展开的。我建议所有准备做类似系统的同学,动手写代码之前先把这条链路捋清楚,它比任何需求文档都好用。

2. 技术选型背后:Python、Vue3 与毕设场景的匹配

2.1 为什么后端用 Python 而不是 Java

后端选 Python 并不是因为它比 Java“好”,而是因为它更适合这个项目的体量和开发节奏。Django 框架自带 Admin 后台,我开发初期几乎没写一行后台管理代码,就靠 Admin 快速录入了测试数据,这让前期的功能验证变得极其高效。等核心业务接口写完,Admin 后台还能作为管理端的一个“保底方案”——即使前端页面没做完,也不影响演示核心流程。

另一个现实理由是答辩环境的不确定性。用 Python 写的后端,在答辩现场的笔记本上重新跑起来的成本远低于 Java 工程。装好 Python 环境、pip 装上依赖、跑一次迁移脚本,几分钟就能起服务。Java 那套 JDK 版本、Maven 依赖、配置文件的连锁问题,在演示当天任何一个环节出错都足够让人崩溃。

2.2 为什么前端选 Vue3 而不是 React

Vue3 在毕设场景下的优势体现在三个方面。第一是学习曲线平缓,Composition API 的写法比 React 的 Hooks 规则更直观,没有闭包陷阱、依赖数组这些容易让新手懵的概念。第二是中文生态极其友好,Element Plus 组件库的中文文档、各类超详细教程到处都是,遇到问题几乎都能搜到现成答案。第三是和 Vite 搭配的开发体验非常舒服,热更新速度快,改完代码浏览器立刻生效,这在赶工期的阶段是实打实的效率提升。

Vue3 里我大量使用了组合式函数来封装可复用的逻辑。比如一个usePagination组合式函数,把分页的当前页、每页条数、总数、加载方法封装在一起,列表页只需要调用一次,就能少写一大段重复代码。这在写论文的技术章节时也是一个亮点——“通过 Composition API 实现逻辑复用”,比干巴巴地写“使用了 Vue3 框架”有说服力得多。

2.3 项目结构:前后端分离后的目录设计

前后端分离是我从一开始就定下的架构。项目分了两个独立的目录,互不干扰:

backend/ manage.py config/ # 项目配置 apps/ users/ # 用户模块 competitions/ # 竞赛模块 registrations/ # 报名模块 works/ # 作品模块 scores/ # 评分模块 announcements/ # 公告模块 requirements.txt frontend/ src/ api/ # axios 接口封装 assets/ components/ # 通用组件 router/ stores/ # pinia 状态管理 views/ admin/ teacher/ student/ utils/

这个结构的好处是:后端按业务模块拆 app,前端按角色分目录,论文里的系统架构图画出来一目了然,代码也容易找。前端我用 Pinia 做了全局状态管理,主要存用户登录信息和角色,路由守卫根据角色控制页面访问权限。导航菜单也是动态渲染的——不同角色登录后看到的菜单项完全不同。

3. 系统数据建模与接口设计:稳定性的地基

3.1 数据库表设计与关系拆解

数据库设计是整个系统我最重视的部分。表设计不合理,后面写多少代码都别扭。我用了 MySQL,核心表一共八张,这里列一下主题结构:

表名核心字段说明
usersusername, password, role, real_name, student_no统一用户表,role 区分学生/教师/管理员
competitionstitle, description, category, start_time, end_time, status竞赛公告,status 控制报名/进行/结束
registrationsuser, competition, team_name, members, status报名记录,status 为待审核/通过/驳回
worksregistration, title, file, submit_time作品表,一个报名记录对应一份作品
scoreswork, judge, score, comment评分表,一个作品对应多条评分
awardscompetition, registration, rank获奖表,发布公示时生成
announcementstitle, content, publish_time系统公告
competition_judgescompetition, judge竞赛和评委的多对多关系

用户表我把三种角色放在一张表里,用 role 字段区分,而不是拆成学生表、教师表。因为三种角色有大量公共字段(姓名、账号、联系方式),拆表会导致登录和权限判断变得异常复杂。对于这个体量的系统,“单用户表 + 角色字段”是最务实的选择。

3.2 权限模型:用 Django 的 Group 还是自建 Role

Django 自带 Group 和 Permission,理论上可以用它们实现权限控制。但我实践后放弃了,原因只有一个:写论文的时候,解释“如何使用 Django 内置 RBAC”远比解释“自己实现了一个基于角色的访问控制”要费劲,而且内置权限的数据结构对评委这个角色并不友好——一个评委只能给“分配给他”的竞赛打分,这种资源级权限是 Group 权限解决不了的。

我最终的做法是:基础权限用role字段 + 装饰器判断,资源级权限(比如评委只能看到自己负责的竞赛)在视图层手动过滤。听起来简单,但实际效果非常好。每次请求进来,先通过中间件解析 JWT 拿到用户 ID 和角色,然后视图里根据角色做数据过滤。逻辑直白,排查问题也方便。

3.3 API 风格与状态码约定

接口我用 RESTful 风格,基于 Django REST Framework 实现。路径设计遵循资源嵌套原则:

POST /api/auth/login/ 登录 GET /api/competitions/ 竞赛列表 POST /api/competitions/ 管理员创建竞赛 POST /api/competitions/{id}/register/ 学生报名 GET /api/competitions/{id}/registrations/ 查看某竞赛的报名列表 POST /api/works/ 提交作品 POST /api/works/{id}/score/ 评委打分 GET /api/competitions/{id}/results/ 查看成绩

状态码约定也提前定好:200 成功、201 创建成功、400 参数错误、401 未登录、403 无权限、404 资源不存在。前端 Axios 拦截器统一处理,遇到 401 就清除登录状态跳回登录页。这些约定看起来基础,但如果没有提前定清楚,前后端联调阶段光是字段名和错误码就能撕扯一周。

4. 核心功能落地:报名、审核、评分、统计的实现细节

4.1 竞赛发布与报名:日期校验和名额控制的常见坑

竞赛发布时最容易出问题的是时间字段的处理。前端传的是“2024-05-20 10:00:00”这种格式,后端 Django 的 DateTimeField 能自动解析,但有一个坑:前端如果传带T的 ISO 字符串(比如2024-05-20T10:00:00),DRF 也能解析,但存进数据库后,前端再读出来时格式可能变成带T和时区的,导致页面显示不统一。

我最后的处理方案是:后端统一使用 Django 的USE_TZ = False,关闭时区转换,存 Naive DateTime;前端在 API 层统一封装日期格式化函数,展示时按YYYY-MM-DD HH:mm:ss格式输出。别小看这个统一,系统的报名、比赛、公示三个环节全依赖时间判断,时间格式不一致会引发连锁 bug。

名额控制也是实际场景里必须考虑的问题。每个竞赛可以设置最大报名人数,报名接口里用select_for_update()加上行级锁,先查当前人数再判断是否允许报名,避免高并发下超员。毕设环境不会有真实并发压力,但写这个处理逻辑在论文里是一个明确的技术亮点。

4.2 作品提交:文件上传与格式校验

作品提交是多媒体文件上传,我用 Django 的FileField存文件路径,前端用FormData提交,Axios 不手动设置 Content-Type,让浏览器自动生成带 boundary 的 multipart 格式。

上传格式校验分两层:前端选文件时通过accept属性限制类型,后端再根据Content-Type和扩展名二次校验。后端校验不能省,因为接口是可以直接调用的,绕开前端页面提交一个.exe文件完全可行。文件大小限制我设置在 50MB,Django 的FILE_UPLOAD_MAX_MEMORY_SIZE和 Nginx 的client_max_body_size两处都要配置,只配一处会出现“本地能传、服务器上传不了”的诡异问题。

文件存储路径我用了competition/{competition_id}/按竞赛分目录,并且对文件名做了重新命名,避免中文名和非法字符导致服务器兼容问题。这个细节在答辩演示上传文件时能省掉很多尴尬。

4.3 评委评分:多个评委取均分的算法设计

评分模块是业务逻辑的关键点。一个竞赛多个评委,每个评委独立打分,最终成绩取均分。最开始的简单实现是:成绩表加一个final_score字段,每次新评分写入时重新计算所有评分的平均数并更新。

这个方案能工作,但有个隐患:如果某评委临时被替换或某份作品被取消资格,历史评分删除后均分可能不准,而且每次重算的时机容易出 bug。我后来改成视图层动态计算:查询成绩时用 Django ORM 的Avg聚合函数现场算均分,配合Count统计评委人数。这样数据源永远是最新的,代码里没有任何“缓存后忘记更新”的机会。

还有一种情况是比赛规则要求“去掉最高分和最低分再取平均”,这个用 ORM 不太好写,我用了原始 SQL 解决,在模型里自定义Manager方法实现。答辩时这段代码可以重点讲一讲,属于“理论算法在真实业务中的落地”。

4.4 数据可视化:用 ECharts 输出竞赛数据大屏

系统加了一个数据统计页:各学院参赛人数对比、各竞赛报名人数趋势、获奖分布饼图、评委打分分布直方图。前端用 ECharts 实现,后端提供几个聚合接口,用 ORM 的annotate配合CountSum直接在数据库层完成统计,返回给前端的就是可以直接用于图表渲染的 JSON 数据。

比如各学院参赛人数的统计,核心代码就一行:

Registration.objects.values('user__college').annotate(count=Count('id'))

Django ORM 的价值在这里体现得特别明显。手写 SQL 当然也能实现,但 ORM 的返回结构更规整,而且对表关联的查询逻辑有更强的可读性。Vue3 里图表组件我用一个BaseChart包装了一下,只需要传入 option 配置对象,不同图表之间切换数据源即可。可视化页面在答辩现场是最抓眼球的一部分,强烈建议所有毕设系统都加。

5. 前后端联调实测:跨域、响应式与鉴权的排错记录

5.1 跨域问题:CORS 配置与前端代理的取舍

前后端分离后第一个迎面而来的问题就是跨域。开发环境下前端跑在http://localhost:5173,后端跑在http://localhost:8000,端口不同,浏览器默认拒绝携带非简单请求的跨域访问。我最初用django-cors-headers在后端直接放开 CORS,方便是方便,但放开后前端的所有请求都能打进来,和“无鉴权”没什么区别。

正确做法分环境:开发环境在后端配置允许http://localhost:5173来源;生产环境则不允许所有来源,而是通过 Nginx 反向代理把/api/路径转发到后端,前端请求走同源地址,跨域问题根本不存在。开发和生产用不同的配置方式,这事我在部署阶段反复折腾了好几次才彻底理清。

5.2 Vue3 响应式陷阱:表格数据不同步的排查过程

这个坑我是真金白银踩出来的。写一个列表页,从接口拿到数组后直接赋值给一个reactive声明的数组变量,页面却死活不更新。查了半天才发现是 Vue3 响应式代理的问题:reactive的深层代理数组整体被替换成新数组时,可能会丢失原有响应式连接。

排查链路是这样的:先在 Vue Devtools 里看数据确实已经更新了,页面上却没有反应;再检查赋值语句state.list = res.data.data,看起来没问题;最后查文档确认了 Vue3 的响应式机制——对reactive对象的属性重新赋值是响应式的,但对象内部数组元素的替换要看触发的方式。

解决办法有三个:一是用ref声明数组,赋值用xxx.value = data,这是最顺手的;二是用state.list.splice(0, state.list.length, ...data)保留引用原地替换;三是Object.assign合并。我最终统一用ref方案,并且所有列表页都按这个约定写,再没出过同类问题。

5.3 Token 过期与 Axios 拦截器的统一处理

JWT 认证在系统里承担了所有接口的鉴权。登录后前端存两个 token:access短令牌(2小时过期)和refresh刷新令牌(7天过期)。正常情况下是 access 过期后用 refresh 换新的 access,但这个闭环我一开始没做,导致用户用着用着突然就跳回登录页,体验极差。

后来我在 Axios 响应拦截器里加了统一处理:请求返回 401 时,先尝试用 refresh token 调用刷新接口,成功则更新 access token 并重放原请求,失败才清空登录状态跳登录页。这个逻辑说起来简单,但写的时候要注意防止并发请求同时刷新 token 的问题,我用了let isRefreshing = false加一个等待队列的方式,保证同时间只有一个刷新请求在飞。这些都是生产环境才会暴露出来的细节,写在论文里会让开发过程显得很扎实。

5.4 时间字段在不同环境下的显示错乱

这个问题的表现是:本地测试时间正常,部署到服务器后所有报名时间都差了 8 小时。排查结论是 MySQL 时区、Django 的TIME_ZONE、服务器系统时区三者之间没有对齐。我的处理是:MySQL 连接参数里加OPTIONS: {'init_command': "SET time_zone = '+08:00'"},同时 Django 设置TIME_ZONE = 'Asia/Shanghai',然后把所有涉及时间输出的地方统一交给前端格式化,后端一律返回 ISO 标准字符串。从此时间再也没有出过问题。

6. 部署、演示与论文写作:从代码到答辩的最后一公里

6.1 从本地开发到服务器部署的简易路径

我部署用的是最常规的方案:一台云服务器,MySQL 放数据库,后端 Gunicorn 跑 Django 应用,前端npm run build出来的静态文件交给 Nginx 托管,Nginx 同时承担反向代理,把/api/请求转发给 Gunicorn。

部署有几个容易卡住的细节:Python 依赖环境建议用虚拟环境,我用了uv来管理,比 pip 快得多;Django 的settings.pyDEBUG = False之后,静态文件的收集方式会变,需要跑collectstatic;Gunicorn 配置文件里 worker 数量不要拍脑袋写,我按服务器核数设的,2 * CPU + 1,实测效果稳定。前端方面,Nginx 需要配置try_files $uri $uri/ /index.html,否则刷新前端路由会 404。

6.2 演示系统的三分钟演示脚本

到答辩阶段我意识到一个残酷的事实:代码写得再好,演示拉胯一样扣分。我提前准备了三条演示路径,每一条都是“开口讲一句业务场景 -> 操作 -> 看效果”的节奏。

第一条是管理员完整创建一场竞赛、审核两个报名、分配评委;第二条是评委登录给作品打分,打出后立刻看成绩页均分变化;第三条是学生登录报名并提交作品,然后去个人中心看状态流转。三条路径串起来正好覆盖主链路的每一个核心功能,时间控制在三分钟左右。我把所有演示用的账号密码贴在笔记本键盘旁边,避免现场登录时手忙脚乱。

6.3 论文技术章节的写作思路

论文写作和写代码是两种完全不同的思维方式。代码要的是逻辑严谨,论文要的是“问题 -> 方案 -> 验证”这条叙事线。我的论文技术章节没有按前后端模块平铺直叙,而是挑了几个核心技术点深入展开:权限控制方案、报名并发控制、评分算法、文件上传跨域处理。每个点都是先说明遇到了什么具体问题,再讲我的方案,最后展示验证结果。

这样的写法比“系统包含用户管理模块、竞赛管理模块、作品管理模块……”这种流水账有说服力得多。答辩老师问的问题,基本也都围绕这几个点展开,因为这些问题代表了你对系统真实的理解程度。当初联调阶段踩的那些坑,反而成了论文和答辩里最值钱的素材。


最后说一点个人体会。做管理系统类毕设,最大的风险不是技术难,而是功能看起来“太简单”——担心答辩老师一句“这不就是一个 CRUD 吗”就把自己问住。我自己的应对方式是找两个深水区:一个是在并发控制、权限设计、文件处理这类工程问题里做足够的考察和实验,一个是把数据统计可视化做得超出预期。这两块投入的时间不多,但在答辩效果上的回报是立竿见影的。

如果你也在做同类系统,建议把精力优先花在数据模型的完整性和核心链路的闭环上,功能宁可少而精,不要多而糙。系统的报名、审核、评分、发布这四个核心动作能跑通,就已经是一个合格的毕设系统了。剩下的那些旁枝末节,等核心立住了再加也不迟。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询