当前位置: 首页 > wzjs >正文

浙江新地标建设集团网站网络营销案例100例

浙江新地标建设集团网站,网络营销案例100例,网页设计实验总结100字,wordpress中文房产主题项目介绍 本项目是一个基于Java全栈技术开发的田径运动会管理系统,旨在为学校、体育场馆提供一个完整的运动会管理解决方案。系统采用前后端分离架构,实现了运动员管理、比赛项目管理、成绩录入、数据统计等核心功能。 技术栈 后端技术 Spring Boot …

项目介绍

本项目是一个基于Java全栈技术开发的田径运动会管理系统,旨在为学校、体育场馆提供一个完整的运动会管理解决方案。系统采用前后端分离架构,实现了运动员管理、比赛项目管理、成绩录入、数据统计等核心功能。

技术栈

后端技术

  • Spring Boot 2.7.0:核心框架
  • Spring Security:安全框架
  • MyBatis-Plus:ORM框架
  • MySQL 8.0:数据库
  • Redis:缓存服务
  • JWT:身份认证

前端技术

  • Vue 3:前端框架
  • Element Plus:UI组件库
  • Axios:HTTP客户端
  • Vuex:状态管理
  • Vue Router:路由管理

核心功能

运动员管理

  • 运动员信息录入与维护
  • 参赛项目分配
  • 号码簿生成
  • 运动员成绩查询

比赛项目管理

  • 项目设置与编排
  • 赛程安排
  • 场地分配
  • 裁判员分配

成绩管理

  • 实时成绩录入
  • 成绩审核
  • 成绩公示
  • 破纪录提醒

数据统计

  • 团体总分排名
  • 单项成绩排名
  • 奖牌榜统计
  • 数据可视化展示

项目特点

  1. 实时数据更新

    • 采用WebSocket技术实现成绩实时推送
    • Redis缓存确保高并发场景下的数据一致性
  2. 高度可配置

    • 灵活的项目设置
    • 可自定义计分规则
    • 支持多种赛制
  3. 安全性

    • 基于RBAC的权限管理
    • 操作日志记录
    • 数据备份恢复
  4. 用户体验

    • 响应式设计
    • 多终端适配
    • 直观的数据展示

项目部署

环境要求

  • JDK 1.8+
  • Maven 3.6+
  • MySQL 8.0+
  • Redis 6.0+
  • Node.js 14+

部署步骤

  1. 后端部署
# 克隆项目
git clone https://github.com/username/sports-meet-manager.git# 进入项目目录
cd sports-meet-manager# 编译打包
mvn clean package# 运行项目
java -jar target/sports-meet-manager.jar
  1. 前端部署
# 进入前端项目目录
cd web# 安装依赖
npm install# 编译打包
npm run build# 部署到nginx
cp -r dist/* /usr/share/nginx/html/

项目展望

  1. 功能扩展

    • 引入AI识别技术,实现自动计时
    • 增加移动端应用
    • 支持更多类型的运动会
  2. 性能优化

    • 引入分布式架构
    • 优化数据处理算法
    • 提升系统并发能力
  3. 用户体验提升

    • 完善数据可视化
    • 优化操作流程
    • 增加个性化配置

总结

本项目采用主流的Java全栈技术栈,实现了一个功能完善、性能稳定的运动会管理系统。通过前后端分离的架构设计,确保了系统的可维护性和扩展性。项目在实际应用中取得了良好的效果,为运动会的组织和管理提供了有力的技术支持。

运动会管理系统模块

1. 数据库设计

1.1 运动员管理相关表

-- 运动员基本信息表
CREATE TABLE t_athlete (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL COMMENT '姓名',gender TINYINT NOT NULL COMMENT '性别:0-女,1-男',birth_date DATE NOT NULL COMMENT '出生日期',id_card VARCHAR(18) UNIQUE COMMENT '身份证号',team_id BIGINT NOT NULL COMMENT '代表队ID',phone VARCHAR(20) COMMENT '联系电话',photo_url VARCHAR(255) COMMENT '照片URL',status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (team_id) REFERENCES t_team(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动员信息表';-- 代表队表
CREATE TABLE t_team (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100) NOT NULL COMMENT '代表队名称',leader_name VARCHAR(50) COMMENT '领队姓名',phone VARCHAR(20) COMMENT '联系电话',status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代表队表';-- 运动员参赛项目关联表
CREATE TABLE t_athlete_event (id BIGINT PRIMARY KEY AUTO_INCREMENT,athlete_id BIGINT NOT NULL COMMENT '运动员ID',event_id BIGINT NOT NULL COMMENT '比赛项目ID',bib_number VARCHAR(20) NOT NULL COMMENT '号码簿编号',track_number INT COMMENT '道次号',group_number VARCHAR(20) COMMENT '分组编号',status TINYINT DEFAULT 1 COMMENT '状态:0-弃权,1-正常',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (athlete_id) REFERENCES t_athlete(id),FOREIGN KEY (event_id) REFERENCES t_event(id),UNIQUE KEY uk_athlete_event (athlete_id, event_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动员参赛项目关联表';-- 运动员成绩表
CREATE TABLE t_athlete_result (id BIGINT PRIMARY KEY AUTO_INCREMENT,athlete_event_id BIGINT NOT NULL COMMENT '运动员参赛关联ID',result VARCHAR(20) NOT NULL COMMENT '成绩',ranking INT COMMENT '名次',is_broken_record TINYINT DEFAULT 0 COMMENT '是否破记录:0-否,1-是',status TINYINT DEFAULT 1 COMMENT '状态:0-无效,1-有效',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (athlete_event_id) REFERENCES t_athlete_event(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动员成绩表';

1.2 比赛项目管理相关表

-- 比赛项目表
CREATE TABLE t_event (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100) NOT NULL COMMENT '项目名称',type TINYINT NOT NULL COMMENT '项目类型:1-田赛,2-径赛',gender_limit TINYINT NOT NULL COMMENT '性别限制:0-女,1-男',age_min INT COMMENT '最小年龄限制',age_max INT COMMENT '最大年龄限制',max_participants INT COMMENT '最大参赛人数',current_record VARCHAR(20) COMMENT '当前记录',record_holder VARCHAR(50) COMMENT '记录保持者',status TINYINT DEFAULT 1 COMMENT '状态:0-关闭,1-开放',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='比赛项目表';-- 赛程表
CREATE TABLE t_schedule (id BIGINT PRIMARY KEY AUTO_INCREMENT,event_id BIGINT NOT NULL COMMENT '比赛项目ID',venue_id BIGINT NOT NULL COMMENT '场地ID',start_time DATETIME NOT NULL COMMENT '开始时间',end_time DATETIME NOT NULL COMMENT '结束时间',round_type TINYINT NOT NULL COMMENT '轮次:1-预赛,2-决赛',status TINYINT DEFAULT 1 COMMENT '状态:0-取消,1-正常',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (event_id) REFERENCES t_event(id),FOREIGN KEY (venue_id) REFERENCES t_venue(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程表';-- 场地表
CREATE TABLE t_venue (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100) NOT NULL COMMENT '场地名称',type TINYINT NOT NULL COMMENT '场地类型:1-田赛场地,2-径赛场地',location VARCHAR(255) COMMENT '位置描述',status TINYINT DEFAULT 1 COMMENT '状态:0-维护中,1-可用',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地表';-- 裁判员表
CREATE TABLE t_referee (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL COMMENT '姓名',gender TINYINT NOT NULL COMMENT '性别:0-女,1-男',phone VARCHAR(20) COMMENT '联系电话',level VARCHAR(20) COMMENT '裁判等级',status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='裁判员表';-- 裁判员分配表
CREATE TABLE t_referee_assignment (id BIGINT PRIMARY KEY AUTO_INCREMENT,referee_id BIGINT NOT NULL COMMENT '裁判员ID',schedule_id BIGINT NOT NULL COMMENT '赛程ID',role_type TINYINT NOT NULL COMMENT '角色:1-主裁判,2-副裁判,3-检录员',status TINYINT DEFAULT 1 COMMENT '状态:0-取消,1-正常',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (referee_id) REFERENCES t_referee(id),FOREIGN KEY (schedule_id) REFERENCES t_schedule(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='裁判员分配表';

2. 后端实现

2.1 项目结构

src/main/java/com/sports
├── common
│   ├── config
│   ├── exception
│   └── util
├── controller
├── service
├── mapper
└── model├── entity├── dto└── vo

2.2 核心代码实现

2.2.1 运动员管理
// AthleteController.java
@RestController
@RequestMapping("/api/athletes")
@Slf4j
public class AthleteController {@Autowiredprivate AthleteService athleteService;@PostMappingpublic ResponseEntity<AthleteVO> createAthlete(@RequestBody @Valid AthleteDTO athleteDTO) {return ResponseEntity.ok(athleteService.createAthlete(athleteDTO));}#### 3.2.2 比赛项目管理页面```vue
<!-- views/event/EventList.vue -->
<template><div class="event-list"><div class="header"><h1>比赛项目管理</h1><el-button type="primary" @click="showCreateDialog">新增项目</el-button></div><!-- 搜索表单 --><el-form :model="queryForm" :inline="true" class="search-form"><el-form-item label="项目名称"><el-input v-model="queryForm.name" placeholder="请输入项目名称"></el-input></el-form-item><el-form-item label="项目类型"><el-select v-model="queryForm.type" placeholder="请选择项目类型"><el-option label="田赛" :value="1"></el-option><el-option label="径赛" :value="2"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleSearch">查询</el-button><el-button @click="resetForm">重置</el-button></el-form-item></el-form><!-- 项目列表 --><el-table :data="events" border stripe><el-table-column prop="name" label="项目名称"></el-table-column><el-table-column prop="type" label="项目类型">@PutMapping("/{id}")public ResponseEntity<AthleteVO> updateAthlete(@PathVariable Long id, @RequestBody @Valid AthleteDTO athleteDTO) {return ResponseEntity.ok(athleteService.updateAthlete(id, athleteDTO));}@PostMapping("/batch")public ResponseEntity<List<AthleteVO>> batchImport(@RequestParam("file") MultipartFile file) {return ResponseEntity.ok(athleteService.batchImport(file));}@GetMappingpublic ResponseEntity<Page<AthleteVO>> getAthletes(AthleteQueryDTO queryDTO, @PageableDefault Pageable pageable) {return ResponseEntity.ok(athleteService.getAthletes(queryDTO, pageable));}
}// AthleteServiceImpl.java
@Service
@Slf4j
public class AthleteServiceImpl implements AthleteService {@Autowiredprivate AthleteMapper athleteMapper;@Autowiredprivate FileService fileService;@Override@Transactionalpublic AthleteVO createAthlete(AthleteDTO athleteDTO) {// 验证身份证号validateIdCard(athleteDTO.getIdCard());// 上传照片String photoUrl = fileService.uploadPhoto(athleteDTO.getPhoto());// 保存运动员信息Athlete athlete = convertToEntity(athleteDTO);athlete.setPhotoUrl(photoUrl);athleteMapper.insert(athlete);return convertToVO(athlete);}@Override@Transactional(readOnly = true)public Page<AthleteVO> getAthletes(AthleteQueryDTO queryDTO, Pageable pageable) {Page<Athlete> athletePage = athleteMapper.selectPage(queryDTO, pageable);return athletePage.map(this::convertToVO);}
}
2.2.2 比赛项目管理
// EventController.java
@RestController
@RequestMapping("/api/events")
@Slf4j
public class EventController {@Autowiredprivate EventService eventService;@PostMappingpublic ResponseEntity<EventVO> createEvent(@RequestBody @Valid EventDTO eventDTO) {return ResponseEntity.ok(eventService.createEvent(eventDTO));}@PostMapping("/{eventId}/schedule")public ResponseEntity<ScheduleVO> createSchedule(@PathVariable Long eventId,@RequestBody @Valid ScheduleDTO scheduleDTO) {return ResponseEntity.ok(eventService.createSchedule(eventId, scheduleDTO));}@PostMapping("/{eventId}/referee-assignments")public ResponseEntity<List<RefereeAssignmentVO>> assignReferees(@PathVariable Long eventId,@RequestBody @Valid List<RefereeAssignmentDTO> assignments) {return ResponseEntity.ok(eventService.assignReferees(eventId, assignments));}
}// EventServiceImpl.java
@Service
@Slf4j
public class EventServiceImpl implements EventService {@Autowiredprivate EventMapper eventMapper;@Autowiredprivate ScheduleMapper scheduleMapper;@Override@Transactionalpublic EventVO createEvent(EventDTO eventDTO) {// 验证项目信息validateEventInfo(eventDTO);// 保存项目信息Event event = convertToEntity(eventDTO);eventMapper.insert(event);return convertToVO(event);}@Override@Transactionalpublic ScheduleVO createSchedule(Long eventId, ScheduleDTO scheduleDTO) {// 检查时间冲突checkTimeConflict(scheduleDTO);// 检查场地可用性checkVenueAvailability(scheduleDTO.getVenueId(), scheduleDTO.getStartTime(), scheduleDTO.getEndTime());// 保存赛程信息Schedule schedule = convertToEntity(scheduleDTO);schedule.setEventId(eventId);scheduleMapper.insert(schedule);return convertToVO(schedule);}
}

3. 前端实现

3.1 项目结构

src
├── api
├── assets
├── components
├── layouts
├── router
├── store
└── views├── athlete└── event

3.2 核心代码实现

3.2.1 运动员管理页面
<!-- views/athlete/AthleteList.vue -->
<template><div class="athlete-list"><div class="header"><h1>运动员管理</h1><div class="actions"><el-button type="primary" @click="showCreateDialog">新增运动员</el-button><el-uploadclass="upload-btn"action="/api/athletes/batch":on-success="handleUploadSuccess"><el-button type="primary">批量导入</el-button></el-upload></div></div><!-- 搜索表单 --><el-form :model="queryForm" :inline="true" class="search-form"><el-form-item label="姓名"><el-input v-model="queryForm.name" placeholder="请输入姓名"></el-input></el-form-item><el-form-item label="代表队"><el-select v-model="queryForm.teamId" placeholder="请选择代表队"><el-optionv-for="team in teams":key="team.id":label="team.name":value="team.id"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleSearch">查询</el-button><el-button @click="resetForm">重置</el-button></el-form-item></el-form><!-- 运动员列表 --><el-table :data="athletes" border stripe><el-table-column prop="name" label="姓名"></el-table-column><el-table-column prop="gender" label="性别"><template #default="scope">{{ scope.row.gender === 1 ? '男' : '女' }}</template></el-table-column><el-table-column prop="birthDate" label="出生日期"></el-table-column><el-table-column prop="teamName" label="代表队"></el-table-column><el-table-column prop="phone" label="联系电话"></el-table-column><el-table-column label="照片"><template #default="scope"><el-image:src="scope.row.photoUrl":preview-src-list="[scope.row.photoUrl]"fit="cover"class="athlete-photo"></el-image></template></el-table-column><el-table-column label="操作" width="250"><template #default="scope"><el-button type="primary" size="small" @click="showEditDialog(scope.row)">编辑</el-button><el-button type="success" size="small" @click="showAssignEvents(scope.row)">分配项目</el-button><el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button></template></el-table-column></el-table><!-- 分页器 --><el-pagination:current-page="page.current":page-size="page.size":total="page.total"@current-change="handlePageChange"layout="total, prev, pager, next"></el-pagination><!-- 新增/编辑对话框 --><el-dialog:title="dialogType === 'create' ? '新增运动员' : '编辑运动员'"v-model="dialogVisible"width="600px"><el-formref="athleteForm":model="athleteForm":rules="rules"label-width="100px"><el-form-item label="姓名" prop="name"><el-input v-model="athleteForm.name"></el-input></el-form-item><el-form-item label="性别" prop="gender"><el-radio-group v-model="athleteForm.gender"><el-radio :label="1">男</el-radio><el-radio :label="0">女</el-radio></el-radio-group></el-form-item><el-form-item label="出生日期" prop="birthDate"><el-date-pickerv-model="athleteForm.birthDate"type="date"placeholder="选择日期"></el-date-picker></el-form-item><el-form-item label="身份证号" prop="idCard"><el-input v-model="athleteForm.idCard"></el-input></el-form-item><el-form-item label="代表队" prop="teamId"><el-select v-model="athleteForm.teamId" placeholder="请选择代表队"><el-optionv-for="team in teams":key="team.id":label="team.name":value="team.id"></el-option></el-select></el-form-item><el-form-item label="联系电话" prop="phone"><el-input v-model="athleteForm.phone"></el-input></el-form-item><el-form-item label="照片" prop="photo"><el-uploadclass="avatar-uploader"action="/api/upload":show-file-list="false":before-upload="beforePhotoUpload":on-success="handlePhotoSuccess"><img v-if="athleteForm.photoUrl" :src="athleteForm.photoUrl" class="avatar"><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item></el-form><template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="handleSubmit">确定</el-button></template></el-dialog><!-- 项目分配对话框 --><el-dialogtitle="分配比赛项目"v-model="eventDialogVisible"width="800px"><el-transferv-model="selectedEvents":data="allEvents":titles="['可选项目', '已选项目']":props="{key: 'id',label: 'name'}"></el-transfer><template #footer><el-button @click="eventDialogVisible = false">取消</el-button><el-button type="primary" @click="handleEventAssign">确定</el-button></template></el-dialog></div>
</template><script setup>
import { ref, onMounted, reactive } from 'vue'
import { getAthletes, createAthlete, updateAthlete, deleteAthlete } from '@/api/athlete'
import { getTeams } from '@/api/team'
import { getEvents, assignEvents } from '@/api/event'
import { ElMessage } from 'element-plus'// 数据定义
const athletes = ref([])
const teams = ref([])
const allEvents = ref([])
const page = reactive({current: 1,size: 10,total: 0
})const queryForm = reactive({name: '',teamId: ''
})const athleteForm = reactive({name: '',gender: 1,birthDate: '',idCard: '',teamId: '',phone: '',photoUrl: ''
})// 校验规则
const rules = {name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],gender: [{ required: true, message: '请选择性别', trigger: 'change' }],birthDate: [{ required: true, message: '请选择出生日期', trigger: 'change' }],idCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' },{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号', trigger: 'blur' }],teamId: [{ required: true, message: '请选择代表队', trigger: 'change' }],phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }]
}// 对话框控制
const dialogVisible = ref(false)
const dialogType = ref('create')
const eventDialogVisible = ref(false)
const selectedEvents = ref([])
const currentAthlete = ref(null)// 初始化数据
onMounted(async () => {await Promise.all([loadAthletes(),loadTeams(),loadEvents()])
})// 方法定义
const loadAthletes = async () => {try {const res = await getAthletes({...queryForm,page: page.current,size: page.size})athletes.value = res.data.recordspage.total = res.data.total} catch (error) {console.error('加载运动员列表失败:', error)ElMessage.error('加载运动员列表失败')}
}const loadTeams = async () => {try {const res = await getTeams()teams.value = res.data} catch (error) {console.error('加载代表队列表失败:', error)ElMessage.error('加载代表队列表失败')}
}const loadEvents = async () => {try {const res = await getEvents()allEvents.value = res.data} catch (error) {console.error('加载比赛项目失败:', error)ElMessage.error('加载比赛项目失败')}
}// 处理搜索
const handleSearch = () => {page.current = 1loadAthletes()
}// 重置表单
const resetForm = () => {queryForm.name = ''queryForm.teamId = ''handleSearch()
}// 显示新增对话框
const showCreateDialog = () => {dialogType.value = 'create'Object.keys(athleteForm).forEach(key => {athleteForm[key] = ''})dialogVisible.value = true
}// 显示编辑对话框
const showEditDialog = (row) => {dialogType.value = 'edit'Object.assign(athleteForm, row)dialogVisible.value = true
}// 显示项目分配对话框
const showAssignEvents = async (row) => {currentAthlete.value = rowselectedEvents.value = row.eventIds || []eventDialogVisible.value = true
}// 提交表单
const handleSubmit = async () => {try {if (dialogType.value === 'create') {await createAthlete(athleteForm)ElMessage.success('新增运动员成功')} else {await updateAthlete(currentAthlete.value.id, athleteForm)ElMessage.success('更新运动员成功')}dialogVisible.value = falseloadAthletes()} catch (error) {console.error('保存运动员失败:', error)ElMessage.error('保存运动员失败')}
}// 处理删除
const handleDelete = async (row) => {try {await ElMessageBox.confirm('确认删除该运动员?', '提示', {type: 'warning'})await deleteAthlete(row.id)ElMessage.success('删除成功')loadAthletes()} catch (error) {if (error !== 'cancel') {console.error('删除运动员失败:', error)ElMessage.error('删除运动员失败')}}
}// 处理项目分配
const handleEventAssign = async () => {try {await assignEvents(currentAthlete.value.id, selectedEvents.value)ElMessage.success('项目分配成功')eventDialogVisible.value = falseloadAthletes()} catch (error) {console.error('项目分配失败:', error)ElMessage.error('项目分配失败')}
}
</script><style scoped>
.athlete-list {padding: 20px;
}.header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;
}.search-form {margin-bottom: 20px;
}.athlete-photo {width: 60px;height: 60px;border-radius: 4px;
}.avatar-uploader {width: 148px;height: 148px;border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;
}.avatar-uploader:hover {border-color: #409EFF;
}.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 148px;height: 148px;text-align: center;line-height: 148px;
}.avatar {width: 148px;height: 148px;display: block;
}

成绩管理与数据统计模块

1. 数据库设计

1.1 成绩管理相关表

-- 成绩记录表
CREATE TABLE t_score (id BIGINT PRIMARY KEY AUTO_INCREMENT,athlete_event_id BIGINT NOT NULL COMMENT '运动员参赛ID',round_type TINYINT NOT NULL COMMENT '轮次:1-预赛,2-决赛',score VARCHAR(20) NOT NULL COMMENT '成绩',score_status TINYINT DEFAULT 0 COMMENT '成绩状态:0-待审核,1-已审核,2-已公示',is_broken_record TINYINT DEFAULT 0 COMMENT '是否破记录:0-否,1-是',recorder_id BIGINT NOT NULL COMMENT '记录员ID',auditor_id BIGINT COMMENT '审核员ID',audit_time DATETIME COMMENT '审核时间',audit_remark VARCHAR(255) COMMENT '审核备注',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (athlete_event_id) REFERENCES t_athlete_event(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成绩记录表';-- 破纪录记录表
CREATE TABLE t_record_breaking (id BIGINT PRIMARY KEY AUTO_INCREMENT,event_id BIGINT NOT NULL COMMENT '比赛项目ID',athlete_id BIGINT NOT NULL COMMENT '运动员ID',old_record VARCHAR(20) COMMENT '原记录',new_record VARCHAR(20) NOT NULL COMMENT '新记录',breaking_time DATETIME NOT NULL COMMENT '破记录时间',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (event_id) REFERENCES t_event(id),FOREIGN KEY (athlete_id) REFERENCES t_athlete(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='破纪录记录表';

1.2 数据统计相关表

-- 团体总分表
CREATE TABLE t_team_score (id BIGINT PRIMARY KEY AUTO_INCREMENT,team_id BIGINT NOT NULL COMMENT '代表队ID',total_score DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '总分',gold_count INT NOT NULL DEFAULT 0 COMMENT '金牌数',silver_count INT NOT NULL DEFAULT 0 COMMENT '银牌数',bronze_count INT NOT NULL DEFAULT 0 COMMENT '铜牌数',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (team_id) REFERENCES t_team(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团体总分表';-- 单项排名表
CREATE TABLE t_event_ranking (id BIGINT PRIMARY KEY AUTO_INCREMENT,event_id BIGINT NOT NULL COMMENT '比赛项目ID',athlete_id BIGINT NOT NULL COMMENT '运动员ID',rank_num INT NOT NULL COMMENT '名次',score VARCHAR(20) NOT NULL COMMENT '成绩',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (event_id) REFERENCES t_event(id),FOREIGN KEY (athlete_id) REFERENCES t_athlete(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单项排名表';

2. 后端实现

2.1 成绩录入与管理

// ScoreController.java
@RestController
@RequestMapping("/api/scores")
@Slf4j
public class ScoreController {@Autowiredprivate ScoreService scoreService;@PostMappingpublic ResponseEntity<ScoreVO> createScore(@RequestBody @Valid ScoreDTO scoreDTO) {return ResponseEntity.ok(scoreService.createScore(scoreDTO));}@PutMapping("/{id}/audit")public ResponseEntity<ScoreVO> auditScore(@PathVariable Long id, @RequestBody @Valid ScoreAuditDTO auditDTO) {return ResponseEntity.ok(scoreService.auditScore(id, auditDTO));}@GetMapping("/records")public ResponseEntity<List<RecordBreakingVO>> getRecordBreakings() {return ResponseEntity.ok(scoreService.getRecordBreakings());}
}// ScoreServiceImpl.java
@Service
@Slf4j
public class ScoreServiceImpl implements ScoreService {@Autowiredprivate ScoreMapper scoreMapper;@Autowiredprivate EventMapper eventMapper;@Autowiredprivate WebSocketService webSocketService;@Override@Transactionalpublic ScoreVO createScore(ScoreDTO scoreDTO) {// 验证成绩格式validateScore(scoreDTO.getScore(), scoreDTO.getEventId());// 检查是否破记录boolean isBrokenRecord = checkRecordBreaking(scoreDTO);// 保存成绩Score score = convertToEntity(scoreDTO);score.setIsBrokenRecord(isBrokenRecord);scoreMapper.insert(score);// 如果破记录,发送通知if (isBrokenRecord) {sendRecordBreakingNotification(score);}// 实时推送成绩更新webSocketService.broadcastScore(convertToVO(score));return convertToVO(score);}@Override@Transactionalpublic ScoreVO auditScore(Long id, ScoreAuditDTO auditDTO) {Score score = scoreMapper.selectById(id);if (score == null) {throw new NotFoundException("成绩记录不存在");}// 更新审核状态score.setScoreStatus(auditDTO.getStatus());score.setAuditorId(auditDTO.getAuditorId());score.setAuditTime(LocalDateTime.now());score.setAuditRemark(auditDTO.getRemark());scoreMapper.updateById(score);// 如果审核通过,更新排名和团体分if (auditDTO.getStatus() == 1) {updateRankingAndTeamScore(score);}return convertToVO(score);}
}

2.2 数据统计服务

// StatisticsController.java
@RestController
@RequestMapping("/api/statistics")
@Slf4j
public class StatisticsController {@Autowiredprivate StatisticsService statisticsService;@GetMapping("/team-scores")public ResponseEntity<List<TeamScoreVO>> getTeamScores() {return ResponseEntity.ok(statisticsService.getTeamScores());}@GetMapping("/event-rankings/{eventId}")public ResponseEntity<List<EventRankingVO>> getEventRankings(@PathVariable Long eventId) {return ResponseEntity.ok(statisticsService.getEventRankings(eventId));}@GetMapping("/medal-statistics")public ResponseEntity<List<MedalStatisticsVO>> getMedalStatistics() {return ResponseEntity.ok(statisticsService.getMedalStatistics());}
}// StatisticsServiceImpl.java
@Service
@Slf4j
public class StatisticsServiceImpl implements StatisticsService {@Autowiredprivate TeamScoreMapper teamScoreMapper;@Autowiredprivate EventRankingMapper eventRankingMapper;@Override@Transactional(readOnly = true)public List<TeamScoreVO> getTeamScores() {List<TeamScore> teamScores = teamScoreMapper.selectList(null);return teamScores.stream().map(this::convertToVO).sorted(Comparator.comparing(TeamScoreVO::getTotalScore).reversed()).collect(Collectors.toList());}@Override@Transactional(readOnly = true)public List<EventRankingVO> getEventRankings(Long eventId) {return eventRankingMapper.selectRankingsByEventId(eventId);}
}

3. 前端实现

3.1 成绩录入组件

<!-- views/score/ScoreInput.vue -->
<template><div class="score-input"><div class="input-header"><h2>{{ event.name }} - 成绩录入</h2><div class="round-selector"><el-radio-group v-model="roundType"><el-radio :label="1">预赛</el-radio><el-radio :label="2">决赛</el-radio></el-radio-group></div></div><el-table :data="athletes" border><el-table-column prop="name" label="运动员"></el-table-column><el-table-column prop="teamName" label="代表队"></el-table-column><el-table-column prop="trackNumber" label="道次"></el-table-column><el-table-column label="成绩"><template #default="scope"><el-inputv-model="scope.row.score":placeholder="getScorePlaceholder(event.type)"@blur="validateScore(scope.row)"></el-input></template></el-table-column><el-table-column label="状态" width="100"><template #default="scope"><el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag></template></el-table-column><el-table-column label="操作" width="200"><template #default="scope"><el-buttonv-if="scope.row.status === 0"type="success"size="small"@click="handleAudit(scope.row, 1)">通过</el-button><el-buttonv-if="scope.row.status === 0"type="danger"size="small"@click="handleAudit(scope.row, -1)">驳回</el-button><el-buttonv-if="scope.row.status === 1"type="primary"size="small"@click="handlePublish(scope.row)">公示</el-button></template></el-table-column></el-table><!-- 审核对话框 --><el-dialog:title="auditType === 1 ? '通过审核' : '驳回审核'"v-model="auditDialogVisible"width="500px"><el-form ref="auditForm" :model="auditForm" :rules="auditRules"><el-form-item label="审核意见" prop="remark"><el-inputtype="textarea"v-model="auditForm.remark":rows="4"placeholder="请输入审核意见"></el-input></el-form-item></el-form><template #footer><el-button @click="auditDialogVisible = false">取消</el-button><el-button type="primary" @click="submitAudit">确定</el-button></template></el-dialog></div>
</template><script setup>
import { ref, reactive, onMounted } from 'vue'
import { getScores, auditScore, publishScore } from '@/api/score'
import { getEvents } from '@/api/event'
import { ElMessage, ElMessageBox } from 'element-plus'// 数据定义
const scores = ref([])
const events = ref([])
const auditDialogVisible = ref(false)
const auditType = ref(1)
const currentScore = ref(null)const queryForm = reactive({eventId: '',status: 0
})const auditForm = reactive({remark: ''
})const auditRules = {remark: [{ required: true, message: '请输入审核意见', trigger: 'blur' },{ min: 5, message: '审核意见至少5个字符', trigger: 'blur' }]
}// 初始化数据
onMounted(async () => {await Promise.all([loadEvents(),loadScores()])
})// 加载项目列表
const loadEvents = async () => {try {const res = await getEvents()events.value = res.data} catch (error) {ElMessage.error('加载项目列表失败')}
}// 加载成绩列表
const loadScores = async () => {try {const res = await getScores(queryForm)scores.value = res.data} catch (error) {ElMessage.error('加载成绩列表失败')}
}// 处理审核
const handleAudit = (score, type) => {currentScore.value = scoreauditType.value = typeauditForm.remark = ''auditDialogVisible.value = true
}// 提交审核
const submitAudit = async () => {try {const auditData = {status: auditType.value === 1 ? 1 : 0,remark: auditForm.remark}await auditScore(currentScore.value.id, auditData)ElMessage.success('审核操作成功')auditDialogVisible.value = falseloadScores()} catch (error) {ElMessage.error('审核操作失败')}
}// 处理公示
const handlePublish = async (score) => {try {await ElMessageBox.confirm('确认公示该成绩?', '提示', {type: 'warning'})await publishScore(score.id)ElMessage.success('成绩公示成功')loadScores()} catch (error) {if (error !== 'cancel') {ElMessage.error('成绩公示失败')}}
}// 状态转换
const getStatusType = (status) => {const statusMap = {0: 'warning',1: 'success',2: 'info'}return statusMap[status]
}const getStatusText = (status) => {const statusMap = {0: '待审核',1: '已审核',2: '已公示'}return statusMap[status]
}
</script><style scoped>
.score-audit {padding: 20px;
}.filter-section {margin-bottom: 20px;
}
</style>

3.3 数据统计组件

<!-- views/statistics/Dashboard.vue -->
<template><div class="dashboard"><el-row :gutter="20"><!-- 团体总分排名 --><el-col :span="12"><el-card class="rank-card"><template #header><div class="card-header"><span>团体总分排名</span><el-button type="primary" size="small" @click="exportTeamScores">导出</el-button></div></template><div class="chart-container"><bar-chart :data="teamScores" /></div></el-card></el-col><!-- 奖牌榜 --><el-col :span="12"><el-card class="rank-card"><template #header><div class="card-header"><span>奖牌榜</span></div></template><div class="chart-container"><stacked-bar-chart :data="medalStatistics" /></div></el-card></el-col></el-row><!-- 单项成绩排名 --><el-card class="rank-card mt-20"><template #header><div class="card-header"><span>单项成绩排名</span><el-select v-model="selectedEvent" placeholder="请选择比赛项目"><el-optionv-for="event in events":key="event.id":label="event.name":value="event.id"></el-option></el-select></div></template><el-table :data="eventRankings" border stripe><el-table-column type="index" label="名次" width="80"></el-table-column><el-table-column prop="athleteName" label="运动员"></el-table-column><el-table-column prop="teamName" label="代表队"></el-table-column><el-table-column prop="score" label="成绩"></el-table-column><el-table-column prop="isBrokenRecord" label="破记录" width="100"><template #default="scope"><el-tag v-if="scope.row.isBrokenRecord" type="success">破记录</el-tag></template></el-table-column></el-table></el-card><!-- 破记录提醒 --><el-card class="rank-card mt-20"><template #header><div class="card-header"><span>破记录记录</span></div></template><el-timeline><el-timeline-itemv-for="record in recordBreakings":key="record.id":timestamp="record.breakingTime"type="success"><h4>{{ record.eventName }}</h4><p>运动员 {{ record.athleteName }} ({{ record.teamName }})以 {{ record.newRecord }} 的成绩打破原记录 {{ record.oldRecord }}</p></el-timeline-item></el-timeline></el-card></div>
</template><script setup>
import { ref, onMounted, watch } from 'vue'
import { getTeamScores, getMedalStatistics, getEventRankings, getRecordBreakings } from '@/api/statistics'
import { getEvents } from '@/api/event'
import BarChart from '@/components/charts/BarChart.vue'
import StackedBarChart from '@/components/charts/StackedBarChart.vue'
import { useWebSocket } from '@/hooks/useWebSocket'// 数据定义
const teamScores = ref([])
const medalStatistics = ref([])
const eventRankings = ref([])
const recordBreakings = ref([])
const events = ref([])
const selectedEvent = ref('')// WebSocket连接
const { messages } = useWebSocket('/ws/statistics')// 监听实时数据更新
watch(messages, (newMessage) => {if (newMessage) {switch (newMessage.type) {case 'TEAM_SCORE_UPDATE':updateTeamScores(newMessage.data)breakcase 'RECORD_BREAKING':handleRecordBreaking(newMessage.data)break}}
})// 加载数据
onMounted(async () => {await Promise.all([loadEvents(),loadTeamScores(),loadMedalStatistics(),loadRecordBreakings()])
})// 监听项目选择
watch(selectedEvent, (newValue) => {if (newValue) {loadEventRankings(newValue)}
})// 加载团体总分
const loadTeamScores = async () => {try {const res = await getTeamScores()teamScores.value = res.data} catch (error) {ElMessage.error('加载团体总分失败')}
}// 加载奖牌统计
const loadMedalStatistics = async () => {try {const res = await getMedalStatistics()medalStatistics.value = res.data} catch (error) {ElMessage.error('加载奖牌统计失败')}
}// 加载项目列表
const loadEvents = async () => {try {const res = await getEvents()events.value = res.data} catch (error) {ElMessage.error('加载项目列表失败')}
}// 加载单项排名
const loadEventRankings = async (eventId) => {try {const res = await getEventRankings(eventId)eventRankings.value = res.data} catch (error) {ElMessage.error('加载单项排名失败')}
}// 加载破记录记录
const loadRecordBreakings = async () => {try {const res = await getRecordBreakings()recordBreakings.value = res.data} catch (error) {ElMessage.error('加载破记录记录失败')}
}// 导出团体总分
const exportTeamScores = () => {// 实现导出逻辑
}// 处理实时破记录通知
const handleRecordBreaking = (record) => {ElNotification({title: '破记录提醒',message: `${record.athleteName} 在 ${record.eventName} 中破记录!`,type: 'success',duration: 0})recordBreakings.value.unshift(record)
}
</script><style scoped>
.dashboard {padding: 20px;
}.rank-card {margin-bottom: 20px;
}.card-header {display: flex;justify-content: space-between;align-items: center;
}.chart-container {height: 400px;
}.mt-20 {margin-top: 20px;
}
</style>

3.4 图表组件实现

<!-- components/charts/BarChart.vue -->
<template><div ref="chartRef" class="chart"></div>
</template><script setup>
import { ref, onMounted, watch } from 'vue'
import * as echarts from 'echarts'const props = defineProps({data: {type: Array,required: true}
})const chartRef = ref(null)
let chart = nullonMounted(() => {initChart()
})watch(() => props.data, (newVal) => {if (chart) {updateChart(newVal)}
})const initChart = () => {chart = echarts.init(chartRef.value)updateChart(props.data)
}const updateChart = (data) => {const option = {tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'}},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: {type: 'category',data: data.map(item => item.teamName)},yAxis: {type: 'value'},series: [{name: '总分',type: 'bar',data: data.map(item => item.totalScore),itemStyle: {color: '#409EFF'}}]}chart.setOption(option)
}
</script><style scoped>
.chart {width: 100%;height: 100%;
}
</style>

这样,我们完成了成绩管理和数据统计模块的完整实现。

http://www.dtcms.com/wzjs/177313.html

相关文章:

  • 重庆巴南区网站建设网站seo排名免费咨询
  • 四川省住房和城乡建设厅网站永久8x的最新域名
  • 上街区网站建设优化网站排名的方法
  • 网站建设公司擅自关闭客户网络自己怎么制作网页
  • 西安网站挂标电商网站制作
  • 怎样建网站赚钱成都网站推广哪家专业
  • 网站制作协议宁德市住房和城乡建设局
  • 什么样的网站才是好网站seo新手教程
  • 莘县网站开发优化营商环境心得体会个人
  • 网站有什么阿里域名注册官网
  • 网站制作专业的公司哪家好企业网站托管
  • 湖北省建设工程网站网站seo关键词优化技巧
  • 如何 套用模板做网站百度一下官网搜索引擎
  • 广州购物网站建设互联网推广是什么工作内容
  • 开发建设网站百度排名点击软件
  • 网站制作中心域名注册平台哪个好
  • 抖音优化排名吉安seo
  • 给小孩做辅食的网站排名优化哪家好
  • 乡镇中心小学校园网站建设指南促销方案
  • 真正免费申请一级域名成都seo优化公司
  • 外包网站建设公司网站前期推广
  • 深圳市南山区做网站的小公司农业推广
  • 昆山玉山网站建设seo百度快速排名
  • 网站建设 上海网站今日的最新新闻
  • 西安营销型网站制作价格石家庄头条今日头条新闻
  • 做盗版网站 国外服务器seo公司上海牛巨微
  • 建设官方网站e路护航成都最新疫情
  • 网站后台无法更新缓存石家庄百度搜索引擎优化
  • 在哪个网站去租地方做收废站企业网站优化报告
  • 网站代理访问是什么意思免费网站可以下载