分享一套锋哥原创的SpringBoot4+Vue3运动会管理系统
大家好,我是Java1234_小锋老师,分享一套锋哥原创的SpringBoot4+Vue3运动会管理系统。
项目介绍
随着高校体育事业的不断发展,运动会作为学校体育工作的重要组成部分,其组织规模和参与人数日益扩大。传统的运动会管理大多依赖人工方式完成报名登记、资格审核、成绩统计与名次排定等工作,存在效率低下、数据易错、查询不便、信息孤岛等诸多问题,已经难以满足现代高校精细化、信息化管理的需求。因此,设计并实现一套功能完善、操作便捷的运动会管理系统具有重要的现实意义。
本文针对高校运动会管理的实际业务需求,设计并实现了一套基于 SpringBoot 与 Vue3 的前后端分离运动会管理系统。系统后端采用 SpringBoot 作为核心框架,结合 MyBatis-Plus 持久层框架实现对数据的高效访问,使用 MySQL 作为数据存储,并借助 JWT 实现无状态的登录认证;前端采用 Vue3 框架,配合 Element Plus 组件库与 ECharts 可视化图表库构建美观、响应式的用户交互界面,通过 Axios 完成前后端数据通信。系统按角色划分为管理员与学生两类用户,主要实现了登录认证、用户管理、学院班级管理、比赛项目管理、报名与审核、成绩录入与排名、数据统计与公告管理等功能模块。
本文从系统的需求分析、可行性分析入手,详细阐述了系统的功能模块设计、数据库设计以及核心功能的具体实现过程,并对系统进行了功能测试。测试结果表明,该系统运行稳定、功能完整、操作友好,能够有效提升高校运动会的组织与管理效率,具有较好的实用价值。
源码下载
链接: https://pan.baidu.com/s/1AIHjxMiADWnRgOuoxUZOpQ?pwd=1234
提取码: 1234
系统展示
![]()
![]()
![]()
![]()
![]()
![]()
核心代码
package com.java1234.sports.controller; import com.java1234.sports.common.Result; import com.java1234.sports.entity.ClassInfo; import com.java1234.sports.service.ClassInfoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 班级管理控制器 * * @author Java1234_小锋老师 */ @RestController @RequestMapping("/api/class") public class ClassController { @Autowired private ClassInfoService classInfoService; /** * 查询班级列表 */ @GetMapping("/list") public Result<List<ClassInfo>> list() { return Result.success(classInfoService.listWithCollege()); } /** * 根据学院ID查询班级 */ @GetMapping("/byCollege/{collegeId}") public Result<List<ClassInfo>> byCollege(@PathVariable Long collegeId) { return Result.success(classInfoService.listByCollegeId(collegeId)); } /** * 新增班级 */ @PostMapping public Result<?> add(@RequestBody ClassInfo classInfo) { classInfoService.save(classInfo); return Result.success(); } /** * 修改班级 */ @PutMapping public Result<?> update(@RequestBody ClassInfo classInfo) { classInfoService.updateById(classInfo); return Result.success(); } /** * 删除班级 */ @DeleteMapping("/{id}") public Result<?> delete(@PathVariable Long id) { classInfoService.removeById(id); return Result.success(); } }<template> <div class="page-container"> <!-- 统计卡片 --> <el-row :gutter="16" style="margin-bottom: 16px;"> <el-col :span="6"> <div class="stat-card"> <div class="stat-label">用户总数</div> <div class="stat-num">{{ stats.userCount || 0 }}</div> </div> </el-col> <el-col :span="6"> <div class="stat-card green"> <div class="stat-label">运动员数</div> <div class="stat-num">{{ stats.studentCount || 0 }}</div> </div> </el-col> <el-col :span="6"> <div class="stat-card orange"> <div class="stat-label">比赛项目</div> <div class="stat-num">{{ stats.eventCount || 0 }}</div> </div> </el-col> <el-col :span="6"> <div class="stat-card blue"> <div class="stat-label">报名总数</div> <div class="stat-num">{{ stats.registrationCount || 0 }}</div> </div> </el-col> </el-row> <!-- 图表区域 --> <el-row :gutter="16"> <el-col :span="12"> <div class="chart-card"> <div class="chart-title">各项目报名人数</div> <div ref="barChartRef" style="height: 350px;"></div> </div> </el-col> <el-col :span="12"> <div class="chart-card"> <div class="chart-title">运动员性别分布</div> <div ref="pieChartRef" style="height: 350px;"></div> </div> </el-col> </el-row> <el-row :gutter="16" style="margin-top: 16px;"> <el-col :span="12"> <div class="chart-card"> <div class="chart-title">项目类型分布</div> <div ref="typeChartRef" style="height: 350px;"></div> </div> </el-col> <el-col :span="12"> <div class="chart-card"> <div class="chart-title">班级团体总分排行</div> <div ref="rankChartRef" style="height: 350px;"></div> </div> </el-col> </el-row> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue' import * as echarts from 'echarts' import { getDashboardStats } from '@/api' const stats = ref({}) const barChartRef = ref() const pieChartRef = ref() const typeChartRef = ref() const rankChartRef = ref() let charts = [] const initCharts = (data) => { // 柱状图 - 各项目报名人数 const barChart = echarts.init(barChartRef.value) barChart.setOption({ tooltip: { trigger: 'axis' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: (data.eventRegStats || []).map(i => i.name), axisLabel: { rotate: 30 } }, yAxis: { type: 'value', minInterval: 1 }, series: [{ type: 'bar', data: (data.eventRegStats || []).map(i => i.count), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#667eea' }, { offset: 1, color: '#764ba2' } ])}, barWidth: '40%', borderRadius: [6, 6, 0, 0] }] }) charts.push(barChart) // 饼图 - 性别分布 const pieChart = echarts.init(pieChartRef.value) pieChart.setOption({ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, legend: { bottom: 0 }, color: ['#409eff', '#f56c6c'], series: [{ type: 'pie', radius: ['40%', '65%'], center: ['50%', '45%'], data: data.genderStats || [], label: { formatter: '{b}\n{c}人' }, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.2)' } } }] }) charts.push(pieChart) // 饼图 - 项目类型 const typeChart = echarts.init(typeChartRef.value) typeChart.setOption({ tooltip: { trigger: 'item' }, legend: { bottom: 0 }, color: ['#11998e', '#f093fb'], series: [{ type: 'pie', radius: '60%', center: ['50%', '45%'], data: data.typeStats || [], roseType: 'radius', label: { formatter: '{b}: {c}项' } }] }) charts.push(typeChart) // 柱状图 - 班级排行 const rankChart = echarts.init(rankChartRef.value) rankChart.setOption({ tooltip: { trigger: 'axis' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'value' }, yAxis: { type: 'category', data: (data.classRankStats || []).map(i => i.name).reverse() }, series: [{ type: 'bar', data: (data.classRankStats || []).map(i => i.score).reverse(), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ { offset: 0, color: '#4facfe' }, { offset: 1, color: '#00f2fe' } ])}, barWidth: '50%', borderRadius: [0, 6, 6, 0] }] }) charts.push(rankChart) } const handleResize = () => charts.forEach(c => c.resize()) onMounted(async () => { const res = await getDashboardStats() stats.value = res.data initCharts(res.data) window.addEventListener('resize', handleResize) }) onUnmounted(() => { window.removeEventListener('resize', handleResize) charts.forEach(c => c.dispose()) }) </script>