3
0

3 کامیت‌ها b03ffa82bf ... 5eecec1af4

نویسنده SHA1 پیام تاریخ
  zhou 5eecec1af4 fix(game): 修复成绩排名和积分计算中的空值处理问题 2 هفته پیش
  zhou 8178e4017d feat(game): 添加项目成绩分页查询和个人排名功能 2 هفته پیش
  zhou 526d85f1a0 fix(game): 修复比赛成绩统计中运动员删除标记的过滤逻辑 2 هفته پیش

+ 39 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameProjectScoreVo.java

@@ -0,0 +1,39 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 项目个人成绩分页视图对象
+ */
+@Data
+public class GameProjectScoreVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // 运动员信息
+    private Long athleteId;
+    private Long userId;
+    private String athleteCode;
+    private String name;
+    private String gender;
+    private String unit;
+    private String groupType;
+
+    // 队伍信息
+    private Long teamId;
+    private String teamName;
+    private String teamCode;
+    private Long rgId;
+    private String rgName;
+
+    // 成绩信息
+    private Long scoreId;
+    private BigDecimal individualPerformance;
+    private BigDecimal teamPerformance;
+    private Integer scoreRank;
+    private Integer scorePoint;
+    private Date updateTime;
+    private String remark;
+}

+ 4 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameProjectStatsVo.java

@@ -9,8 +9,12 @@ import java.io.Serializable;
 @Data
 public class GameProjectStatsVo implements Serializable {
     private Long projectId;
+    /** 报名总人数 */
+    private Integer registrationCount;
     /** 参赛总数(根据项目类型自动计算运动员或队伍数) */
     private Integer totalParticipants;
     /** 完赛数量 */
     private Integer completedParticipants;
+    /** 未完赛数量 */
+    private Integer unstartedParticipants;
 }

+ 34 - 7
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameScoreMapper.java

@@ -3,6 +3,8 @@ package org.dromara.system.mapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.system.domain.vo.GameProjectScoreVo;
 import org.dromara.system.domain.vo.GameProjectStatsVo;
 import org.dromara.system.domain.GameScore;
 import org.dromara.system.domain.vo.GameScoreVo;
@@ -25,17 +27,25 @@ public interface GameScoreMapper extends BaseMapperPlus<GameScore, GameScoreVo>
      * 获取看板排名数据
      */
     List<RankingBoardVO> selectRankingBoardData(@Param("eventId") Long eventId,
-                                               @Param("projectId") Long projectId,
-                                               @Param("classification") String classification,
-                                               @Param("rgId") Long rgId);
+            @Param("projectId") Long projectId,
+            @Param("classification") String classification,
+            @Param("rgId") Long rgId);
 
     /**
-     * 批量统计项目的参赛和完赛情况
+     * 获取单个项目的统计数据(报名人数、参赛人数、完赛人数、未完赛人数)
      */
-    List<GameProjectStatsVo> selectStatsByProjectIds(@Param("eventId") Long eventId, @Param("projectIds") List<Long> projectIds);
+    GameProjectStatsVo selectProjectStats(@Param("eventId") Long eventId, @Param("projectId") Long projectId,
+            @Param("classification") String classification);
+
+    /**
+     * 根据项目Id统计该项目参赛运动员和参赛队伍的数量以及完/未完赛数
+     */
+    List<GameProjectStatsVo> selectStatsByProjectIds(@Param("eventId") Long eventId,
+            @Param("projectIds") List<Long> projectIds);
 
     /**
      * 获取赛事下每个项目成绩最高的前三名
+     * 
      * @param eventId
      * @return
      */
@@ -43,15 +53,16 @@ public interface GameScoreMapper extends BaseMapperPlus<GameScore, GameScoreVo>
 
     /**
      * 获取运动员某个项目成绩
+     * 
      * @param athleteId
      * @param projectId
      * @return
      */
     GameScoreVo selectVoByAthleteIdAndProjectId(Long athleteId, Long projectId);
 
-
     /**
      * 获取运动员项目成绩
+     * 
      * @param athleteId
      * @return
      */
@@ -60,7 +71,23 @@ public interface GameScoreMapper extends BaseMapperPlus<GameScore, GameScoreVo>
 
     // 查询运动员在指定项目中的成绩
     List<GameScore> selectByAthleteAndProjects(@Param("athleteId") Long athleteId,
-                                               @Param("projectIds") List<Long> projectIds);
+            @Param("projectIds") List<Long> projectIds);
+
+    /**
+     * 分页查询项目个人成绩(关联运动员、队伍和成绩)
+     */
+    Page<GameProjectScoreVo> selectProjectIndividualScorePage(Page<GameProjectScoreVo> page,
+            @Param("eventId") Long eventId,
+            @Param("projectId") Long projectId,
+            @Param("searchValue") String searchValue);
+
+    /**
+     * 分页查询项目团体成绩(关联队伍和成绩)
+     */
+    Page<GameProjectScoreVo> selectProjectTeamScorePage(Page<GameProjectScoreVo> page,
+            @Param("eventId") Long eventId,
+            @Param("projectId") Long projectId,
+            @Param("searchValue") String searchValue);
 
     // 根据运动员ID删除运动员成绩
     int deleteByAthleteIds(@Param("athleteIds") Collection<Long> athleteIds);

+ 122 - 162
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameScoreServiceImpl.java

@@ -24,7 +24,6 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.system.domain.*;
-import org.dromara.system.domain.bo.GameAthleteBo;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.constant.ProjectClassification;
 import org.dromara.system.domain.constant.SortType;
@@ -61,7 +60,6 @@ import java.io.IOException;
  * 成绩Service业务层处理
  *
  * @author zlt
- * @date 2025-07-30
  */
 @Slf4j
 @RequiredArgsConstructor
@@ -247,9 +245,6 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
     /**
      * 获取用户端获奖名单
-     *
-     * @param eventId
-     * @return 获奖名单 key:项目 list:获奖信息
      */
     @Override
     public Map<String, List<GameScoreVo>> getAppScore(Long eventId) {
@@ -316,10 +311,6 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
     /**
      * 根据运动员ID和项目ID查询成绩
-     *
-     * @param athleteId
-     * @param projectId
-     * @return
      */
     @Override
     public GameScoreVo getScoreByAthleteIdAndProjectId(Long athleteId, Long projectId) {
@@ -332,53 +323,20 @@ public class GameScoreServiceImpl implements IGameScoreService {
     @Override
     public Map<String, Object> getProjectScoreData(Long eventId, Long projectId, String classification,
             String searchValue, PageQuery pageQuery) {
-//        log.info(
-//                "开始获取项目成绩数据 (真分页): eventId={}, projectId={}, classification={}, searchValue={}, pageNum={}, pageSize={}",
-//                eventId, projectId, classification, searchValue, pageQuery.getPageNum(), pageQuery.getPageSize());
-
         Map<String, Object> result = new HashMap<>();
-
         // 1. 计算统计信息
+        GameProjectStatsVo projectStats = baseMapper.selectProjectStats(eventId, projectId, classification);
         Map<String, Object> stats = new HashMap<>();
-        // 报名人数 (始终按人头算)
-        long registrationCount = gameAthleteService.selectAthleteCountByProjectId(eventId, projectId);
-        stats.put("registrationCount", registrationCount);
-
-        if (ProjectClassification.SINGLE.getValue().equals(classification)) {
-            // 个人项目统计 (按人)
-            stats.put("participantCount", registrationCount);
-            // 完赛人数 (有成绩记录的运动员数量)
-            long finishedCount = baseMapper.selectCount(new LambdaQueryWrapper<GameScore>()
-                    .eq(GameScore::getProjectId, projectId)
-                    .eq(GameScore::getEventId, eventId)
-                    .isNotNull(GameScore::getAthleteId)
-                    .gt(GameScore::getScoreId, 0));
-            stats.put("finishedCount", finishedCount);
-            stats.put("unstartedCount", registrationCount - finishedCount);
+        if (projectStats != null) {
+            stats.put("registrationCount", projectStats.getRegistrationCount());
+            stats.put("participantCount", projectStats.getTotalParticipants());
+            stats.put("finishedCount", projectStats.getCompletedParticipants());
+            stats.put("unstartedCount", projectStats.getTotalParticipants() - projectStats.getCompletedParticipants());
         } else {
-            // 团体项目统计 (按队)
-            // 获取总队数
-            QueryWrapper<GameAthlete> teamCountWrapper = new QueryWrapper<>();
-            teamCountWrapper.select("DISTINCT team_id")
-                    .eq("event_id", eventId)
-                    .isNotNull("team_id")
-                    .apply("(JSON_CONTAINS(project_value, CAST({0} AS JSON)) OR JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR))))",
-                            projectId);
-            long totalTeamCount = gameAthleteMapper.selectCount(teamCountWrapper);
-
-            stats.put("participantCount", totalTeamCount);
-            // 完赛队数 (有成绩记录的队伍数量)
-            // 注意:团体项目虽然每个队员有记录,但我们按 team_id 去重统计
-            QueryWrapper<GameScore> finishedTeamWrapper = new QueryWrapper<>();
-            finishedTeamWrapper.select("DISTINCT team_id")
-                    .eq("project_id", projectId)
-                    .eq("event_id", eventId)
-                    .isNotNull("team_id")
-                    .gt("score_id", 0);
-            long finishedTeamCount = baseMapper.selectCount(finishedTeamWrapper);
-
-            stats.put("finishedCount", finishedTeamCount);
-            stats.put("unstartedCount", totalTeamCount - finishedTeamCount);
+            stats.put("registrationCount", 0);
+            stats.put("participantCount", 0);
+            stats.put("finishedCount", 0);
+            stats.put("unstartedCount", 0);
         }
         result.put("stats", stats);
 
@@ -402,16 +360,12 @@ public class GameScoreServiceImpl implements IGameScoreService {
     private TableDataInfo<Map<String, Object>> getIndividualProjectDataPaged(Long eventId, Long projectId,
             String searchValue,
             PageQuery pageQuery) {
-        GameAthleteBo bo = new GameAthleteBo();
-        bo.setEventId(eventId);
-        bo.setProjectId(projectId);
-        bo.setName(searchValue);
+        // 1. 直接使用关联查询获取分页数据
+        Page<GameProjectScoreVo> resultPage = baseMapper.selectProjectIndividualScorePage(pageQuery.build(), eventId,
+                projectId, searchValue);
+        List<GameProjectScoreVo> records = resultPage.getRecords();
 
-        // 调用底层的分页查询
-        TableDataInfo<GameAthleteVo> athletePage = gameAthleteService.queryPageList(bo, pageQuery);
-        List<GameAthleteVo> athletes = athletePage.getRows();
-
-        // 获取项目信息和计时属性
+        // 2. 获取项目配置信息
         GameEventProjectVo project = gameEventProjectService.queryById(projectId);
         boolean isTiming = isTimingProject(project);
         GameEventVo defaultEvent = gameEventService.getDefaultEvent();
@@ -419,45 +373,38 @@ public class GameScoreServiceImpl implements IGameScoreService {
                 && defaultEvent.getEventId().equals(eventId);
 
         List<Map<String, Object>> resultList = new ArrayList<>();
-        for (GameAthleteVo athlete : athletes) {
+        for (GameProjectScoreVo record : records) {
             Map<String, Object> data = new HashMap<>();
-            data.put("athleteId", athlete.getAthleteId());
-            data.put("userId", athlete.getUserId());
+            data.put("athleteId", record.getAthleteId());
+            data.put("userId", record.getUserId());
             data.put("eventId", eventId);
             data.put("projectId", projectId);
-            data.put("teamId", athlete.getTeamId());
-            data.put("athleteCode", athlete.getAthleteCode());
-            data.put("name", athlete.getName());
-            data.put("gender", athlete.getGender());
+            data.put("teamId", record.getTeamId());
+            data.put("athleteCode", record.getAthleteCode());
+            data.put("name", record.getName());
+            data.put("gender", record.getGender());
             data.put("projectType", project.getProjectType());
             data.put("projectName", project.getProjectName());
-            data.put("unit", athlete.getUnit());
-            data.put("groupType", athlete.getGroupType());
-
-            if (athlete.getTeamId() != null) {
-                GameTeamVo team = gameTeamService.queryById(athlete.getTeamId());
-                if (team != null) {
-                    data.put("teamName", team.getTeamName());
-                    data.put("teamCode", team.getTeamCode());
-                }
-            }
-
-            GameScoreVo score = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), projectId);
-            if (score != null) {
-                data.put("scoreId", score.getScoreId());
-                data.put("scorePoint", score.getScorePoint());
-                if (isTiming && score.getIndividualPerformance() != null) {
-                    String timeFormat = convertDecimalToTimeScore(score.getIndividualPerformance());
+            data.put("unit", record.getUnit());
+            data.put("groupType", record.getGroupType());
+            data.put("teamName", record.getTeamName());
+            data.put("teamCode", record.getTeamCode());
+
+            if (record.getScoreId() != null) {
+                data.put("scoreId", record.getScoreId());
+                data.put("scorePoint", record.getScorePoint());
+                if (isTiming && record.getIndividualPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(record.getIndividualPerformance());
                     data.put("individualPerformance",
-                            timeFormat != null ? timeFormat : score.getIndividualPerformance());
+                            timeFormat != null ? timeFormat : record.getIndividualPerformance());
                 } else if (isHealthCheck) {
-                    handleHealthCheckRemark(score, data);
+                    handleHealthCheckRemarkFromVo(record, data);
                 } else {
-                    data.put("individualPerformance", score.getIndividualPerformance());
+                    data.put("individualPerformance", record.getIndividualPerformance());
                 }
-                data.put("teamPerformance", score.getTeamPerformance());
-                data.put("scoreRank", score.getScoreRank());
-                data.put("updateTime", score.getUpdateTime());
+                data.put("teamPerformance", record.getTeamPerformance());
+                data.put("scoreRank", record.getScoreRank());
+                data.put("updateTime", record.getUpdateTime());
             } else {
                 setDefaultScoreData(data, isTiming);
             }
@@ -465,12 +412,26 @@ public class GameScoreServiceImpl implements IGameScoreService {
             resultList.add(data);
         }
 
-        TableDataInfo<Map<String, Object>> resultPage = new TableDataInfo<>();
-        resultPage.setRows(resultList);
-        resultPage.setTotal(athletePage.getTotal());
-        resultPage.setCode(200);
-        resultPage.setMsg("查询成功");
-        return resultPage;
+        TableDataInfo<Map<String, Object>> tableDataInfo = new TableDataInfo<>();
+        tableDataInfo.setRows(resultList);
+        tableDataInfo.setTotal(resultPage.getTotal());
+        tableDataInfo.setCode(200);
+        tableDataInfo.setMsg("查询成功");
+        return tableDataInfo;
+    }
+
+    private void handleHealthCheckRemarkFromVo(GameProjectScoreVo record, Map<String, Object> data) {
+        if (record.getRemark() != null && record.getRemark().contains("resultData")) {
+            try {
+                ObjectMapper objectMapper = new ObjectMapper();
+                Map remarkMap = objectMapper.readValue(record.getRemark(), Map.class);
+                data.put("individualPerformance", remarkMap.get("resultData").toString());
+            } catch (Exception e) {
+                log.error("解析remark失败", e);
+            }
+        } else {
+            data.put("individualPerformance", 0.0);
+        }
     }
 
     /**
@@ -478,51 +439,39 @@ public class GameScoreServiceImpl implements IGameScoreService {
      */
     private TableDataInfo<Map<String, Object>> getTeamProjectDataPaged(Long eventId, Long projectId, String searchValue,
             PageQuery pageQuery) {
-        // 由于需要搜索队伍,而 selectTeamPageByProjectId 没带搜索,我们需要先通过运动员搜索出队伍ID
-        // 如果没有搜索,直接分页查队伍
-        TableDataInfo<GameTeamVo> teamPage = gameAthleteService.selectTeamPageByProjectId(projectId, pageQuery);
-        List<GameTeamVo> teams = teamPage.getRows();
+        // 1. 直接使用关联查询获取分页数据
+        Page<GameProjectScoreVo> resultPage = baseMapper.selectProjectTeamScorePage(pageQuery.build(), eventId,
+                projectId, searchValue);
+        List<GameProjectScoreVo> records = resultPage.getRecords();
 
         GameEventProjectVo project = gameEventProjectService.queryById(projectId);
         boolean isTiming = isTimingProject(project);
 
         List<Map<String, Object>> resultList = new ArrayList<>();
-        for (GameTeamVo team : teams) {
+        for (GameProjectScoreVo record : records) {
             Map<String, Object> data = new HashMap<>();
-            data.put("teamId", team.getTeamId());
-            data.put("teamName", team.getTeamName());
-            data.put("teamCode", team.getTeamCode());
-            data.put("rgId", team.getRgId());
-            data.put("rgName", team.getRgName());
+            data.put("teamId", record.getTeamId());
+            data.put("teamName", record.getTeamName());
+            data.put("teamCode", record.getTeamCode());
+            data.put("rgId", record.getRgId());
+            data.put("rgName", record.getRgName());
             data.put("eventId", eventId);
             data.put("projectId", projectId);
             data.put("projectType", project.getProjectType());
             data.put("projectName", project.getProjectName());
 
-            // 团体项目成绩:取该项目下该队任一队员的成绩
-            List<GameAthleteVo> teamAthletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId,
-                    null);
-            GameAthleteVo firstAthlete = teamAthletes.stream()
-                    .filter(a -> team.getTeamId().equals(a.getTeamId()))
-                    .findFirst().orElse(null);
-
-            if (firstAthlete != null) {
-                GameScoreVo score = getScoreByAthleteIdAndProjectId(firstAthlete.getAthleteId(), projectId);
-                if (score != null) {
-                    data.put("scoreId", score.getScoreId());
-                    data.put("scorePoint", score.getScorePoint());
-                    data.put("individualPerformance", score.getIndividualPerformance());
-                    if (isTiming && score.getTeamPerformance() != null) {
-                        String timeFormat = convertDecimalToTimeScore(score.getTeamPerformance());
-                        data.put("teamPerformance", timeFormat != null ? timeFormat : score.getTeamPerformance());
-                    } else {
-                        data.put("teamPerformance", score.getTeamPerformance());
-                    }
-                    data.put("scoreRank", score.getScoreRank());
-                    data.put("updateTime", score.getUpdateTime());
+            if (record.getScoreId() != null) {
+                data.put("scoreId", record.getScoreId());
+                data.put("scorePoint", record.getScorePoint());
+                data.put("individualPerformance", record.getIndividualPerformance());
+                if (isTiming && record.getTeamPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(record.getTeamPerformance());
+                    data.put("teamPerformance", timeFormat != null ? timeFormat : record.getTeamPerformance());
                 } else {
-                    setDefaultScoreData(data, isTiming);
+                    data.put("teamPerformance", record.getTeamPerformance());
                 }
+                data.put("scoreRank", record.getScoreRank());
+                data.put("updateTime", record.getUpdateTime());
             } else {
                 setDefaultScoreData(data, isTiming);
             }
@@ -530,26 +479,12 @@ public class GameScoreServiceImpl implements IGameScoreService {
             resultList.add(data);
         }
 
-        TableDataInfo<Map<String, Object>> resultPage = new TableDataInfo<>();
-        resultPage.setRows(resultList);
-        resultPage.setTotal(teamPage.getTotal());
-        resultPage.setCode(200);
-        resultPage.setMsg("查询成功");
-        return resultPage;
-    }
-
-    private void handleHealthCheckRemark(GameScoreVo score, Map<String, Object> data) {
-        if (score.getRemark() != null && score.getRemark().contains("resultData")) {
-            try {
-                ObjectMapper objectMapper = new ObjectMapper();
-                Map remarkMap = objectMapper.readValue(score.getRemark(), Map.class);
-                data.put("individualPerformance", remarkMap.get("resultData").toString());
-            } catch (Exception e) {
-                log.error("解析remark失败", e);
-            }
-        } else {
-            data.put("individualPerformance", 0.0);
-        }
+        TableDataInfo<Map<String, Object>> resultPageInfo = new TableDataInfo<>();
+        resultPageInfo.setRows(resultList);
+        resultPageInfo.setTotal(resultPage.getTotal());
+        resultPageInfo.setCode(200);
+        resultPageInfo.setMsg("查询成功");
+        return resultPageInfo;
     }
 
     private void setDefaultScoreData(Map<String, Object> data, boolean isTiming) {
@@ -700,14 +635,14 @@ public class GameScoreServiceImpl implements IGameScoreService {
      */
     private Boolean handleTeamScoreUpdate(GameScoreBo bo) {
         try {
-            List<Long> atheleteIds = gameAthleteMapper.selectObjs(new QueryWrapper<GameAthlete>()
+            List<Long> athleteIds = gameAthleteMapper.selectObjs(new QueryWrapper<GameAthlete>()
                     .select("athlete_id")
                     .eq("event_id", bo.getEventId())
                     .eq("team_id", bo.getTeamId())
                     .apply("(JSON_CONTAINS(project_value, CAST({0} AS JSON)) OR JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR))))",
                             bo.getProjectId())
                     .eq("del_flag", "0"));
-            if (atheleteIds.isEmpty()) {
+            if (athleteIds.isEmpty()) {
                 log.warn("未找到队伍 {} 的运动员", bo.getTeamId());
                 return false;
             }
@@ -716,13 +651,13 @@ public class GameScoreServiceImpl implements IGameScoreService {
             Map<Long, Long> athleteScoreIdMap = baseMapper.selectList(Wrappers.<GameScore>lambdaQuery()
                     .select(GameScore::getScoreId, GameScore::getAthleteId)
                     .eq(GameScore::getProjectId, bo.getProjectId())
-                    .in(GameScore::getAthleteId, atheleteIds))
+                    .in(GameScore::getAthleteId, athleteIds))
                     .stream()
                     .collect(Collectors.toMap(GameScore::getAthleteId, GameScore::getScoreId, (v1, v2) -> v1));
 
             List<GameScore> scoreList = new ArrayList<>();
             // 为每个运动员创建或更新成绩记录
-            for (Long athleteId : atheleteIds) {
+            for (Long athleteId : athleteIds) {
                 GameScore athleteScore = new GameScore();
                 athleteScore.setScoreId(athleteScoreIdMap.get(athleteId)); // 设置已存在的 ID
                 athleteScore.setEventId(bo.getEventId());
@@ -802,6 +737,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
         // 3. 根据项目类型执行不同的排名逻辑
         if (ProjectClassification.SINGLE.getValue().equals(project.getClassification())) {
+            // 排除零分
             // --- 个人项目排名 ---
             allScores.sort((a, b) -> compareScores(a, b, orderType, ProjectClassification.SINGLE.getValue()));
 
@@ -812,9 +748,18 @@ public class GameScoreServiceImpl implements IGameScoreService {
                         ProjectClassification.SINGLE.getValue()) != 0) {
                     currentRank = i + 1;
                 }
-                int points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
+                
+                Integer points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
+                Integer finalRank = currentRank;
+
+                // 成绩为0的处理:积分和名次均设为空
+                BigDecimal perf = current.getIndividualPerformance();
+                if (perf == null || perf.compareTo(BigDecimal.ZERO) <= 0) {
+                    points = null;
+                    finalRank = null;
+                }
 
-                updateList.add(buildUpdateScore(current.getScoreId(), currentRank, points));
+                updateList.add(buildUpdateScore(current.getScoreId(), finalRank, points));
             }
         } else {
             // --- 团体项目排名 ---
@@ -845,11 +790,20 @@ public class GameScoreServiceImpl implements IGameScoreService {
                 }
 
                 // 团体积分通常有加成(此处可根据需求调整,默认取配置)
-                int points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
+                Integer points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
+                Integer finalRank = currentRank;
+
+                // 成绩为0的处理
+                GameScoreVo representative = teamGroups.get(teamId).get(0);
+                BigDecimal teamPerf = representative.getTeamPerformance();
+                if (teamPerf == null || teamPerf.compareTo(BigDecimal.ZERO) <= 0) {
+                    points = null;
+                    finalRank = null;
+                }
 
                 // 将该队的名次和积分应用给该项目下该队的所有人
                 for (GameScoreVo memberScore : teamGroups.get(teamId)) {
-                    updateList.add(buildUpdateScore(memberScore.getScoreId(), currentRank, points));
+                    updateList.add(buildUpdateScore(memberScore.getScoreId(), finalRank, points));
                 }
             }
         }
@@ -862,7 +816,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
         return true;
     }
 
-    private GameScore buildUpdateScore(Long scoreId, int rank, int points) {
+    private GameScore buildUpdateScore(Long scoreId, Integer rank, Integer points) {
         GameScore update = new GameScore();
         update.setScoreId(scoreId);
         update.setScoreRank(rank);
@@ -884,11 +838,17 @@ public class GameScoreServiceImpl implements IGameScoreService {
         if (perfB == null)
             perfB = BigDecimal.ZERO;
 
-        int res = 0;
-        if (orderType.contains("0"))
-            res = perfA.compareTo(perfB); // 升序
-        else
+        int res;
+        if (orderType.contains("0")) {
+            // 升序排列(通常是计时类):0 应该被视为最差成绩,排在最后
+            boolean aIsZero = perfA.compareTo(BigDecimal.ZERO) == 0;
+            boolean bIsZero = perfB.compareTo(BigDecimal.ZERO) == 0;
+            if (aIsZero && !bIsZero) return 1;
+            if (!aIsZero && bIsZero) return -1;
+            res = perfA.compareTo(perfB);
+        } else {
             res = perfB.compareTo(perfA); // 降序
+        }
 
         if (res != 0)
             return res;

+ 86 - 2
ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/GameScoreMapper.xml

@@ -4,6 +4,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.system.mapper.GameScoreMapper">
 
+    <select id="selectProjectStats" resultType="org.dromara.system.domain.vo.GameProjectStatsVo">
+        SELECT
+            #{projectId} as projectId,
+            (SELECT COUNT(*) FROM game_athlete WHERE event_id = #{eventId} AND del_flag = '0'
+                AND (JSON_CONTAINS(project_value, CAST(#{projectId} AS CHAR)) OR JSON_CONTAINS(project_value, CONCAT('"', #{projectId}, '"')))) as registrationCount,
+            (CASE
+                WHEN #{classification} = '0' THEN
+                    (SELECT COUNT(*) FROM game_athlete WHERE event_id = #{eventId} AND del_flag = '0'
+                        AND (JSON_CONTAINS(project_value, CAST(#{projectId} AS CHAR)) OR JSON_CONTAINS(project_value, CONCAT('"', #{projectId}, '"'))))
+                ELSE
+                    (SELECT COUNT(DISTINCT team_id) FROM game_athlete WHERE event_id = #{eventId} AND del_flag = '0'
+                        AND (JSON_CONTAINS(project_value, CAST(#{projectId} AS CHAR)) OR JSON_CONTAINS(project_value, CONCAT('"', #{projectId}, '"'))))
+            END) as totalParticipants,
+            (SELECT COUNT(DISTINCT (CASE WHEN #{classification} = '0' THEN s.athlete_id ELSE s.team_id END))
+             FROM game_score s
+            INNER JOIN game_athlete a ON (s.athlete_id = a.athlete_id OR s.team_id = a.team_id)
+             WHERE s.event_id = #{eventId} AND s.project_id = #{projectId} AND s.del_flag = '0' AND a.del_flag = '0'
+               AND (s.individual_performance > 0 OR s.team_performance > 0 OR s.score_point > 0)
+               AND (
+                    (#{classification} = '0' AND s.athlete_id = a.athlete_id)
+                    OR (#{classification} = '1' AND s.team_id = a.team_id)
+                 )
+            ) as completedParticipants
+    </select>
+
     <!--- 根据项目Id统计该项目参赛运动员和参赛队伍的数量以及完/未完赛数-->
     <select id="selectStatsByProjectIds" resultType="org.dromara.system.domain.vo.GameProjectStatsVo">
         SELECT
@@ -22,8 +47,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             END) as totalParticipants,
             (SELECT COUNT(DISTINCT (CASE WHEN p.classification = '0' THEN s.athlete_id ELSE s.team_id END))
              FROM game_score s
-             WHERE s.event_id = #{eventId} AND s.project_id = p.project_id AND s.del_flag = '0'
+             INNER JOIN game_athlete a ON (s.athlete_id = a.athlete_id OR s.team_id = a.team_id)
+             WHERE s.event_id = #{eventId} AND s.project_id = p.project_id AND s.del_flag = '0' AND a.del_flag = '0'
                AND ((s.individual_performance > 0) OR (s.team_performance > 0))
+               AND (
+                 (p.classification = '0' AND s.athlete_id = a.athlete_id)
+                 OR (p.classification = '1' AND s.team_id = a.team_id)
+               )
             ) as completedParticipants
         FROM game_event_project p
         WHERE p.event_id = #{eventId} and p.del_flag = '0'
@@ -99,6 +129,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 WHERE s.event_id = #{eventId}
                   AND s.project_id = #{projectId}
                   AND s.del_flag = '0'
+                  AND s.score_rank IS NOT NULL AND s.score_rank > 0
                   <if test="rgId != null">
                     AND (rg.rg_id = #{rgId} OR FIND_IN_SET(#{rgId}, rg.ancestors))
                   </if>
@@ -121,7 +152,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 ) s
                 LEFT JOIN game_team t ON s.team_id = t.team_id
                 LEFT JOIN game_rank_group rg ON t.rg_id = rg.rg_id
-                WHERE 1=1
+                WHERE s.score_rank IS NOT NULL AND s.score_rank > 0
                   <if test="rgId != null">
                     AND (rg.rg_id = #{rgId} OR FIND_IN_SET(#{rgId}, rg.ancestors))
                   </if>
@@ -129,4 +160,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </otherwise>
         </choose>
     </select>
+
+    <select id="selectProjectIndividualScorePage" resultType="org.dromara.system.domain.vo.GameProjectScoreVo">
+        SELECT
+            a.athlete_id as athleteId, a.user_id as userId, a.team_id as teamId,
+            a.athlete_code as athleteCode, a.name as name, a.gender as gender,
+            a.unit as unit, a.group_type as groupType,
+            t.team_name as teamName, t.team_code as teamCode,
+            s.score_id as scoreId, s.score_point as scorePoint,
+            s.individual_performance as individualPerformance,
+            s.team_performance as teamPerformance,
+            s.score_rank as scoreRank, s.update_time as updateTime, s.remark as remark
+        FROM game_athlete a
+        LEFT JOIN game_team t ON a.team_id = t.team_id AND t.del_flag = '0'
+        LEFT JOIN game_score s ON a.athlete_id = s.athlete_id AND s.project_id = #{projectId} AND s.del_flag = '0'
+        WHERE a.event_id = #{eventId} AND a.del_flag = '0'
+          AND (JSON_CONTAINS(a.project_value, CAST(#{projectId} AS CHAR)) OR JSON_CONTAINS(a.project_value, CONCAT('"', #{projectId}, '"')))
+          <if test="searchValue != null and searchValue != ''">
+            AND a.name LIKE CONCAT('%', #{searchValue}, '%')
+          </if>
+        ORDER BY a.create_time ASC
+    </select>
+
+    <select id="selectProjectTeamScorePage" resultType="org.dromara.system.domain.vo.GameProjectScoreVo">
+        SELECT
+            t.team_id as teamId, t.team_name as teamName, t.team_code as teamCode,
+            t.rg_id as rgId, rg.rg_name as rgName,
+            s.score_id as scoreId, s.score_point as scorePoint,
+            s.individual_performance as individualPerformance,
+            s.team_performance as teamPerformance,
+            s.score_rank as scoreRank, s.update_time as updateTime
+        FROM game_team t
+        INNER JOIN (
+            SELECT DISTINCT team_id
+            FROM game_athlete
+            WHERE event_id = #{eventId} AND del_flag = '0'
+              AND (JSON_CONTAINS(project_value, CAST(#{projectId} AS CHAR)) OR JSON_CONTAINS(project_value, CONCAT('"', #{projectId}, '"')))
+        ) a ON t.team_id = a.team_id
+        LEFT JOIN game_rank_group rg ON t.rg_id = rg.rg_id AND rg.del_flag = '0'
+        LEFT JOIN (
+            SELECT s1.* FROM game_score s1
+            INNER JOIN (
+                SELECT MIN(score_id) as min_id
+                FROM game_score
+                WHERE project_id = #{projectId} AND del_flag = '0'
+                GROUP BY team_id
+            ) s2 ON s1.score_id = s2.min_id
+        ) s ON t.team_id = s.team_id
+        WHERE t.event_id = #{eventId} AND t.del_flag = '0'
+          <if test="searchValue != null and searchValue != ''">
+            AND t.team_name LIKE CONCAT('%', #{searchValue}, '%')
+          </if>
+        ORDER BY t.create_time ASC
+    </select>
 </mapper>