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