7 次代码提交 4d2c600f02 ... 42761d811a

作者 SHA1 备注 提交日期
  wenkai 42761d811a fix:添加文件校验 2 天之前
  zhou 86010a8a80 Merge branch 'dev_zlt' into dev 2 天之前
  zhou 94e865d380 feat(game-event): 小程序端增加成绩查询功能 2 天之前
  wenkai c9f4e3221b Merge branch 'wk-dev-8-13' into dev 2 天之前
  zhou 30b7878a56 feat(game-event): 导出成绩汇总表功能 3 天之前
  zhou b0bddf3215 feat(game-event): 优化团体项目排名和积分计算逻辑 3 天之前
  wenkai a4ed374fd9 fix:生成号码布调整 3 天之前
共有 14 个文件被更改,包括 735 次插入94 次删除
  1. 10 3
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameScoreController.java
  2. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/NumberController.java
  3. 13 4
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/ScoreController.java
  4. 4 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameScoreBo.java
  5. 22 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/constant/ProjectClassification.java
  6. 30 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/constant/SortType.java
  7. 33 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AppScoreVo.java
  8. 85 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameScoreSummaryExportVo.java
  9. 3 3
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameTeamVo.java
  10. 3 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameScoreMapper.java
  11. 17 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameScoreService.java
  12. 58 3
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java
  13. 455 78
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameScoreServiceImpl.java
  14. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java

+ 10 - 3
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameScoreController.java

@@ -127,9 +127,6 @@ public class GameScoreController extends BaseController {
             @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         // 手动构建PageQuery对象
         PageQuery pageQuery = new PageQuery(pageSize, pageNum);
-//        pageQuery.setPageNum(pageNum);
-//        pageQuery.setPageSize(pageSize);
-
         return gameScoreService.getProjectScoreData(eventId, projectId, classification, searchValue, pageQuery);
     }
 
@@ -154,4 +151,14 @@ public class GameScoreController extends BaseController {
             @RequestParam("projectId") Long projectId) {
         return toAjax(gameScoreService.recalculateRankingsAndPoints(eventId, projectId));
     }
+
+    /**
+     * 导出成绩汇总表
+     */
+    @SaCheckPermission("system:gameScore:export")
+    @Log(title = "导出成绩汇总表", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportScoresSummary")
+    public void exportScoresSummary(@RequestParam("eventId") Long eventId, HttpServletResponse response) {
+        gameScoreService.exportScoresSummary(eventId, response);
+    }
 }

+ 1 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/NumberController.java

@@ -58,7 +58,7 @@ public class NumberController {
     @PostMapping("/generateBib")
     public void generateNumberBib(HttpServletResponse response,
                                   @RequestPart("bgImage") MultipartFile bgImage,
-                                  @RequestPart("logo") MultipartFile logo,
+                                  @RequestPart(name = "logo") MultipartFile logo,
                                   GenerateBibBo bibParam) {
         gameEventService.generateNumberBib(response, bgImage, logo, bibParam);
     }

+ 13 - 4
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/ScoreController.java

@@ -2,14 +2,13 @@ package org.dromara.system.controller.app;
 
 import cn.dev33.satoken.annotation.SaIgnore;
 import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.system.domain.vo.AppScoreVo;
 import org.dromara.system.domain.vo.GameScoreVo;
 import org.dromara.system.domain.vo.GameTeamVo;
 import org.dromara.system.service.IGameScoreService;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 import java.util.Map;
@@ -36,4 +35,14 @@ public class ScoreController {
         return gameScoreService.getAppScore(eventId);
     }
 
+    /**
+     * 查询成绩
+     */
+    @GetMapping("/list")
+    public R<List<AppScoreVo>> listScore(
+        @RequestParam("eventId") Long eventId,
+        @RequestParam("projectId") Long projectId){
+        List<AppScoreVo> result = gameScoreService.listAppScore(eventId, projectId);
+        return R.ok(result);
+    }
 }

+ 4 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameScoreBo.java

@@ -53,10 +53,13 @@ public class GameScoreBo extends BaseEntity {
     private Long teamId;
 
     /**
-     * 成绩
+     * 个人成绩
      */
 //    @NotBlank(message = "成绩值不能为空", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal individualPerformance;
+    /**
+     * 团队成绩
+     */
     private BigDecimal teamPerformance;
 
     /**

+ 22 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/constant/ProjectClassification.java

@@ -0,0 +1,22 @@
+package org.dromara.system.domain.constant;
+
+public enum ProjectClassification {
+    SINGLE("个人项目", "0"),
+    TEAM("团队项目", "1");
+
+    private final String description;
+    private final String value;
+
+    ProjectClassification(String description, String value) {
+        this.description = description;
+        this.value = value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}

+ 30 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/constant/SortType.java

@@ -0,0 +1,30 @@
+package org.dromara.system.domain.constant;
+
+public enum SortType {
+    ASC("升序","0"),
+    DESC("降序","1");
+
+    private String description;
+    private String value;
+
+    SortType(String description, String value) {
+        this.description = description;
+        this.value = value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}

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

@@ -0,0 +1,33 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 小程序端查询成绩信息结果Vo
+ */
+@Data
+public class AppScoreVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long projectId;
+    private String projectName;
+    private String classification;
+
+    private Long teamId;
+    private String teamName;
+
+    private Long athleteId;
+    private String athleteName;
+
+    private BigDecimal individualPerformance;
+    private BigDecimal teamPerformance;
+    private Integer scorePoint;
+    private Integer scoreRank;
+
+}

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

@@ -0,0 +1,85 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 成绩汇总导出VO
+ *
+ * @author zlt
+ * @date 2025-01-27
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class GameScoreSummaryExportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @ExcelProperty(value = "序号", index = 0)
+    private Integer serialNumber;
+
+    /**
+     * 队伍名称
+     */
+    @ExcelProperty(value = "代表队名称", index = 1)
+    private String teamName;
+
+    /**
+     * 排名
+     */
+    @ExcelProperty(value = "排名", index = 2)
+    private Integer rank;
+
+    /**
+     * 总分
+     */
+    @ExcelProperty(value = "总分", index = 3)
+    private Integer totalScore;
+
+    /**
+     * 加分
+     */
+    @ExcelProperty(value = "加分", index = 4)
+    private Integer extraScore;
+
+    /**
+     * 动态项目积分存储
+     */
+    private Map<String, Integer> projectScores = new HashMap<>();
+
+    /**
+     * 设置指定项目的积分
+     * @param projectName 项目名称
+     * @param score 积分值
+     */
+    public void setProjectScore(String projectName, Integer score) {
+        projectScores.put(projectName, score);
+    }
+
+    /**
+     * 获取指定项目的积分
+     * @param projectName 项目名称
+     * @return 积分值
+     */
+    public Integer getProjectScore(String projectName) {
+        return projectScores.getOrDefault(projectName, 0);
+    }
+
+    /**
+     * 获取所有项目积分
+     * @return 项目积分Map
+     */
+    public Map<String, Integer> getAllProjectScores() {
+        return projectScores;
+    }
+} 

+ 3 - 3
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameTeamVo.java

@@ -34,13 +34,13 @@ public class GameTeamVo implements Serializable {
     /**
      * 赛事ID
      */
-    @ExcelProperty(value = "赛事ID")
+//    @ExcelProperty(value = "赛事ID")
     private Long eventId;
 
     /**
      * 赛事ID
      */
-    @ExcelProperty(value = "赛事名称")
+//    @ExcelProperty(value = "赛事名称")
     private String eventName;
 
     /**
@@ -83,7 +83,7 @@ public class GameTeamVo implements Serializable {
     /**
      * 号码段
      */
-    @ExcelProperty(value = "号码段")
+//    @ExcelProperty(value = "号码段")
     private String numberRange;
 
     /**

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

@@ -1,5 +1,6 @@
 package org.dromara.system.mapper;
 
+import org.apache.ibatis.annotations.Select;
 import org.dromara.system.domain.GameScore;
 import org.dromara.system.domain.vo.GameScoreVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
@@ -30,4 +31,6 @@ public interface GameScoreMapper extends BaseMapperPlus<GameScore, GameScoreVo>
      * @return
      */
     GameScoreVo selectVoByAthleteIdAndProjectId(Long athleteId, Long projectId);
+
+
 }

+ 17 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameScoreService.java

@@ -1,5 +1,6 @@
 package org.dromara.system.service;
 
+import org.dromara.system.domain.vo.AppScoreVo;
 import org.dromara.system.domain.vo.GameScoreVo;
 import org.dromara.system.domain.bo.GameScoreBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -9,6 +10,7 @@ import org.dromara.system.domain.vo.GameTeamVo;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import jakarta.servlet.http.HttpServletResponse;
 
 /**
  * 成绩Service接口
@@ -108,4 +110,19 @@ public interface IGameScoreService {
      * @return 是否成功
      */
     Boolean recalculateRankingsAndPoints(Long eventId, Long projectId);
+
+    /**
+     * 导出成绩汇总表
+     * @param eventId 赛事ID
+     * @param response HTTP响应对象
+     */
+    void exportScoresSummary(Long eventId, HttpServletResponse response);
+
+    /**
+     * 用户端查询项目成绩信息
+     * @param eventId
+     * @param projectId
+     * @return
+     */
+    List<AppScoreVo> listAppScore(Long eventId, Long projectId);
 }

+ 58 - 3
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java

@@ -1,5 +1,6 @@
 package org.dromara.system.service.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.img.FontUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -19,6 +20,7 @@ import com.itextpdf.text.pdf.PdfWriter;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotNull;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -43,7 +45,6 @@ import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.vo.*;
 import org.dromara.system.mapper.GameEventMapper;
 import org.dromara.system.service.*;
-import org.jetbrains.annotations.NotNull;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -85,7 +86,15 @@ public class GameEventServiceImpl implements IGameEventService {
     private IGameEventGroupService gameEventGroupService;
     private static final ExecutorService PDF_GENERATION_EXECUTOR =
         Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
-
+    // 常见图片类型的文件头(前几个字节)
+    private static final String[] IMAGE_HEADER_PREFIXES = {
+        "FFD8FF", // JPEG
+        "89504E47", // PNG
+        "47494638", // GIF
+        "49492A00", // TIFF (little endian)
+        "4D4D002A", // TIFF (big endian)
+        "424D"      // BMP
+    };
     /**
      * 查询赛事基本信息
      *
@@ -580,13 +589,24 @@ public class GameEventServiceImpl implements IGameEventService {
     }
 
     @Override
-    public void generateNumberBib(HttpServletResponse response, MultipartFile bgImage, MultipartFile logo, GenerateBibBo bibParam) {
+    public void generateNumberBib(HttpServletResponse response,
+                                  MultipartFile bgImage, MultipartFile logo,
+                                  GenerateBibBo bibParam) {
+        if (bgImage!=null&&!isImage(bgImage)) {
+            throw new ServiceException("背景图不是图片格式");
+        }
+        if (logo!=null&&!isImage(logo)) {
+            throw new ServiceException("logo不是图片格式");
+        }
         //1.查询当前赛事所有队员数据
         GameAthleteBo gameAthleteBo = new GameAthleteBo();
         Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
         Long defaultEventId = Long.valueOf(cacheObject.toString());
         gameAthleteBo.setEventId(defaultEventId);
         List<GameAthleteVo> athleteVoList = gameAthleteService.queryList(gameAthleteBo);
+        if(CollectionUtil.isEmpty(athleteVoList)){
+            throw new ServiceException("当前赛事没有队员数据,无法生成号码布");
+        }
         //2.提前查询队员队伍名称缓存
         Set<Long> teamIds = athleteVoList.stream()
             .map(GameAthleteVo::getTeamId)
@@ -602,6 +622,41 @@ public class GameEventServiceImpl implements IGameEventService {
         generateBib(response, bgImage, logo, eventVo.getEventName(), gameEventGroup.getGroupName(), athleteVoList, teamNameMap, projectMap, bibParam);
     }
 
+    /**
+     * 判断是否为图片
+     * @param file
+     * @return
+     */
+    public static boolean isImage(MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            return false;
+        }
+
+        try (InputStream is = file.getInputStream()) {
+            byte[] header = new byte[8]; // 读取前8字节足够判断大部分图片
+            int bytesRead = is.read(header);
+            if (bytesRead < 4) {
+                return false;
+            }
+
+            StringBuilder hexHeader = new StringBuilder();
+            for (int i = 0; i < bytesRead; i++) {
+                hexHeader.append(String.format("%02X", header[i] & 0xFF));
+            }
+
+            String headerStr = hexHeader.toString();
+            for (String prefix : IMAGE_HEADER_PREFIXES) {
+                if (headerStr.startsWith(prefix)) {
+                    return true;
+                }
+            }
+
+            return false;
+
+        } catch (IOException e) {
+            return false;
+        }
+    }
     /**
      * 生成号码布并直接通过 HttpServletResponse 返回 ZIP 文件
      *

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

@@ -13,17 +13,18 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.system.domain.GameAthlete;
 import org.dromara.system.domain.GameEventProject;
 import org.dromara.system.domain.constant.GameEventConstant;
-import org.dromara.system.domain.vo.GameAthleteVo;
-import org.dromara.system.domain.vo.GameEventProjectVo;
-import org.dromara.system.domain.vo.GameTeamVo;
+import org.dromara.system.domain.constant.ProjectClassification;
+import org.dromara.system.domain.constant.SortType;
+import org.dromara.system.domain.vo.*;
+import org.dromara.system.mapper.GameEventProjectMapper;
 import org.dromara.system.service.IGameAthleteService;
 import org.dromara.system.service.IGameEventProjectService;
 import org.dromara.system.service.IGameTeamService;
 import org.springframework.stereotype.Service;
 import org.dromara.system.domain.bo.GameScoreBo;
-import org.dromara.system.domain.vo.GameScoreVo;
 import org.dromara.system.domain.GameScore;
 import org.dromara.system.mapper.GameScoreMapper;
 import org.dromara.system.service.IGameScoreService;
@@ -32,6 +33,16 @@ import java.math.BigDecimal;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.bo.GameEventProjectBo;
+import org.dromara.system.domain.bo.GameTeamBo;
+
+import java.net.URLEncoder;
+
+// Apache POI imports for dynamic Excel export
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
 /**
  * 成绩Service业务层处理
@@ -47,6 +58,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
     private final GameScoreMapper baseMapper;
     private final IGameTeamService gameTeamService;
     private final IGameAthleteService gameAthleteService;
+
+    private final GameEventProjectMapper projectMapper;
     private final IGameEventProjectService gameEventProjectService;
 
     /**
@@ -330,7 +343,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             log.info("处理运动员: athleteId={}, name={}, teamId={}", athlete.getAthleteId(), athlete.getName(), athlete.getTeamId());
 
             Map<String, Object> data = new HashMap<>();
-            data.put("id", athlete.getAthleteId());
+//            data.put("id", athlete.getAthleteId());
             data.put("athleteId", athlete.getAthleteId());
             data.put("userId", athlete.getUserId());
             data.put("eventId", eventId);
@@ -404,7 +417,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             }
 
             Map<String, Object> data = new HashMap<>();
-            data.put("id", team.getTeamId());
+//            data.put("id", team.getTeamId());
             data.put("teamId", team.getTeamId());
             data.put("teamName", team.getTeamName());
             data.put("teamCode", team.getTeamCode());
@@ -581,7 +594,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             queryBo.setProjectId(projectId);
             List<GameScoreVo> allScores = queryList(queryBo);
 
-            if ("0".equals(classification)) {
+            if (ProjectClassification.SINGLE.getValue().equals(classification)) {
                 // 个人项目:按个人成绩排序
                 return handleIndividualProjectRanking(allScores, scoreValue, eventId, projectId);
             } else {
@@ -614,8 +627,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
             .sorted((a, b) -> {
                 BigDecimal aIndividualPerformance = a.getIndividualPerformance() != null ? a.getIndividualPerformance() : BigDecimal.ZERO;
                 BigDecimal bIndividualPerformance = b.getIndividualPerformance() != null ? b.getIndividualPerformance() : BigDecimal.ZERO;
-                
-                if ("0".equals(orderType)) {
+
+                if (orderType.equals(SortType.ASC.getValue())) {
                     // 升序:成绩越小越好
                     return aIndividualPerformance.compareTo(bIndividualPerformance);
                 } else {
@@ -632,7 +645,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             .toList();
 
         // 根据orderType决定积分分配方式
-        if ("0".equals(orderType)) {
+        if (orderType.equals(SortType.ASC.getValue())) {
             // 升序项目:成绩越小,积分分配越多
             for (int i = 0; i < sortedScores.size(); i++) {
                 GameScoreVo score = sortedScores.get(i);
@@ -711,50 +724,33 @@ public class GameScoreServiceImpl implements IGameScoreService {
         String orderType = project.getOrderType();
         log.info("团体项目排名计算,projectId: {}, orderType: {}", projectId, orderType);
 
-        // 按队伍ID分组,汇总每个队伍的积分
-        Map<Long, Integer> teamTotalPoints = new HashMap<>();
+        // 按队伍ID分组,获取每个队伍的最佳成绩
         Map<Long, BigDecimal> teamBestPerformance = new HashMap<>();
-        Map<Long, String> teamNames = new HashMap<>();
-//        Map<Long, String> teamCodes = new HashMap<>();
 
         for (GameScoreVo score : allScores) {
             Long teamId = score.getTeamId();
             if (teamId != null) {
-                // 累计队伍积分,处理null值
-                Integer currentPoints = score.getScorePoint() != null ? score.getScorePoint() : 0;
-                teamTotalPoints.merge(teamId, currentPoints, Integer::sum);
-
-                // 记录队伍最佳成绩
+                // 记录队伍最佳成绩(团体成绩)
                 if (score.getTeamPerformance() != null && score.getTeamPerformance().compareTo(BigDecimal.ZERO) > 0) {
                     BigDecimal currentBest = teamBestPerformance.get(teamId);
-                    if (currentBest == null || score.getTeamPerformance().compareTo(currentBest) > 0) {
+                    if (currentBest == null ||
+                        (orderType.equals(SortType.ASC.getValue()) && score.getTeamPerformance().compareTo(currentBest) < 0) || // 升序:成绩越小越好
+                        (!orderType.equals(SortType.ASC.getValue()) && score.getTeamPerformance().compareTo(currentBest) > 0)) { // 降序:成绩越大越好
                         teamBestPerformance.put(teamId, score.getTeamPerformance());
                     }
                 }
-
-                // 记录队伍信息
-                if (score.getTeamName() != null) {
-                    teamNames.put(teamId, score.getTeamName());
-                }
-//                if (score.getTeamCode() != null) {
-//                    teamCodes.put(teamId, score.getTeamCode());
-//                }
             }
         }
 
-        // 根据orderType决定队伍总积分排序方式
-        // orderType: 0-升序(积分越高排名越靠前),1-降序(积分越高排名越靠前)
-        List<Map.Entry<Long, Integer>> sortedTeams;
-        if ("0".equals(orderType)) {
-            // 升序:积分越高排名越靠前
-            sortedTeams = teamTotalPoints.entrySet().stream()
-                .sorted(Map.Entry.comparingByValue())
-                .collect(Collectors.toList());
+        // 根据队伍成绩进行排序
+        List<Map.Entry<Long, BigDecimal>> sortedTeamsByPerformance = new ArrayList<>(teamBestPerformance.entrySet());
+
+        if (orderType.equals(SortType.ASC.getValue())) {
+            // 升序:成绩越小排名越靠前
+            sortedTeamsByPerformance.sort(Map.Entry.comparingByValue());
         } else {
-            // 降序:积分越高排名越靠前(默认)
-            sortedTeams = teamTotalPoints.entrySet().stream()
-                .sorted(Map.Entry.<Long, Integer>comparingByValue().reversed())
-                .collect(Collectors.toList());
+            // 降序:成绩越大排名越靠前
+            sortedTeamsByPerformance.sort(Map.Entry.<Long, BigDecimal>comparingByValue().reversed());
         }
 
         // 解析积分分值
@@ -763,53 +759,434 @@ public class GameScoreServiceImpl implements IGameScoreService {
             .map(Integer::parseInt)
             .collect(Collectors.toList());
 
-        // 根据orderType决定积分分配方式
-        if ("0".equals(orderType)) {
-            // 升序项目:成绩越小,积分分配越多
-            for (int i = 0; i < sortedTeams.size(); i++) {
-                Map.Entry<Long, Integer> teamEntry = sortedTeams.get(i);
-                Long teamId = teamEntry.getKey();
-                int rank = i + 1;
-                int points = i < pointValues.size() ? pointValues.get(i) : 0;
+        // 根据成绩排序结果分配积分和排名
+        Map<Long, Integer> teamPoints = new HashMap<>();
+        Map<Long, Integer> teamRanks = new HashMap<>();
+
+        for (int i = 0; i < sortedTeamsByPerformance.size(); i++) {
+            Map.Entry<Long, BigDecimal> teamEntry = sortedTeamsByPerformance.get(i);
+            Long teamId = teamEntry.getKey();
+            int points = i < pointValues.size() ? pointValues.get(i) : 0;
+            int rank = i + 1;
+
+            teamPoints.put(teamId, points);
+            teamRanks.put(teamId, rank);
+        }
+
+        // 更新每个队伍中所有运动员的成绩记录
+        for (GameScoreVo score : allScores) {
+            Long teamId = score.getTeamId();
+            if (teamId != null && teamPoints.containsKey(teamId)) {
+                GameScoreBo updateBo = new GameScoreBo();
+                updateBo.setScoreId(score.getScoreId());
+                updateBo.setScoreRank(teamRanks.get(teamId));
+                updateBo.setScorePoint(teamPoints.get(teamId));
+                updateBo.setEventId(eventId);
+                updateBo.setProjectId(projectId);
+
+                updateByBo(updateBo);
+            }
+        }
+
+        return true;
+    }
 
-                // 更新该队伍所有运动员的成绩记录
-                for (GameScoreVo score : allScores) {
-                    if (teamId.equals(score.getTeamId())) {
-                        GameScoreBo updateBo = new GameScoreBo();
-                        updateBo.setScoreId(score.getScoreId());
-                        updateBo.setScoreRank(rank);
-                        updateBo.setScorePoint(points); // 使用队伍排名对应的积分
-                        updateBo.setEventId(eventId);
-                        updateBo.setProjectId(projectId);
-
-                        updateByBo(updateBo);
+    /**
+     * 导出成绩汇总表
+     * @param eventId 赛事ID
+     * @param response HTTP响应对象
+     */
+    @Override
+    public void exportScoresSummary(Long eventId, HttpServletResponse response) {
+        try {
+            // 获取所有项目
+            GameEventProjectBo projectQuery = new GameEventProjectBo();
+            projectQuery.setEventId(eventId);
+            List<GameEventProjectVo> projects = gameEventProjectService.queryList(projectQuery);
+
+            if (projects.isEmpty()) {
+                throw new RuntimeException("未找到赛事项目");
+            }
+
+            // 获取所有队伍
+            GameTeamBo teamQuery = new GameTeamBo();
+            teamQuery.setEventId(eventId);
+            List<GameTeamVo> teams = gameTeamService.queryList(teamQuery);
+
+            if (teams.isEmpty()) {
+                throw new RuntimeException("未找到参赛队伍");
+            }
+
+            // 批量查询所有队伍在所有项目中的成绩
+            List<GameScoreVo> allScores = baseMapper.selectVoList(
+                Wrappers.lambdaQuery(GameScore.class)
+                    .eq(GameScore::getEventId, eventId)
+            );
+
+            // 按队伍ID和项目ID分组成绩数据
+            Map<String, GameScoreVo> scoreMap = new HashMap<>();
+            for (GameScoreVo score : allScores) {
+                if (score.getTeamId() != null && score.getProjectId() != null) {
+                    String key = score.getTeamId() + "_" + score.getProjectId();
+                    scoreMap.put(key, score);
+                }
+            }
+
+            // 构建导出数据
+            List<GameScoreSummaryExportVo> exportData = new ArrayList<>();
+
+            for (int i = 0; i < teams.size(); i++) {
+                GameTeamVo team = teams.get(i);
+                GameScoreSummaryExportVo exportVo = new GameScoreSummaryExportVo();
+
+                // 设置基本信息
+                exportVo.setSerialNumber(i + 1);
+                exportVo.setTeamName(team.getTeamName());
+
+                // 计算该队伍在各项目中的积分
+                int totalScore = 0;
+
+                // 设置各项目积分
+                for (GameEventProjectVo project : projects) {
+                    String key = team.getTeamId() + "_" + project.getProjectId();
+                    GameScoreVo score = scoreMap.get(key);
+
+                    int projectScore = 0;
+                    if (score != null && score.getScorePoint() != null) {
+                        projectScore = score.getScorePoint();
+                        totalScore += projectScore;
                     }
+
+                    // 使用项目名称设置积分
+                    exportVo.setProjectScore(project.getProjectName(), projectScore);
                 }
+
+                // 设置总分
+                exportVo.setTotalScore(totalScore);
+
+                // 设置加分(暂时设为0,后续可根据业务需求调整)
+                exportVo.setExtraScore(0);
+
+                exportData.add(exportVo);
+            }
+
+            // 按总分排序,生成排名
+            exportData.sort((a, b) -> Integer.compare(b.getTotalScore(), a.getTotalScore()));
+            for (int i = 0; i < exportData.size(); i++) {
+                exportData.get(i).setRank(i + 1);
+            }
+
+            // 使用动态列导出Excel
+            exportExcelWithDynamicColumns(exportData, projects, response);
+
+        } catch (Exception e) {
+            log.error("导出成绩汇总表失败", e);
+            throw new RuntimeException("导出失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 用户端查询项目成绩信息
+     */
+    @Override
+    public List<AppScoreVo> listAppScore(Long eventId, Long projectId) {
+        // 获取项目详细信息
+        GameEventProjectVo project = gameEventProjectService.queryById(projectId);
+
+        // 查询参与了该项目的运动员
+        List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, null);
+
+        // 获取所有相关成绩
+        GameScoreBo scoreBo = new GameScoreBo();
+        scoreBo.setEventId(eventId);
+        scoreBo.setProjectId(projectId);
+        List<GameScoreVo> scores = queryList(scoreBo);
+
+        // 创建成绩映射 athleteId -> GameScoreVo
+        Map<Long, GameScoreVo> scoreMap = scores.stream()
+            .collect(Collectors.toMap(GameScoreVo::getAthleteId, Function.identity(), (existing, replacement) -> existing));
+
+        // 队伍id与名称的映射
+        Set<Long> teamIds = athletes.stream().map(GameAthleteVo::getTeamId).collect(Collectors.toSet());
+        Map<Long, String>  teamMap = gameTeamService.queryTeamIdAndName(teamIds);
+
+        List<AppScoreVo> result = result = athletes.stream().map(athlete -> {
+            AppScoreVo vo = new AppScoreVo();
+            vo.setProjectId(projectId);
+            vo.setProjectName(project != null ? project.getProjectName() : "");
+            vo.setClassification(project != null ? project.getClassification() : "");
+            vo.setAthleteId(athlete.getAthleteId());
+            vo.setAthleteName(athlete.getName());
+            vo.setTeamId(athlete.getTeamId());
+
+            // 获取队伍信息
+            if (athlete.getTeamId() != null) {
+                String teamName = teamMap.get(athlete.getTeamId());
+                vo.setTeamName(teamName != null ? teamName : "");
+            }
+
+            // 获取成绩信息
+            GameScoreVo score = scoreMap.get(athlete.getAthleteId());
+            if (score != null) {
+                vo.setIndividualPerformance(convertToBigDecimal(score.getIndividualPerformance()));
+                vo.setTeamPerformance(convertToBigDecimal(score.getTeamPerformance()));
+                vo.setScorePoint(score.getScorePoint());
+                vo.setScoreRank(score.getScoreRank());
+            } else {
+                vo.setIndividualPerformance(BigDecimal.ZERO);
+                vo.setTeamPerformance(BigDecimal.ZERO);
+                vo.setScorePoint(0);
+                vo.setScoreRank(0);
             }
+
+            return vo;
+        }).toList();
+
+        // 按排名排序
+        return result.stream()
+            .sorted(Comparator.comparing(AppScoreVo::getScoreRank))
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * 安全地将对象转换为BigDecimal
+     * @param value 待转换的值
+     * @return BigDecimal值
+     */
+    private BigDecimal convertToBigDecimal(Object value) {
+        if (value == null) {
+            return BigDecimal.ZERO;
+        }
+
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        } else if (value instanceof Double) {
+            return BigDecimal.valueOf((Double) value);
+        } else if (value instanceof Float) {
+            return BigDecimal.valueOf((Float) value);
+        } else if (value instanceof String) {
+            try {
+                return new BigDecimal((String) value);
+            } catch (NumberFormatException e) {
+                return BigDecimal.ZERO;
+            }
+        } else if (value instanceof Integer) {
+            return BigDecimal.valueOf((Integer) value);
+        } else if (value instanceof Long) {
+            return BigDecimal.valueOf((Long) value);
         } else {
-            // 降序项目:成绩越大,积分分配越多
-            for (int i = 0; i < sortedTeams.size(); i++) {
-                Map.Entry<Long, Integer> teamEntry = sortedTeams.get(i);
-                Long teamId = teamEntry.getKey();
-                int rank = i + 1;
-                int points = i < pointValues.size() ? pointValues.get(i) : 0;
+            return BigDecimal.ZERO;
+        }
+    }
 
-                // 更新该队伍所有运动员的成绩记录
-                for (GameScoreVo score : allScores) {
-                    if (teamId.equals(score.getTeamId())) {
-                        GameScoreBo updateBo = new GameScoreBo();
-                        updateBo.setScoreId(score.getScoreId());
-                        updateBo.setScoreRank(rank);
-                        updateBo.setScorePoint(points); // 使用队伍排名对应的积分
-                        updateBo.setEventId(eventId);
-                        updateBo.setProjectId(projectId);
-
-                        updateByBo(updateBo);
-                    }
+
+    /**
+     * 动态列导出Excel
+     */
+    private void exportExcelWithDynamicColumns(List<GameScoreSummaryExportVo> exportData,
+                                            List<GameEventProjectVo> projects,
+                                            HttpServletResponse response) throws Exception {
+        // 创建动态列的数据结构
+        List<Map<String, Object>> dynamicData = new ArrayList<>();
+
+        for (GameScoreSummaryExportVo exportVo : exportData) {
+            Map<String, Object> rowData = new HashMap<>();
+
+            // 基础列
+            rowData.put("序号", exportVo.getSerialNumber());
+            rowData.put("代表队名称", exportVo.getTeamName());
+            rowData.put("排名", exportVo.getRank());
+            rowData.put("总分", exportVo.getTotalScore());
+
+            // 动态项目列
+            for (GameEventProjectVo project : projects) {
+                String projectName = project.getProjectName();
+                Integer score = exportVo.getProjectScore(projectName);
+                rowData.put(projectName, score != null ? score : 0);
+            }
+
+            // 加分列
+            rowData.put("加分", exportVo.getExtraScore());
+
+            dynamicData.add(rowData);
+        }
+
+        // 使用Apache POI直接操作Excel
+        exportExcelWithPOI(dynamicData, projects, response);
+    }
+
+    /**
+     * 使用Apache POI导出动态列Excel
+     */
+    private void exportExcelWithPOI(List<Map<String, Object>> dynamicData,
+                                   List<GameEventProjectVo> projects,
+                                   HttpServletResponse response) throws Exception {
+        try (Workbook workbook = new XSSFWorkbook()) {
+            Sheet sheet = workbook.createSheet("成绩汇总表");
+
+            // 创建标题行样式
+            CellStyle headerStyle = createHeaderStyle(workbook);
+
+            // 创建数据行样式 - 奇数行(浅色背景)
+            CellStyle dataStyleOdd = createDataStyle(workbook, IndexedColors.WHITE);
+
+            // 创建数据行样式 - 偶数行(深色背景)
+            CellStyle dataStyleEven = createDataStyle(workbook, IndexedColors.GREY_25_PERCENT);
+
+            // 创建标题行
+            Row headerRow = sheet.createRow(0);
+            int colIndex = 0;
+
+            // 基础列标题
+            String[] baseHeaders = {"序号", "代表队名称", "排名", "总分"};
+            for (String header : baseHeaders) {
+                Cell cell = headerRow.createCell(colIndex++);
+                cell.setCellValue(header);
+                cell.setCellStyle(headerStyle);
+            }
+
+            // 动态项目列标题
+            for (GameEventProjectVo project : projects) {
+                Cell cell = headerRow.createCell(colIndex++);
+                cell.setCellValue(project.getProjectName());
+                cell.setCellStyle(headerStyle);
+            }
+
+            // 加分列标题
+            Cell extraCell = headerRow.createCell(colIndex++);
+            extraCell.setCellValue("加分");
+            extraCell.setCellStyle(headerStyle);
+
+            // 填充数据行
+            int rowIndex = 1;
+            for (Map<String, Object> rowData : dynamicData) {
+                Row dataRow = sheet.createRow(rowIndex++);
+                colIndex = 0;
+
+                // 选择行样式(奇数行或偶数行)
+                CellStyle currentRowStyle = (rowIndex % 2 == 0) ? dataStyleEven : dataStyleOdd;
+
+                // 基础列数据
+                createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("序号"), currentRowStyle);
+                createCellWithStyle(dataRow, colIndex++, (String) rowData.get("代表队名称"), currentRowStyle);
+                createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("排名"), currentRowStyle);
+                createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("总分"), currentRowStyle);
+
+                // 动态项目列数据
+                for (GameEventProjectVo project : projects) {
+                    String projectName = project.getProjectName();
+                    Object scoreObj = rowData.get(projectName);
+                    int score = scoreObj != null ? (Integer) scoreObj : 0;
+                    createCellWithStyle(dataRow, colIndex++, score, currentRowStyle);
+                }
+
+                // 加分列数据
+                createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("加分"), currentRowStyle);
+            }
+
+            // 自动调整列宽
+            for (int i = 0; i < colIndex; i++) {
+                sheet.autoSizeColumn(i);
+                // 设置最小列宽,确保内容不会太挤
+                if (sheet.getColumnWidth(i) < 3000) {
+                    sheet.setColumnWidth(i, 3000);
                 }
             }
+
+            // 写入响应流
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("成绩汇总表", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            workbook.write(response.getOutputStream());
         }
+    }
 
-        return true;
+    /**
+     * 创建标题行样式
+     */
+    private CellStyle createHeaderStyle(Workbook workbook) {
+        CellStyle headerStyle = workbook.createCellStyle();
+
+        // 字体样式
+        Font headerFont = workbook.createFont();
+        headerFont.setBold(true);
+        headerFont.setFontHeightInPoints((short) 12);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        headerStyle.setFont(headerFont);
+
+        // 对齐方式
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        // 背景色
+        headerStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex());
+        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+        // 边框样式
+        headerStyle.setBorderTop(BorderStyle.THIN);
+        headerStyle.setBorderBottom(BorderStyle.THIN);
+        headerStyle.setBorderLeft(BorderStyle.THIN);
+        headerStyle.setBorderRight(BorderStyle.THIN);
+        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
+        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
+        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
+
+        return headerStyle;
+    }
+
+    /**
+     * 创建数据行样式
+     */
+    private CellStyle createDataStyle(Workbook workbook, IndexedColors backgroundColor) {
+        CellStyle dataStyle = workbook.createCellStyle();
+
+        // 字体样式
+        Font dataFont = workbook.createFont();
+        dataFont.setFontHeightInPoints((short) 11);
+        dataStyle.setFont(dataFont);
+
+        // 对齐方式
+        dataStyle.setAlignment(HorizontalAlignment.CENTER);
+        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        // 背景色
+        dataStyle.setFillForegroundColor(backgroundColor.getIndex());
+        dataStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+        // 边框样式
+        dataStyle.setBorderTop(BorderStyle.THIN);
+        dataStyle.setBorderBottom(BorderStyle.THIN);
+        dataStyle.setBorderLeft(BorderStyle.THIN);
+        dataStyle.setBorderRight(BorderStyle.THIN);
+        dataStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
+        dataStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+        dataStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
+        dataStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
+
+        return dataStyle;
+    }
+
+    /**
+     * 创建单元格并设置样式
+     */
+    private void createCellWithStyle(Row row, int colIndex, Object value, CellStyle style) {
+        Cell cell = row.createCell(colIndex);
+
+        if (value instanceof Integer) {
+            cell.setCellValue((Integer) value);
+        } else if (value instanceof String) {
+            cell.setCellValue((String) value);
+        } else if (value instanceof Double) {
+            cell.setCellValue((Double) value);
+        } else if (value != null) {
+            cell.setCellValue(value.toString());
+        } else {
+            cell.setCellValue("");
+        }
+
+        cell.setCellStyle(style);
     }
 }

+ 1 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java

@@ -389,7 +389,7 @@ public class GameTeamServiceImpl implements IGameTeamService {
     public Map<Long, String> queryTeamIdAndName(Set<Long> teamIds) {
         List<GameTeamVo> list = baseMapper.selectVoList(
             Wrappers.lambdaQuery(GameTeam.class)
-                .in(GameTeam::getEventId, teamIds)
+                .in(GameTeam::getTeamId, teamIds)
         );
         Map<Long, String> map = list.stream()
             .collect(Collectors.toMap(GameTeamVo::getTeamId, GameTeamVo::getTeamName));