فهرست منبع

feat(game-score): 添加成绩详情导出功能

- 新增导出成绩详情接口/exportScoresDetail- 实现赛事项目成绩数据的完整导出逻辑
- 支持个人项目和团体项目的区分处理-为每个项目创建独立的Excel Sheet页
- 添加Excel样式设置和列宽自适应功能
- 处理Sheet名称的特殊字符和长度限制
- 完善导出过程的日志记录和异常处理
zhou 2 هفته پیش
والد
کامیت
e95cfed032

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

@@ -214,4 +214,14 @@ public class GameScoreController extends BaseController {
             throw new RuntimeException("参数解析失败:" + e.getMessage());
         }
     }
+
+    /**
+     * 导出成绩详情数据(全部)
+     */
+    @SaCheckPermission("system:gameScore:export")
+    @Log(title = "导出成绩详情", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportScoresDetail")
+    public void exportScoresDetail(@RequestParam("eventId") Long eventId, HttpServletResponse response) {
+        gameScoreService.exportScoresDetail(eventId, response);
+    }
 }

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

@@ -139,4 +139,11 @@ public interface IGameScoreService {
      * 导出加分Excel
      */
     void exportBonusExcel(Map<String, Object> data, HttpServletResponse response);
+
+    /**
+     * 导出成绩详情数据(全部)
+     * @param eventId 赛事ID
+     * @param response HTTP响应对象
+     */
+    void exportScoresDetail(Long eventId, HttpServletResponse response);
 }

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

@@ -1493,4 +1493,343 @@ public class GameScoreServiceImpl implements IGameScoreService {
             throw new RuntimeException("导出失败:" + e.getMessage());
         }
     }
+
+    /**
+     * 导出积分明细Excel
+     * @param eventId 赛事ID
+     * @param response HTTP响应对象
+     */
+    @Override
+    public void exportScoresDetail(Long eventId, HttpServletResponse response) {
+        try {
+            log.info("开始导出成绩详情,eventId: {}", eventId);
+            
+            // 1. 获取所有项目
+            List<GameEventProjectVo> projects = gameEventProjectService.queryListByEventId(eventId);
+            if (projects.isEmpty()) {
+                throw new RuntimeException("未找到赛事项目");
+            }
+            
+            // 2. 创建Excel工作簿
+            try (Workbook workbook = new XSSFWorkbook()) {
+                
+                // 3. 创建样式
+                CellStyle headerStyle = createHeaderStyle(workbook);
+                CellStyle dataStyleOdd = createDataStyle(workbook, IndexedColors.WHITE);
+                CellStyle dataStyleEven = createDataStyle(workbook, IndexedColors.GREY_25_PERCENT);
+                
+                // 4. 为每个项目创建Sheet页
+                for (GameEventProjectVo project : projects) {
+                    createProjectSheet(workbook, project, eventId, headerStyle, dataStyleOdd, dataStyleEven);
+                }
+                
+                // 5. 设置响应头并输出
+                setResponseHeaders(response, "成绩详情表");
+                workbook.write(response.getOutputStream());
+            }
+            
+            log.info("成绩详情导出完成");
+            
+        } catch (Exception e) {
+            log.error("导出成绩详情失败", e);
+            throw new RuntimeException("导出失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 为单个项目创建Sheet页
+     */
+    private void createProjectSheet(Workbook workbook, GameEventProjectVo project, Long eventId, 
+        CellStyle headerStyle, CellStyle dataStyleOdd, CellStyle dataStyleEven) {
+
+        // 1. 创建Sheet页,名称限制在31个字符内
+        String sheetName = truncateSheetName(project.getProjectName());
+        Sheet sheet = workbook.createSheet(sheetName);
+
+        // 2. 获取项目详细数据
+        List<Map<String, Object>> projectData = getProjectScoreDataAll(
+            eventId, 
+            project.getProjectId(), 
+            project.getClassification()
+        );
+
+        // 3. 创建标题行
+        createProjectHeaderRow(sheet, project, headerStyle);
+
+        // 4. 填充数据行
+        fillProjectDataRows(sheet, projectData, project, dataStyleOdd, dataStyleEven);
+
+        // 5. 自动调整列宽
+        autoSizeColumns(sheet);
+    }
+
+    /**
+     * 获取项目成绩数据(全部,不分页)
+     */
+    private List<Map<String, Object>> getProjectScoreDataAll(Long eventId, Long projectId, String classification) {
+        log.info("获取项目全部数据: eventId={}, projectId={}, classification={}", eventId, projectId, classification);
+        
+        if ("0".equals(classification)) {
+            // 个人项目:获取所有运动员数据
+            return getIndividualProjectDataAll(eventId, projectId);
+        } else {
+            // 团体项目:获取所有队伍数据
+            return getTeamProjectDataAll(eventId, projectId);
+        }
+    }
+
+    /**
+     * 获取个人项目全部数据(不分页)
+     */
+    private List<Map<String, Object>> getIndividualProjectDataAll(Long eventId, Long projectId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        
+        // 查询参与该项目的所有运动员(不分页)
+        List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, null);
+        
+        for (GameAthleteVo athlete : athletes) {
+            Map<String, Object> data = new HashMap<>();
+            
+            // 基础信息
+            data.put("athleteId", athlete.getAthleteId());
+            data.put("userId", athlete.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("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());
+                data.put("individualPerformance", score.getIndividualPerformance());
+                data.put("teamPerformance", score.getTeamPerformance());
+                data.put("scoreRank", score.getScoreRank());
+                data.put("updateTime", score.getUpdateTime());
+            } else {
+                // 设置默认值
+                data.put("scoreId", 0);
+                data.put("scorePoint", 0);
+                data.put("individualPerformance", 0.0);
+                data.put("teamPerformance", 0.0);
+                data.put("scoreRank", 0);
+                data.put("updateTime", "");
+            }
+            
+            resultList.add(data);
+        }
+        
+        return resultList;
+    }
+
+    /**
+     * 获取团体项目全部数据(不分页)
+     */
+    private List<Map<String, Object>> getTeamProjectDataAll(Long eventId, Long projectId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        Set<Long> processedTeamIds = new HashSet<>();
+        
+        // 查询参与该项目的所有运动员
+        List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, null);
+        
+        for (GameAthleteVo athlete : athletes) {
+            if (athlete.getTeamId() == null || processedTeamIds.contains(athlete.getTeamId())) {
+                continue;
+            }
+            
+            processedTeamIds.add(athlete.getTeamId());
+            
+            // 查询队伍信息
+            GameTeamVo team = gameTeamService.queryById(athlete.getTeamId());
+            if (team == null) {
+                continue;
+            }
+            
+            Map<String, Object> data = new HashMap<>();
+            data.put("teamId", team.getTeamId());
+            data.put("teamName", team.getTeamName());
+            data.put("teamCode", team.getTeamCode());
+            data.put("eventId", eventId);
+            data.put("projectId", projectId);
+            
+            // 查询成绩信息
+            GameScoreVo score = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), projectId);
+            if (score != null) {
+                data.put("scoreId", score.getScoreId());
+                data.put("scorePoint", score.getScorePoint());
+                data.put("individualPerformance", score.getIndividualPerformance());
+                data.put("teamPerformance", score.getTeamPerformance());
+                data.put("scoreRank", score.getScoreRank());
+                data.put("updateTime", score.getUpdateTime());
+            } else {
+                data.put("scoreId", 0);
+                data.put("scorePoint", 0);
+                data.put("individualPerformance", 0.0);
+                data.put("teamPerformance", 0.0);
+                data.put("scoreRank", 0);
+                data.put("updateTime", "");
+            }
+            
+            resultList.add(data);
+        }
+        
+        return resultList;
+    }
+
+    /**
+     * 创建项目Sheet页的标题行
+     */
+    private void createProjectHeaderRow(Sheet sheet, GameEventProjectVo project, CellStyle headerStyle) {
+        Row headerRow = sheet.createRow(0);
+        int colIndex = 0;
+        
+        // 根据项目类型确定列标题
+        if ("0".equals(project.getClassification())) {
+            // 个人项目列标题
+            String[] headers = {
+                "序号", "队伍编号", "队伍名称", "姓名", "号码", 
+                "个人成绩", "积分", "排名", "更新时间"
+            };
+            
+            for (String header : headers) {
+                Cell cell = headerRow.createCell(colIndex++);
+                cell.setCellValue(header);
+                cell.setCellStyle(headerStyle);
+            }
+        } else {
+            // 团体项目列标题
+            String[] headers = {
+                "序号", "队伍编号", "队伍名称", "团队成绩", 
+                "积分", "排名", "更新时间"
+            };
+            
+            for (String header : headers) {
+                Cell cell = headerRow.createCell(colIndex++);
+                cell.setCellValue(header);
+                cell.setCellStyle(headerStyle);
+            }
+        }
+    }
+
+    /**
+     * 填充项目数据行
+     */
+    private void fillProjectDataRows(Sheet sheet, List<Map<String, Object>> projectData, 
+    GameEventProjectVo project, CellStyle dataStyleOdd, CellStyle dataStyleEven) {
+
+        int rowIndex = 1;
+        for (int i = 0; i < projectData.size(); i++) {
+            Map<String, Object> data = projectData.get(i);
+            Row dataRow = sheet.createRow(rowIndex++);
+
+            // 选择行样式
+            CellStyle currentRowStyle = (rowIndex % 2 == 0) ? dataStyleEven : dataStyleOdd;
+
+            int colIndex = 0;
+
+            if ("0".equals(project.getClassification())) {
+                // 个人项目数据
+                createCellWithStyle(dataRow, colIndex++, i + 1, currentRowStyle); // 序号
+                createCellWithStyle(dataRow, colIndex++, data.get("teamCode"), currentRowStyle); // 队伍编号
+                createCellWithStyle(dataRow, colIndex++, data.get("teamName"), currentRowStyle); // 队伍名称
+                createCellWithStyle(dataRow, colIndex++, data.get("name"), currentRowStyle); // 姓名
+                createCellWithStyle(dataRow, colIndex++, data.get("athleteCode"), currentRowStyle); // 号码
+                createCellWithStyle(dataRow, colIndex++, data.get("individualPerformance"), currentRowStyle); // 个人成绩
+                createCellWithStyle(dataRow, colIndex++, data.get("scorePoint"), currentRowStyle); // 积分
+                createCellWithStyle(dataRow, colIndex++, data.get("scoreRank"), currentRowStyle); // 排名
+                createCellWithStyle(dataRow, colIndex++, data.get("updateTime"), currentRowStyle); // 更新时间
+            } else {
+                // 团体项目数据
+                createCellWithStyle(dataRow, colIndex++, i + 1, currentRowStyle); // 序号
+                createCellWithStyle(dataRow, colIndex++, data.get("teamCode"), currentRowStyle); // 队伍编号
+                createCellWithStyle(dataRow, colIndex++, data.get("teamName"), currentRowStyle); // 队伍名称
+                createCellWithStyle(dataRow, colIndex++, data.get("teamPerformance"), currentRowStyle); // 团队成绩
+                createCellWithStyle(dataRow, colIndex++, data.get("scorePoint"), currentRowStyle); // 积分
+                createCellWithStyle(dataRow, colIndex++, data.get("scoreRank"), currentRowStyle); // 排名
+                createCellWithStyle(dataRow, colIndex++, data.get("updateTime"), currentRowStyle); // 更新时间
+            }
+        }
+    }
+
+    /**
+     * 处理Sheet名称(Excel限制31个字符,不能包含特殊字符)
+     */
+    private String truncateSheetName(String projectName) {
+        if (projectName == null) {
+            return "项目";
+        }
+        
+        // 替换Excel不允许的特殊字符
+        String cleanName = projectName
+            .replace("*", "×")     // 星号替换为乘号
+            .replace("\\", "_")    // 反斜杠
+            .replace("/", "_")     // 正斜杠
+            .replace("?", "_")     // 问号
+            .replace("[", "(")     // 左方括号替换为左括号
+            .replace("]", ")")     // 右方括号替换为右括号
+            .replace(":", ":")    // 英文冒号替换为中文冒号
+            .trim();               // 去除首尾空格
+        
+        // 处理单引号问题
+        if (cleanName.startsWith("'") || cleanName.endsWith("'")) {
+            cleanName = cleanName.replace("'", "_");
+        }
+        
+        // 如果处理后为空,使用默认名称
+        if (cleanName.isEmpty()) {
+            cleanName = "项目";
+        }
+        
+        // 限制长度在31个字符内
+        if (cleanName.length() > 31) {
+            cleanName = cleanName.substring(0, 31);
+        }
+        
+        return cleanName;
+    }
+
+    /**
+     * 自动调整列宽
+     */
+    private void autoSizeColumns(Sheet sheet) {
+        if (sheet.getLastRowNum() > 0) {
+            Row firstRow = sheet.getRow(0);
+            if (firstRow != null) {
+                for (int i = 0; i < firstRow.getLastCellNum(); i++) {
+                    sheet.autoSizeColumn(i);
+                    // 设置最小列宽
+                    if (sheet.getColumnWidth(i) < 2000) {
+                        sheet.setColumnWidth(i, 2000);
+                    }
+                    // 设置最大列宽
+                    if (sheet.getColumnWidth(i) > 8000) {
+                        sheet.setColumnWidth(i, 8000);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置响应头
+     */
+    private void setResponseHeaders(HttpServletResponse response, String fileName) throws Exception {
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
+        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
+    }
 }