12 KiB
编排功能实施总结
完成日期: 2025-12-11 实施人员: Claude Code 项目: 武术赛事管理系统 - 编排模块
📋 实施概述
本次实施完成了武术赛事编排系统的前后端完整功能,包括数据查询、草稿保存、编排锁定等核心功能。
✅ 已完成功能
1. 后端实现
1.1 Controller层
文件: MartialScheduleArrangeController.java
已实现的接口:
- ✅
GET /api/martial/schedule/result- 获取编排结果 - ✅
POST /api/martial/schedule/save-draft- 保存编排草稿 - ✅
POST /api/martial/schedule/save-and-lock- 完成编排并锁定 - ✅
POST /api/martial/schedule/auto-arrange- 手动触发自动编排
1.2 Service层
文件: MartialScheduleServiceImpl.java
已实现的方法:
getScheduleResult(Long competitionId)
- 功能:获取赛程编排结果
- 优化:使用LEFT JOIN一次性查询所有数据,避免N+1问题
- 返回:包含分组、场地、时间段、参赛者的完整数据
@Override
public ScheduleResultDTO getScheduleResult(Long competitionId) {
// 使用优化的一次性JOIN查询
List<ScheduleGroupDetailVO> details = scheduleGroupMapper
.selectScheduleGroupDetails(competitionId);
// 在内存中按分组ID分组
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
// 检查编排状态并组装数据
// ...
}
saveDraftSchedule(SaveScheduleDraftDTO dto)
- 功能:保存编排草稿
- 事务:使用@Transactional确保数据一致性
- 处理:更新分组、明细、参赛者信息
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraftSchedule(SaveScheduleDraftDTO dto) {
// 遍历每个分组
for (CompetitionGroupDTO groupDTO : dto.getCompetitionGroups()) {
// 更新编排明细(场地、时间段)
// 更新参赛者信息(状态、出场顺序)
}
return true;
}
saveAndLockSchedule(Long competitionId)
- 功能:完成编排并锁定
- 事务:使用@Transactional确保数据一致性
- 处理:将所有参赛者状态改为"completed"
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveAndLockSchedule(Long competitionId) {
// 查询所有分组
// 更新所有参赛者的编排状态为completed
for (MartialScheduleParticipant participant : participants) {
participant.setScheduleStatus("completed");
scheduleParticipantMapper.updateById(participant);
}
return true;
}
1.3 Mapper层
文件: MartialScheduleGroupMapper.xml
核心SQL查询:
<select id="selectScheduleGroupDetails" resultType="ScheduleGroupDetailVO">
SELECT
g.id AS groupId,
g.group_name AS groupName,
g.category AS category,
g.project_type AS projectType,
g.total_teams AS totalTeams,
g.total_participants AS totalParticipants,
g.display_order AS displayOrder,
d.id AS detailId,
d.venue_id AS venueId,
d.venue_name AS venueName,
d.time_slot AS timeSlot,
p.id AS participantId,
p.organization AS organization,
p.check_in_status AS checkInStatus,
p.schedule_status AS scheduleStatus,
p.performance_order AS performanceOrder
FROM
martial_schedule_group g
LEFT JOIN
martial_schedule_detail d ON g.id = d.schedule_group_id AND d.is_deleted = 0
LEFT JOIN
martial_schedule_participant p ON g.id = p.schedule_group_id AND p.is_deleted = 0
WHERE
g.competition_id = #{competitionId}
AND g.is_deleted = 0
ORDER BY
g.display_order ASC,
p.performance_order ASC
</select>
优化说明:
- ✅ 使用LEFT JOIN避免N+1查询问题
- ✅ 一次性获取所有关联数据
- ✅ 在Service层进行内存分组,提高性能
1.4 DTO类
已定义的DTO:
ScheduleResultDTO - 编排结果DTO
@Data
public class ScheduleResultDTO {
private Boolean isDraft; // 是否为草稿
private Boolean isCompleted; // 是否已完成
private List<CompetitionGroupDTO> competitionGroups; // 竞赛分组列表
}
CompetitionGroupDTO - 竞赛分组DTO
@Data
public class CompetitionGroupDTO {
private Long id; // 分组ID
private String title; // 分组标题
private String type; // 类型:集体/单人/双人
private String count; // 队伍数量
private String code; // 分组编号
private Long venueId; // 场地ID
private String venueName; // 场地名称
private String timeSlot; // 时间段
private Integer timeSlotIndex; // 时间段索引
private List<ParticipantDTO> participants; // 参赛人员列表
}
ParticipantDTO - 参赛人员DTO
@Data
public class ParticipantDTO {
private Long id; // 参赛人员ID
private String schoolUnit; // 学校/单位
private String status; // 状态:未签到/已签到/异常
private Integer sortOrder; // 排序
}
SaveScheduleDraftDTO - 保存草稿DTO
@Data
public class SaveScheduleDraftDTO {
private Long competitionId; // 赛事ID
private Boolean isDraft; // 是否为草稿
private List<CompetitionGroupDTO> competitionGroups; // 竞赛分组数据
}
2. 前端实现
2.1 页面组件
文件: index.vue
主要功能:
- ✅ 场地选择和时间段选择
- ✅ 竞赛分组列表展示(根据场地和时间段过滤)
- ✅ 参赛者上移/下移功能
- ✅ 异常标记功能
- ✅ 分组移动功能
- ✅ 草稿保存功能
- ✅ 完成编排并锁定功能
2.2 核心方法
loadScheduleData() - 加载编排数据
async loadScheduleData() {
const res = await getScheduleResult(this.competitionId)
const data = res.data?.data
this.isScheduleCompleted = data.isCompleted || false
this.competitionGroups = data.competitionGroups.map(/* 数据映射 */)
// 加载异常组数据
this.loadExceptionList()
}
handleSaveDraft() - 保存草稿
async handleSaveDraft() {
const saveData = {
competitionId: this.competitionId,
isDraft: true,
competitionGroups: this.competitionGroups.map(group => ({
// 映射所有分组数据
participants: group.items.map((item, index) => ({
id: item.id,
schoolUnit: item.schoolUnit,
status: item.status,
sortOrder: index + 1 // 重新计算顺序
}))
}))
}
await saveDraftSchedule(saveData)
this.$message.success('草稿保存成功')
}
confirmComplete() - 完成编排(已修复)
async confirmComplete() {
// 1. 先保存草稿
const saveData = { /* 构建数据 */ }
await saveDraftSchedule(saveData)
// 2. 然后锁定
await saveAndLockSchedule(saveData)
// 3. 更新UI状态
this.isScheduleCompleted = true
this.$message.success('编排已完成并锁定')
}
2.3 计算属性
filteredCompetitionGroups - 过滤竞赛分组
computed: {
filteredCompetitionGroups() {
if (!this.selectedVenueId || this.selectedTime === null) {
return []
}
return this.competitionGroups.filter(group => {
return group.venueId === this.selectedVenueId &&
group.timeSlotIndex === this.selectedTime
})
}
}
2.4 API调用
// 获取赛程编排结果
export const getScheduleResult = (competitionId) => {
return request({
url: '/api/martial/schedule/result',
method: 'get',
params: { competitionId }
})
}
// 保存编排草稿
export const saveDraftSchedule = (data) => {
return request({
url: '/api/martial/schedule/save-draft',
method: 'post',
data
})
}
// 保存并锁定赛程编排
export const saveAndLockSchedule = (data) => {
return request({
url: '/api/martial/schedule/save-and-lock',
method: 'post',
data
})
}
🔧 修复的问题
问题1: 前端页面不显示编排数据
原因: 缺少场地和时间段过滤逻辑
解决方案: 添加计算属性filteredCompetitionGroups实现动态过滤
问题2: confirmComplete方法未调用保存接口
原因: 直接修改状态,没有调用后端接口 解决方案: 修改为先保存草稿,再调用锁定接口
修改前:
confirmComplete() {
this.isScheduleCompleted = true
this.confirmDialogVisible = false
this.$message.success('编排已完成,现在可以进行调度操作')
}
修改后:
async confirmComplete() {
try {
// 1. 保存草稿
await saveDraftSchedule(saveData)
// 2. 锁定
await saveAndLockSchedule(saveData)
// 3. 更新UI
this.isScheduleCompleted = true
this.$message.success('编排已完成并锁定')
} catch (err) {
this.$message.error('完成编排失败')
}
}
📊 数据流转
完整流程
1. 用户进入编排页面
↓
2. mounted钩子执行
- loadCompetitionInfo() - 加载赛事信息
- loadVenues() - 加载场地列表
- loadScheduleData() - 加载编排数据
↓
3. 后端查询编排数据
GET /api/martial/schedule/result?competitionId=1
- 执行优化的LEFT JOIN查询
- 在内存中分组和组装数据
- 返回ScheduleResultDTO
↓
4. 前端渲染
- 显示场地按钮列表
- 显示时间段按钮列表
- 根据选中的场地和时间段过滤分组
↓
5. 用户操作
- 选择场地/时间段
- 上移/下移参赛者
- 标记异常
- 移动分组
↓
6. 保存草稿
POST /api/martial/schedule/save-draft
- 更新编排明细(场地、时间段)
- 更新参赛者信息(状态、出场顺序)
↓
7. 完成编排
- 先调用保存草稿接口
- 再调用锁定接口
POST /api/martial/schedule/save-and-lock
- 更新所有参赛者状态为"completed"
🎯 核心技术点
1. 性能优化
- 后端: 使用LEFT JOIN避免N+1查询
- 前端: 使用计算属性实现响应式过滤
2. 数据一致性
- 使用@Transactional确保事务性
- 先保存草稿再锁定,确保数据完整
3. 用户体验
- 实时更新:修改后立即反馈
- 错误处理:统一的错误提示
- 状态管理:清晰的草稿/已完成状态
📝 测试建议
功能测试
- ✅ 测试加载编排数据
- ✅ 测试场地和时间段切换
- ✅ 测试参赛者上移/下移
- ✅ 测试异常标记和移除
- ✅ 测试分组移动
- ✅ 测试保存草稿
- ✅ 测试完成编排并锁定
性能测试
- 测试大量数据(1000+参赛者)的加载速度
- 测试频繁切换场地和时间段的响应速度
- 测试保存草稿的并发性能
边界测试
- 测试没有编排数据的情况
- 测试没有场地信息的情况
- 测试网络异常的情况
- 测试已锁定编排的操作限制
🔗 相关文档
📅 后续优化建议
短期优化(1-2周)
- 前端虚拟滚动 - 优化大数据量渲染
- 批量操作 - 支持批量上移/下移
- 撤销/重做 - 支持操作撤销
中期优化(1-2月)
- 缓存策略 - 减少重复查询
- 实时推送 - WebSocket实时更新
- 导出功能 - 完善Excel导出
长期优化(3-6月)
- AI智能编排 - 自动优化编排顺序
- 协同编辑 - 多人同时编排
- 移动端适配 - 响应式设计
实施完成日期: 2025-12-11 文档最后更新: 2025-12-11