Files
martial-web/doc/schedule/implementation-summary.md
宅房 5b806e29b7
Some checks failed
continuous-integration/drone/push Build is failing
fix bugs
2025-12-11 16:56:19 +08:00

12 KiB
Raw Blame History

编排功能实施总结

完成日期: 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调用

文件: activitySchedule.js

// 获取赛程编排结果
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. 用户体验

  • 实时更新:修改后立即反馈
  • 错误处理:统一的错误提示
  • 状态管理:清晰的草稿/已完成状态

📝 测试建议

功能测试

  1. 测试加载编排数据
  2. 测试场地和时间段切换
  3. 测试参赛者上移/下移
  4. 测试异常标记和移除
  5. 测试分组移动
  6. 测试保存草稿
  7. 测试完成编排并锁定

性能测试

  1. 测试大量数据1000+参赛者)的加载速度
  2. 测试频繁切换场地和时间段的响应速度
  3. 测试保存草稿的并发性能

边界测试

  1. 测试没有编排数据的情况
  2. 测试没有场地信息的情况
  3. 测试网络异常的情况
  4. 测试已锁定编排的操作限制

🔗 相关文档

📅 后续优化建议

短期优化1-2周

  1. 前端虚拟滚动 - 优化大数据量渲染
  2. 批量操作 - 支持批量上移/下移
  3. 撤销/重做 - 支持操作撤销

中期优化1-2月

  1. 缓存策略 - 减少重复查询
  2. 实时推送 - WebSocket实时更新
  3. 导出功能 - 完善Excel导出

长期优化3-6月

  1. AI智能编排 - 自动优化编排顺序
  2. 协同编辑 - 多人同时编排
  3. 移动端适配 - 响应式设计

实施完成日期: 2025-12-11 文档最后更新: 2025-12-11