Browse Source

feat(game-event): 导出成绩汇总表功能

- 新增导出成绩汇总表接口和实现方法
- 创建成绩汇总导出VO类
- 使用Apache POI实现动态列Excel导出
- 添加数据处理和样式设置相关方法
zhou 3 days ago
parent
commit
30b7878a56

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

@@ -151,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);
+    }
 }

+ 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;
+    }
+} 

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

@@ -9,6 +9,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 +109,11 @@ public interface IGameScoreService {
      * @return 是否成功
      */
     Boolean recalculateRankingsAndPoints(Long eventId, Long projectId);
+
+    /**
+     * 导出成绩汇总表
+     * @param eventId 赛事ID
+     * @param response HTTP响应对象
+     */
+    void exportScoresSummary(Long eventId, HttpServletResponse response);
 }

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

@@ -34,6 +34,17 @@ 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.vo.GameScoreSummaryExportVo;
+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业务层处理
@@ -780,5 +791,307 @@ public class GameScoreServiceImpl implements IGameScoreService {
         return true;
     }
 
+    /**
+     * 导出成绩汇总表
+     * @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());
+        }
+    }
+
+    /**
+     * 动态列导出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());
+        }
+    }
+
+    /**
+     * 创建标题行样式
+     */
+    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);
+    }
 }