feat: 添加报名详情页4个统计表Excel导出功能

- 参赛人数统计:按单位统计单人/集体项目、男女人数
- 项目时间统计:按项目统计人数、组数、时长
- 金额统计:按单位统计项目数和金额
- 编排表号统计:导出场地、时段、表号信息

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Z-WICK
2026-01-26 11:35:55 +08:00
co-authored by factory-droid[bot]
parent db6f85eef3
commit 0a90f7e079
5 changed files with 482 additions and 7 deletions
@@ -1,5 +1,6 @@
package org.springblade.modules.martial.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
@@ -7,14 +8,14 @@ import lombok.AllArgsConstructor;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.modules.martial.excel.AthleteExportExcel;
import org.springblade.modules.martial.excel.ResultExportExcel;
import org.springblade.modules.martial.excel.ScheduleExportExcel;
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
import org.springblade.modules.martial.excel.*;
import org.springblade.modules.martial.pojo.dto.CompetitionGroupDTO;
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
import org.springblade.modules.martial.pojo.entity.MartialProject;
import org.springblade.modules.martial.pojo.entity.MartialVenue;
import org.springblade.modules.martial.pojo.vo.CertificateVO;
import org.springblade.modules.martial.service.IMartialAthleteService;
import org.springblade.modules.martial.service.IMartialResultService;
import org.springblade.modules.martial.service.IMartialScheduleService;
import org.springblade.modules.martial.service.*;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@@ -32,6 +33,8 @@ public class MartialExportController {
private final IMartialResultService resultService;
private final IMartialAthleteService athleteService;
private final IMartialScheduleService scheduleService;
private final IMartialProjectService projectService;
private final IMartialVenueService venueService;
@GetMapping("/results")
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
@@ -111,4 +114,279 @@ public class MartialExportController {
CertificateVO certificate = resultService.generateCertificateData(resultId);
return R.data(certificate);
}
// ========== 报名详情统计导出 ==========
@GetMapping("/registration/participants")
@Operation(summary = "导出参赛人数统计", description = "按单位统计参赛人数")
public void exportParticipantStats(@RequestParam Long competitionId, HttpServletResponse response) {
List<ParticipantStatsExcel> list = buildParticipantStats(competitionId);
String fileName = "参赛人数统计_" + DateUtil.today();
ExcelUtil.export(response, fileName, "参赛人数统计", list, ParticipantStatsExcel.class);
}
@GetMapping("/registration/project-time")
@Operation(summary = "导出项目时间统计", description = "按项目统计时间")
public void exportProjectTimeStats(@RequestParam Long competitionId, HttpServletResponse response) {
List<ProjectTimeStatsExcel> list = buildProjectTimeStats(competitionId);
String fileName = "项目时间统计_" + DateUtil.today();
ExcelUtil.export(response, fileName, "项目时间统计", list, ProjectTimeStatsExcel.class);
}
@GetMapping("/registration/amount")
@Operation(summary = "导出金额统计", description = "按单位统计金额")
public void exportAmountStats(@RequestParam Long competitionId, HttpServletResponse response) {
List<AmountStatsExcel> list = buildAmountStats(competitionId);
String fileName = "金额统计_" + DateUtil.today();
ExcelUtil.export(response, fileName, "金额统计", list, AmountStatsExcel.class);
}
@GetMapping("/registration/schedule-table")
@Operation(summary = "导出编排表号统计", description = "导出编排表号")
public void exportScheduleTableStats(@RequestParam Long competitionId, HttpServletResponse response) {
List<ScheduleTableStatsExcel> list = buildScheduleTableStats(competitionId);
String fileName = "编排表号统计_" + DateUtil.today();
ExcelUtil.export(response, fileName, "编排表号统计", list, ScheduleTableStatsExcel.class);
}
private List<ParticipantStatsExcel> buildParticipantStats(Long competitionId) {
LambdaQueryWrapper<MartialAthlete> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(MartialAthlete::getCompetitionId, competitionId)
.eq(MartialAthlete::getIsDeleted, 0);
List<MartialAthlete> athletes = athleteService.list(wrapper);
java.util.Map<Long, MartialProject> projectMap = new java.util.HashMap<>();
java.util.Set<Long> projectIds = athletes.stream()
.map(MartialAthlete::getProjectId)
.filter(java.util.Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (!projectIds.isEmpty()) {
projectService.listByIds(projectIds).forEach(p -> projectMap.put(p.getId(), p));
}
java.util.Map<String, ParticipantStatsExcel> orgMap = new java.util.LinkedHashMap<>();
java.util.Map<String, java.util.Set<String>> orgIdCards = new java.util.HashMap<>();
for (MartialAthlete athlete : athletes) {
String org = athlete.getOrganization();
if (org == null || org.isEmpty()) org = "未知单位";
orgMap.computeIfAbsent(org, k -> {
ParticipantStatsExcel excel = new ParticipantStatsExcel();
excel.setOrganization(k);
excel.setSingleCount(0);
excel.setTeamCount(0);
excel.setMaleCount(0);
excel.setFemaleCount(0);
excel.setTotal(0);
return excel;
});
orgIdCards.computeIfAbsent(org, k -> new java.util.HashSet<>());
ParticipantStatsExcel stat = orgMap.get(org);
MartialProject project = projectMap.get(athlete.getProjectId());
int projectType = (project != null && project.getType() != null) ? project.getType() : 1;
if (projectType == 1) {
stat.setSingleCount(stat.getSingleCount() + 1);
} else {
stat.setTeamCount(stat.getTeamCount() + 1);
}
String idCard = athlete.getIdCard();
if (idCard != null && !idCard.isEmpty() && !orgIdCards.get(org).contains(idCard)) {
orgIdCards.get(org).add(idCard);
stat.setTotal(stat.getTotal() + 1);
if (athlete.getGender() != null && athlete.getGender() == 1) {
stat.setMaleCount(stat.getMaleCount() + 1);
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
stat.setFemaleCount(stat.getFemaleCount() + 1);
}
}
}
java.util.List<ParticipantStatsExcel> result = new java.util.ArrayList<>(orgMap.values());
for (int i = 0; i < result.size(); i++) {
result.get(i).setIndex(i + 1);
}
return result;
}
private List<ProjectTimeStatsExcel> buildProjectTimeStats(Long competitionId) {
LambdaQueryWrapper<MartialAthlete> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(MartialAthlete::getCompetitionId, competitionId)
.eq(MartialAthlete::getIsDeleted, 0);
List<MartialAthlete> athletes = athleteService.list(wrapper);
java.util.Map<Long, MartialProject> projectMap = new java.util.HashMap<>();
java.util.Set<Long> projectIds = athletes.stream()
.map(MartialAthlete::getProjectId)
.filter(java.util.Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (!projectIds.isEmpty()) {
projectService.listByIds(projectIds).forEach(p -> projectMap.put(p.getId(), p));
}
java.util.Map<Long, Integer> projectAthleteCount = new java.util.HashMap<>();
for (MartialAthlete athlete : athletes) {
Long projectId = athlete.getProjectId();
if (projectId != null) {
projectAthleteCount.merge(projectId, 1, Integer::sum);
}
}
java.util.List<ProjectTimeStatsExcel> result = new java.util.ArrayList<>();
int index = 1;
for (java.util.Map.Entry<Long, Integer> entry : projectAthleteCount.entrySet()) {
MartialProject project = projectMap.get(entry.getKey());
if (project != null) {
ProjectTimeStatsExcel excel = new ProjectTimeStatsExcel();
excel.setIndex(index++);
excel.setProjectName(project.getProjectName());
excel.setProjectType(project.getType() != null && project.getType() == 2 ? "集体" : "单人");
excel.setAthleteCount(entry.getValue());
excel.setGroupCount(project.getType() != null && project.getType() == 2 ? 1 : entry.getValue());
excel.setEstimatedDuration(project.getEstimatedDuration());
excel.setProjectCode(project.getProjectCode());
result.add(excel);
}
}
return result;
}
private List<AmountStatsExcel> buildAmountStats(Long competitionId) {
LambdaQueryWrapper<MartialAthlete> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(MartialAthlete::getCompetitionId, competitionId)
.eq(MartialAthlete::getIsDeleted, 0);
List<MartialAthlete> athletes = athleteService.list(wrapper);
java.util.Map<Long, MartialProject> projectMap = new java.util.HashMap<>();
java.util.Set<Long> projectIds = athletes.stream()
.map(MartialAthlete::getProjectId)
.filter(java.util.Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (!projectIds.isEmpty()) {
projectService.listByIds(projectIds).forEach(p -> projectMap.put(p.getId(), p));
}
java.util.Map<String, AmountStatsExcel> orgMap = new java.util.LinkedHashMap<>();
java.util.Map<String, java.util.Set<Long>> orgProjectIds = new java.util.HashMap<>();
for (MartialAthlete athlete : athletes) {
String org = athlete.getOrganization();
if (org == null || org.isEmpty()) org = "未知单位";
orgMap.computeIfAbsent(org, k -> {
AmountStatsExcel excel = new AmountStatsExcel();
excel.setOrganization(k);
excel.setProjectCount(0);
excel.setTotalAmount(java.math.BigDecimal.ZERO);
return excel;
});
orgProjectIds.computeIfAbsent(org, k -> new java.util.HashSet<>());
Long projectId = athlete.getProjectId();
if (projectId != null && !orgProjectIds.get(org).contains(projectId)) {
orgProjectIds.get(org).add(projectId);
AmountStatsExcel stat = orgMap.get(org);
stat.setProjectCount(stat.getProjectCount() + 1);
MartialProject project = projectMap.get(projectId);
if (project != null && project.getPrice() != null) {
stat.setTotalAmount(stat.getTotalAmount().add(project.getPrice()));
}
}
}
java.util.List<AmountStatsExcel> result = new java.util.ArrayList<>(orgMap.values());
for (int i = 0; i < result.size(); i++) {
result.get(i).setIndex(i + 1);
}
return result;
}
private List<ScheduleTableStatsExcel> buildScheduleTableStats(Long competitionId) {
ScheduleResultDTO scheduleResult = scheduleService.getScheduleResult(competitionId);
if (scheduleResult == null || scheduleResult.getCompetitionGroups() == null) {
return new java.util.ArrayList<>();
}
LambdaQueryWrapper<MartialVenue> venueWrapper = new LambdaQueryWrapper<>();
venueWrapper.eq(MartialVenue::getCompetitionId, competitionId)
.eq(MartialVenue::getIsDeleted, 0);
List<MartialVenue> venues = venueService.list(venueWrapper);
java.util.Map<Long, MartialVenue> venueMap = venues.stream()
.collect(java.util.stream.Collectors.toMap(MartialVenue::getId, v -> v));
java.util.List<ScheduleTableStatsExcel> result = new java.util.ArrayList<>();
java.util.List<CompetitionGroupDTO> groups = scheduleResult.getCompetitionGroups();
for (int i = 0; i < groups.size(); i++) {
CompetitionGroupDTO group = groups.get(i);
ScheduleTableStatsExcel excel = new ScheduleTableStatsExcel();
excel.setIndex(i + 1);
excel.setProjectName(group.getTitle());
excel.setProjectType(group.getType());
int participantCount = group.getParticipants() != null ? group.getParticipants().size() : 0;
excel.setAthleteCount(participantCount);
excel.setGroupCount("集体".equals(group.getType()) ? 1 : participantCount);
excel.setEstimatedDuration(group.getEstimatedDuration() != null ? group.getEstimatedDuration() : 0);
MartialVenue venue = venueMap.get(group.getVenueId());
excel.setVenueName(venue != null ? venue.getVenueName() : "-");
String timeSlot = group.getTimeSlot();
if (timeSlot != null && timeSlot.contains(":")) {
int hour = Integer.parseInt(timeSlot.split(":")[0]);
excel.setTimeSlot(hour < 12 ? "上午" : "下午");
} else {
excel.setTimeSlot("-");
}
excel.setTableNo(generateTableNo(group, groups, venueMap));
result.add(excel);
}
return result;
}
private String generateTableNo(CompetitionGroupDTO group,
java.util.List<CompetitionGroupDTO> allGroups,
java.util.Map<Long, MartialVenue> venueMap) {
int venueNo = 1;
if (group.getVenueId() != null) {
MartialVenue venue = venueMap.get(group.getVenueId());
if (venue != null && venue.getVenueName() != null) {
java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("\\d+").matcher(venue.getVenueName());
if (matcher.find()) {
venueNo = Integer.parseInt(matcher.group());
}
}
}
int period = 1;
if (group.getTimeSlot() != null && group.getTimeSlot().contains(":")) {
int hour = Integer.parseInt(group.getTimeSlot().split(":")[0]);
period = hour < 12 ? 1 : 2;
}
final int finalPeriod = period;
final Long venueId = group.getVenueId();
java.util.List<CompetitionGroupDTO> sameSlotGroups = allGroups.stream()
.filter(g -> {
if (!java.util.Objects.equals(g.getVenueId(), venueId)) return false;
if (g.getTimeSlot() == null || !g.getTimeSlot().contains(":")) return false;
int gHour = Integer.parseInt(g.getTimeSlot().split(":")[0]);
return (gHour < 12 ? 1 : 2) == finalPeriod;
})
.collect(java.util.stream.Collectors.toList());
int orderIndex = 0;
for (int i = 0; i < sameSlotGroups.size(); i++) {
if (java.util.Objects.equals(sameSlotGroups.get(i).getId(), group.getId())) {
orderIndex = i + 1;
break;
}
}
return String.format("%d%d%02d", venueNo, period, orderIndex);
}
}
@@ -0,0 +1,39 @@
package org.springblade.modules.martial.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 金额统计导出Excel
*/
@Data
@ColumnWidth(15)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class AmountStatsExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("序号")
@ColumnWidth(8)
private Integer index;
@ExcelProperty("学校/单位")
@ColumnWidth(25)
private String organization;
@ExcelProperty("项目数")
@ColumnWidth(10)
private Integer projectCount;
@ExcelProperty("金额")
@ColumnWidth(12)
private BigDecimal totalAmount;
}
@@ -0,0 +1,50 @@
package org.springblade.modules.martial.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 参赛人数统计导出Excel
*/
@Data
@ColumnWidth(15)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class ParticipantStatsExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("序号")
@ColumnWidth(8)
private Integer index;
@ExcelProperty("单位")
@ColumnWidth(25)
private String organization;
@ExcelProperty("单人项目")
@ColumnWidth(12)
private Integer singleCount;
@ExcelProperty("集体项目")
@ColumnWidth(12)
private Integer teamCount;
@ExcelProperty("")
@ColumnWidth(8)
private Integer maleCount;
@ExcelProperty("")
@ColumnWidth(8)
private Integer femaleCount;
@ExcelProperty("合计")
@ColumnWidth(8)
private Integer total;
}
@@ -0,0 +1,50 @@
package org.springblade.modules.martial.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 项目时间统计导出Excel
*/
@Data
@ColumnWidth(15)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class ProjectTimeStatsExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("序号")
@ColumnWidth(8)
private Integer index;
@ExcelProperty("项目")
@ColumnWidth(20)
private String projectName;
@ExcelProperty("单人/集体")
@ColumnWidth(12)
private String projectType;
@ExcelProperty("人数/集体")
@ColumnWidth(12)
private Integer athleteCount;
@ExcelProperty("组数")
@ColumnWidth(8)
private Integer groupCount;
@ExcelProperty("时长(分)")
@ColumnWidth(10)
private Integer estimatedDuration;
@ExcelProperty("项目编码")
@ColumnWidth(12)
private String projectCode;
}
@@ -0,0 +1,58 @@
package org.springblade.modules.martial.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 编排表号统计导出Excel
*/
@Data
@ColumnWidth(15)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class ScheduleTableStatsExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("序号")
@ColumnWidth(8)
private Integer index;
@ExcelProperty("项目")
@ColumnWidth(20)
private String projectName;
@ExcelProperty("单人/集体")
@ColumnWidth(12)
private String projectType;
@ExcelProperty("人数")
@ColumnWidth(8)
private Integer athleteCount;
@ExcelProperty("组数")
@ColumnWidth(8)
private Integer groupCount;
@ExcelProperty("时长(分)")
@ColumnWidth(10)
private Integer estimatedDuration;
@ExcelProperty("场地")
@ColumnWidth(12)
private String venueName;
@ExcelProperty("时段")
@ColumnWidth(8)
private String timeSlot;
@ExcelProperty("表号")
@ColumnWidth(10)
private String tableNo;
}