重构项4: 添加出场顺序显示功能
后端: - 新增LineupGroupVO和LineupParticipantVO类 - 在MartialMiniController中添加/schedule/status和/schedule/lineup接口 - 注入MartialScheduleStatusMapper和MartialScheduleGroupMapper Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,26 +10,19 @@ import org.springblade.core.tool.utils.DateUtil;
|
|||||||
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ResultExportExcel;
|
import org.springblade.modules.martial.excel.ResultExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
import org.springblade.modules.martial.service.IMartialResultService;
|
import org.springblade.modules.martial.service.IMartialResultService;
|
||||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出打印 控制器
|
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/export")
|
@RequestMapping("/martial/export")
|
||||||
@@ -40,67 +33,47 @@ public class MartialExportController {
|
|||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScheduleService scheduleService;
|
private final IMartialScheduleService scheduleService;
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.1: 导出成绩单
|
|
||||||
*/
|
|
||||||
@GetMapping("/results")
|
@GetMapping("/results")
|
||||||
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
||||||
public void exportResults(
|
public void exportResults(@RequestParam Long competitionId, @RequestParam(required = false) Long projectId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
@RequestParam(required = false) Long projectId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
||||||
String fileName = "成绩单_" + DateUtil.today();
|
String fileName = "成绩单_" + DateUtil.today();
|
||||||
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
||||||
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.2: 导出运动员名单
|
|
||||||
*/
|
|
||||||
@GetMapping("/athletes")
|
@GetMapping("/athletes")
|
||||||
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
||||||
public void exportAthletes(
|
public void exportAthletes(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
||||||
String fileName = "运动员名单_" + DateUtil.today();
|
String fileName = "运动员名单_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.3: 导出赛程表
|
|
||||||
*/
|
|
||||||
@GetMapping("/schedule")
|
@GetMapping("/schedule")
|
||||||
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
||||||
public void exportSchedule(
|
public void exportSchedule(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
||||||
String fileName = "赛程表_" + DateUtil.today();
|
String fileName = "赛程表_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@GetMapping("/schedule2")
|
||||||
* Task 3.4: 生成单个证书(HTML格式)
|
@Operation(summary = "导出赛程表-模板2", description = "按场地导出比赛时间表格式的赛程安排")
|
||||||
*/
|
public void exportScheduleTemplate2(@RequestParam Long competitionId, @RequestParam(required = false) Long venueId,
|
||||||
@GetMapping("/certificate/{resultId}")
|
@RequestParam(required = false) String venueName, @RequestParam(required = false) String timeSlot, HttpServletResponse response) {
|
||||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面,可打印为PDF")
|
List<ScheduleExportExcel2> list = scheduleService.exportScheduleTemplate2(competitionId, venueId);
|
||||||
public void generateCertificate(
|
String fileName = "比赛时间_" + (venueName != null ? venueName : "全部场地") + "_" + DateUtil.today();
|
||||||
@PathVariable Long resultId,
|
String sheetName = (venueName != null ? venueName : "全部场地") + (timeSlot != null ? "_" + timeSlot : "");
|
||||||
HttpServletResponse response
|
ExcelUtil.export(response, fileName, sheetName, list, ScheduleExportExcel2.class);
|
||||||
) throws IOException {
|
}
|
||||||
// 1. 获取证书数据
|
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
|
||||||
|
|
||||||
// 2. 读取HTML模板
|
@GetMapping("/certificate/{resultId}")
|
||||||
|
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面")
|
||||||
|
public void generateCertificate(@PathVariable Long resultId, HttpServletResponse response) throws IOException {
|
||||||
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
||||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// 3. 替换模板变量
|
|
||||||
String html = template
|
String html = template
|
||||||
.replace("${playerName}", certificate.getPlayerName())
|
.replace("${playerName}", certificate.getPlayerName())
|
||||||
.replace("${competitionName}", certificate.getCompetitionName())
|
.replace("${competitionName}", certificate.getCompetitionName())
|
||||||
@@ -109,15 +82,10 @@ public class MartialExportController {
|
|||||||
.replace("${medalClass}", certificate.getMedalClass())
|
.replace("${medalClass}", certificate.getMedalClass())
|
||||||
.replace("${organization}", certificate.getOrganization())
|
.replace("${organization}", certificate.getOrganization())
|
||||||
.replace("${issueDate}", certificate.getIssueDate());
|
.replace("${issueDate}", certificate.getIssueDate());
|
||||||
|
|
||||||
// 4. 返回HTML
|
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
response.getWriter().write(html);
|
response.getWriter().write(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 批量生成证书数据
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificates/batch")
|
@GetMapping("/certificates/batch")
|
||||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||||
@@ -125,14 +93,10 @@ public class MartialExportController {
|
|||||||
return R.data(certificates);
|
return R.data(certificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 获取单个证书数据(JSON格式)
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificate/data/{resultId}")
|
@GetMapping("/certificate/data/{resultId}")
|
||||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式),供前端渲染")
|
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式)")
|
||||||
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
return R.data(certificate);
|
return R.data(certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
|||||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupGroupVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupParticipantVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springblade.modules.martial.service.*;
|
import org.springblade.modules.martial.service.*;
|
||||||
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||||
@@ -27,6 +30,8 @@ import org.springblade.modules.martial.pojo.entity.MtVenue;
|
|||||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||||
import org.springblade.core.redis.cache.BladeRedis;
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleStatusMapper;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -34,6 +39,8 @@ import java.math.RoundingMode;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -60,6 +67,8 @@ public class MartialMiniController extends BladeController {
|
|||||||
private final IMartialScoreService scoreService;
|
private final IMartialScoreService scoreService;
|
||||||
private final BladeRedis bladeRedis;
|
private final BladeRedis bladeRedis;
|
||||||
private final IMartialResultService resultService;
|
private final IMartialResultService resultService;
|
||||||
|
private final MartialScheduleStatusMapper scheduleStatusMapper;
|
||||||
|
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||||
|
|
||||||
// Redis缓存key前缀
|
// Redis缓存key前缀
|
||||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
@@ -817,4 +826,145 @@ public class MartialMiniController extends BladeController {
|
|||||||
return R.data(list);
|
return R.data(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 出场顺序相关 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编排状态
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/status")
|
||||||
|
@Operation(summary = "获取编排状态", description = "检查赛事编排是否完成")
|
||||||
|
public R<Map<String, Object>> getScheduleStatus(@RequestParam Long competitionId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialScheduleStatus::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialScheduleStatus::getIsDeleted, 0);
|
||||||
|
wrapper.last("LIMIT 1");
|
||||||
|
MartialScheduleStatus status = scheduleStatusMapper.selectOne(wrapper);
|
||||||
|
|
||||||
|
if (status == null) {
|
||||||
|
result.put("isCompleted", false);
|
||||||
|
result.put("scheduleStatus", 0);
|
||||||
|
result.put("statusText", "未编排");
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCompleted = status.getScheduleStatus() != null && status.getScheduleStatus() == 2;
|
||||||
|
result.put("isCompleted", isCompleted);
|
||||||
|
result.put("scheduleStatus", status.getScheduleStatus());
|
||||||
|
result.put("statusText", getScheduleStatusText(status.getScheduleStatus()));
|
||||||
|
result.put("totalGroups", status.getTotalGroups());
|
||||||
|
result.put("totalParticipants", status.getTotalParticipants());
|
||||||
|
result.put("lockedTime", status.getLockedTime());
|
||||||
|
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getScheduleStatusText(Integer status) {
|
||||||
|
if (status == null) return "未编排";
|
||||||
|
switch (status) {
|
||||||
|
case 0: return "未编排";
|
||||||
|
case 1: return "编排中";
|
||||||
|
case 2: return "已锁定";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取出场顺序
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/lineup")
|
||||||
|
@Operation(summary = "获取出场顺序", description = "获取已编排的出场顺序列表")
|
||||||
|
public R<Map<String, Object>> getLineup(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam(required = false) Long projectId
|
||||||
|
) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// 使用现有mapper查询编排详情
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
|
||||||
|
if (details == null || details.isEmpty()) {
|
||||||
|
result.put("groups", new ArrayList<>());
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按项目过滤
|
||||||
|
if (projectId != null) {
|
||||||
|
// 需要通过groupName或其他字段判断项目,这里先获取项目名
|
||||||
|
MartialProject project = projectService.getById(projectId);
|
||||||
|
if (project != null) {
|
||||||
|
String projectName = project.getProjectName();
|
||||||
|
details = details.stream()
|
||||||
|
.filter(d -> d.getGroupName() != null && d.getGroupName().contains(projectName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为LineupGroupVO格式
|
||||||
|
Map<Long, LineupGroupVO> groupMap = new HashMap<>();
|
||||||
|
for (ScheduleGroupDetailVO detail : details) {
|
||||||
|
Long groupId = detail.getGroupId();
|
||||||
|
LineupGroupVO group = groupMap.get(groupId);
|
||||||
|
if (group == null) {
|
||||||
|
group = new LineupGroupVO();
|
||||||
|
group.setGroupId(groupId);
|
||||||
|
group.setGroupName(detail.getGroupName());
|
||||||
|
group.setCategory(detail.getCategory());
|
||||||
|
group.setVenueName(detail.getVenueName());
|
||||||
|
group.setTimeSlot(detail.getTimeSlot());
|
||||||
|
group.setTableNo(generateTableNo(detail));
|
||||||
|
group.setParticipants(new ArrayList<>());
|
||||||
|
groupMap.put(groupId, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加参赛者
|
||||||
|
if (detail.getParticipantId() != null) {
|
||||||
|
LineupParticipantVO participant = new LineupParticipantVO();
|
||||||
|
participant.setId(detail.getParticipantId());
|
||||||
|
participant.setOrder(detail.getPerformanceOrder() != null ? detail.getPerformanceOrder() : group.getParticipants().size() + 1);
|
||||||
|
participant.setPlayerName(detail.getPlayerName());
|
||||||
|
participant.setOrganization(detail.getOrganization());
|
||||||
|
participant.setStatus(detail.getScheduleStatus() != null ? detail.getScheduleStatus() : "waiting");
|
||||||
|
group.getParticipants().add(participant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("groups", new ArrayList<>(groupMap.values()));
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成表号: 场地(1位) + 时段(1位) + 序号(2位)
|
||||||
|
*/
|
||||||
|
private String generateTableNo(ScheduleGroupDetailVO detail) {
|
||||||
|
// 场地编号(简单取第一个数字或默认1)
|
||||||
|
int venueNo = 1;
|
||||||
|
if (detail.getVenueName() != null) {
|
||||||
|
String venueName = detail.getVenueName();
|
||||||
|
for (char c : venueName.toCharArray()) {
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
venueNo = Character.getNumericValue(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时段:上午=1, 下午=2
|
||||||
|
int period = 1;
|
||||||
|
if (detail.getTimeSlot() != null) {
|
||||||
|
try {
|
||||||
|
int hour = Integer.parseInt(detail.getTimeSlot().split(":")[0]);
|
||||||
|
period = hour < 12 ? 1 : 2;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序号:使用displayOrder或默认1
|
||||||
|
int orderNo = detail.getDisplayOrder() != null ? detail.getDisplayOrder() : 1;
|
||||||
|
|
||||||
|
return String.format("%d%d%02d", venueNo, period, orderNo);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,21 @@ import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
|||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springblade.modules.martial.service.*;
|
import org.springblade.modules.martial.service.*;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||||
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -45,9 +54,17 @@ public class MartialMiniController extends BladeController {
|
|||||||
private final IMartialJudgeService judgeService;
|
private final IMartialJudgeService judgeService;
|
||||||
private final IMartialCompetitionService competitionService;
|
private final IMartialCompetitionService competitionService;
|
||||||
private final IMartialVenueService venueService;
|
private final IMartialVenueService venueService;
|
||||||
|
private final IMtVenueService mtVenueService;
|
||||||
private final IMartialProjectService projectService;
|
private final IMartialProjectService projectService;
|
||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScoreService scoreService;
|
private final IMartialScoreService scoreService;
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
private final IMartialResultService resultService;
|
||||||
|
|
||||||
|
// Redis缓存key前缀
|
||||||
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
|
// 登录缓存过期时间(7天)
|
||||||
|
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录验证
|
* 登录验证
|
||||||
@@ -91,26 +108,55 @@ public class MartialMiniController extends BladeController {
|
|||||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||||
judgeInviteService.updateById(invite);
|
judgeInviteService.updateById(invite);
|
||||||
|
|
||||||
MartialVenue venue = null;
|
// 从 martial_venue 表获取场地信息
|
||||||
|
MartialVenue martialVenue = null;
|
||||||
if (invite.getVenueId() != null) {
|
if (invite.getVenueId() != null) {
|
||||||
venue = venueService.getById(invite.getVenueId());
|
martialVenue = venueService.getById(invite.getVenueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
MiniLoginVO vo = new MiniLoginVO();
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
vo.setToken(token);
|
vo.setToken(token);
|
||||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
vo.setMatchId(competition.getId());
|
vo.setMatchId(competition.getId());
|
||||||
vo.setMatchName(competition.getCompetitionName());
|
vo.setMatchName(competition.getCompetitionName());
|
||||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||||
competition.getCompetitionStartTime().toString() : "");
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
vo.setJudgeId(judge.getId());
|
vo.setJudgeId(judge.getId());
|
||||||
vo.setJudgeName(judge.getName());
|
vo.setJudgeName(judge.getName());
|
||||||
vo.setVenueId(venue != null ? venue.getId() : null);
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
vo.setVenueName(venue != null ? venue.getVenueName() : null);
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
vo.setProjects(projects);
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
return R.data(vo);
|
return R.data(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +198,136 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean success = scoreService.save(score);
|
boolean success = scoreService.save(score);
|
||||||
|
|
||||||
|
// 评分保存成功后,计算并更新选手总分
|
||||||
|
if (success) {
|
||||||
|
Long athleteId = parseLong(dto.getAthleteId());
|
||||||
|
Long projectId = parseLong(dto.getProjectId());
|
||||||
|
Long venueId = parseLong(dto.getVenueId());
|
||||||
|
if (athleteId != null && projectId != null) {
|
||||||
|
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算并更新选手总分
|
||||||
|
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
* 只有所有裁判都评分完成后才更新总分
|
||||||
|
*/
|
||||||
|
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||||
|
try {
|
||||||
|
// 1. 查询该场地的裁判员数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 2. 获取主裁判ID列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> scores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 4. 判断是否所有裁判都已评分
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了裁判数量,检查是否评分完成
|
||||||
|
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
||||||
|
// 未完成评分,清空总分
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null && athlete.getTotalScore() != null) {
|
||||||
|
athlete.setTotalScore(null);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 计算总分(去掉最高最低分取平均)
|
||||||
|
BigDecimal totalScore = calculateTotalScore(scores);
|
||||||
|
|
||||||
|
// 5. 更新选手总分
|
||||||
|
if (totalScore != null) {
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null) {
|
||||||
|
athlete.setTotalScore(totalScore);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录错误但不影响评分提交
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||||
|
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||||
|
*/
|
||||||
|
private int getRequiredJudgeCount(Long venueId) {
|
||||||
|
if (venueId == null || venueId <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||||
|
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||||
|
// Use distinct judge_id to count unique judges
|
||||||
|
return (int) judges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算总分
|
||||||
|
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
*/
|
||||||
|
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有分数并排序
|
||||||
|
List<BigDecimal> scoreValues = scores.stream()
|
||||||
|
.map(MartialScore::getScore)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int count = scoreValues.size();
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 3) {
|
||||||
|
// 裁判数量<3,直接取平均分
|
||||||
|
BigDecimal sum = scoreValues.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
||||||
|
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
||||||
|
|
||||||
|
// 计算平均分
|
||||||
|
BigDecimal sum = middleScores.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全地将String转换为Long
|
* 安全地将String转换为Long
|
||||||
@@ -171,8 +345,8 @@ public class MartialMiniController extends BladeController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(支持分页)
|
* 获取选手列表(支持分页)
|
||||||
* - 普通裁判:获取所有选手,标记是否已评分
|
* - 裁判员:获取所有选手,标记是否已评分
|
||||||
* - 裁判长:获取已有评分的选手列表
|
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/score/athletes")
|
@GetMapping("/score/athletes")
|
||||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||||
@@ -181,6 +355,7 @@ public class MartialMiniController extends BladeController {
|
|||||||
@RequestParam Integer refereeType,
|
@RequestParam Integer refereeType,
|
||||||
@RequestParam(required = false) Long projectId,
|
@RequestParam(required = false) Long projectId,
|
||||||
@RequestParam(required = false) Long venueId,
|
@RequestParam(required = false) Long venueId,
|
||||||
|
@RequestParam(required = false) Long competitionId,
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "10") Integer size
|
@RequestParam(defaultValue = "10") Integer size
|
||||||
) {
|
) {
|
||||||
@@ -188,6 +363,11 @@ public class MartialMiniController extends BladeController {
|
|||||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
|
||||||
|
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||||
|
if (competitionId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||||
|
}
|
||||||
|
|
||||||
if (projectId != null) {
|
if (projectId != null) {
|
||||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||||
}
|
}
|
||||||
@@ -196,35 +376,48 @@ public class MartialMiniController extends BladeController {
|
|||||||
|
|
||||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||||
|
|
||||||
// 2. 获取所有评分记录
|
// 2. 获取该场地所有主裁判的judge_id列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
// 添加场地过滤
|
||||||
|
if (venueId != null && venueId > 0) {
|
||||||
|
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||||
|
}
|
||||||
|
// 排除主裁判的评分
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
// 按选手ID分组统计评分
|
// 按选手ID分组统计评分
|
||||||
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||||
|
|
||||||
// 3. 根据裁判类型处理选手列表
|
// 4. 获取该场地的应评裁判数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 5. 根据裁判类型处理选手列表
|
||||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||||
|
|
||||||
if (refereeType == 1) {
|
if (refereeType == 1) {
|
||||||
// 裁判长:返回已有评分的选手
|
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||||
filteredList = athletes.stream()
|
filteredList = athletes.stream()
|
||||||
.filter(athlete -> {
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
|
||||||
return scores != null && !scores.isEmpty();
|
|
||||||
})
|
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
} else {
|
} else {
|
||||||
// 普通裁判:返回所有选手,标记是否已评分
|
// 裁判员:返回所有选手,标记是否已评分
|
||||||
filteredList = athletes.stream()
|
filteredList = athletes.stream()
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 手动分页
|
// 6. 手动分页
|
||||||
int total = filteredList.size();
|
int total = filteredList.size();
|
||||||
int fromIndex = (current - 1) * size;
|
int fromIndex = (current - 1) * size;
|
||||||
int toIndex = Math.min(fromIndex + size, total);
|
int toIndex = Math.min(fromIndex + size, total);
|
||||||
@@ -236,13 +429,31 @@ public class MartialMiniController extends BladeController {
|
|||||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 构建分页结果
|
// 7. 构建分页结果
|
||||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||||
page.setRecords(pageRecords);
|
page.setRecords(pageRecords);
|
||||||
|
|
||||||
return R.data(page);
|
return R.data(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场地所有主裁判的judge_id列表
|
||||||
|
*/
|
||||||
|
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||||
|
if (venueId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
||||||
|
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
||||||
|
return chiefJudges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情
|
* 获取评分详情
|
||||||
*/
|
*/
|
||||||
@@ -254,10 +465,10 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/score/modify")
|
@PutMapping("/score/modify")
|
||||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||||
@@ -268,26 +479,107 @@ public class MartialMiniController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||||
public R logout() {
|
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
// 从Redis删除登录缓存
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.del(cacheKey);
|
||||||
|
}
|
||||||
return R.success("退出成功");
|
return R.success("退出成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token验证
|
* Token验证(从Redis恢复登录状态)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/verify")
|
@GetMapping("/verify")
|
||||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||||
public R verify() {
|
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
return R.success("Token有效");
|
if (token == null || token.isEmpty()) {
|
||||||
|
return R.fail("Token不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取登录信息
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
||||||
|
|
||||||
|
if (loginInfo != null) {
|
||||||
|
// 刷新缓存过期时间
|
||||||
|
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
||||||
|
return R.data(loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis中没有,尝试从数据库恢复
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||||
|
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("Token无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return R.fail("Token已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重建登录信息
|
||||||
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
|
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||||
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
|
vo.setToken(token);
|
||||||
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
|
vo.setMatchId(competition != null ? competition.getId() : null);
|
||||||
|
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
||||||
|
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
||||||
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
|
vo.setJudgeId(judge != null ? judge.getId() : null);
|
||||||
|
vo.setJudgeName(judge != null ? judge.getName() : null);
|
||||||
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 重新缓存到Redis
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
|
return R.data(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换选手实体为VO
|
* 转换选手实体为VO
|
||||||
|
* 新增:只有评分完成时才显示总分
|
||||||
*/
|
*/
|
||||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||||
MartialAthlete athlete,
|
MartialAthlete athlete,
|
||||||
List<MartialScore> scores,
|
List<MartialScore> scores,
|
||||||
Long currentJudgeId) {
|
Long currentJudgeId,
|
||||||
|
int requiredJudgeCount) {
|
||||||
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||||
vo.setAthleteId(athlete.getId());
|
vo.setAthleteId(athlete.getId());
|
||||||
vo.setName(athlete.getPlayerName());
|
vo.setName(athlete.getPlayerName());
|
||||||
@@ -296,7 +588,9 @@ public class MartialMiniController extends BladeController {
|
|||||||
vo.setTeam(athlete.getTeamName());
|
vo.setTeam(athlete.getTeamName());
|
||||||
vo.setOrderNum(athlete.getOrderNum());
|
vo.setOrderNum(athlete.getOrderNum());
|
||||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||||
vo.setTotalScore(athlete.getTotalScore());
|
|
||||||
|
// 设置应评分裁判数量
|
||||||
|
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||||
|
|
||||||
// 设置项目名称
|
// 设置项目名称
|
||||||
if (athlete.getProjectId() != null) {
|
if (athlete.getProjectId() != null) {
|
||||||
@@ -307,8 +601,10 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置评分状态
|
// 设置评分状态
|
||||||
|
int scoredCount = 0;
|
||||||
if (scores != null && !scores.isEmpty()) {
|
if (scores != null && !scores.isEmpty()) {
|
||||||
vo.setScoredJudgeCount(scores.size());
|
scoredCount = scores.size();
|
||||||
|
vo.setScoredJudgeCount(scoredCount);
|
||||||
|
|
||||||
// 查找当前裁判的评分
|
// 查找当前裁判的评分
|
||||||
MartialScore myScore = scores.stream()
|
MartialScore myScore = scores.stream()
|
||||||
@@ -326,6 +622,23 @@ public class MartialMiniController extends BladeController {
|
|||||||
vo.setScored(false);
|
vo.setScored(false);
|
||||||
vo.setScoredJudgeCount(0);
|
vo.setScoredJudgeCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断评分是否完成(所有裁判都已评分)
|
||||||
|
boolean scoringComplete = false;
|
||||||
|
if (requiredJudgeCount > 0) {
|
||||||
|
scoringComplete = scoredCount >= requiredJudgeCount;
|
||||||
|
} else {
|
||||||
|
// 如果没有配置裁判数量,只要有评分就算完成
|
||||||
|
scoringComplete = scoredCount > 0;
|
||||||
|
}
|
||||||
|
vo.setScoringComplete(scoringComplete);
|
||||||
|
|
||||||
|
// 只有评分完成时才显示总分
|
||||||
|
if (scoringComplete) {
|
||||||
|
vo.setTotalScore(athlete.getTotalScore());
|
||||||
|
} else {
|
||||||
|
vo.setTotalScore(null);
|
||||||
|
}
|
||||||
|
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -378,4 +691,130 @@ public class MartialMiniController extends BladeController {
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比赛的所有项目
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场地获取项目列表
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========== 三级裁判评分流程 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主裁判确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/chief/confirm")
|
||||||
|
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
||||||
|
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
||||||
|
if (resultId == null || chiefJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总裁确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/general/confirm")
|
||||||
|
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
||||||
|
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
||||||
|
if (resultId == null || generalJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待主裁判确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/chief/pending")
|
||||||
|
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/pending")
|
||||||
|
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||||
|
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有场地列表(总裁用)
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/venues")
|
||||||
|
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||||
|
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||||
|
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||||
|
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||||
|
List<MartialVenue> venues = venueService.list(wrapper);
|
||||||
|
return R.data(venues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/confirmed")
|
||||||
|
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule export Excel - Template 2 (Competition Schedule Format)
|
||||||
|
* Format: Sequence, Project, Participants, Groups, Time, Table Number
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ColumnWidth(12)
|
||||||
|
@HeadRowHeight(25)
|
||||||
|
@ContentRowHeight(20)
|
||||||
|
public class ScheduleExportExcel2 implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ExcelProperty("序号")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer sequenceNo;
|
||||||
|
|
||||||
|
@ExcelProperty("项目")
|
||||||
|
@ColumnWidth(25)
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
@ExcelProperty("人数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer participantCount;
|
||||||
|
|
||||||
|
@ExcelProperty("组数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer groupCount;
|
||||||
|
|
||||||
|
@ExcelProperty("时间")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private Integer durationMinutes;
|
||||||
|
|
||||||
|
@ExcelProperty("表号")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private String tableNo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.springblade.modules.martial.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出场顺序分组VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LineupGroupVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long groupId;
|
||||||
|
private String groupName;
|
||||||
|
private String projectName;
|
||||||
|
private String category;
|
||||||
|
private String venueName;
|
||||||
|
private String timeSlot;
|
||||||
|
private String tableNo;
|
||||||
|
private List<LineupParticipantVO> participants;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.springblade.modules.martial.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出场顺序参赛者VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LineupParticipantVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Integer order;
|
||||||
|
private String playerName;
|
||||||
|
private String organization;
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.springblade.modules.martial.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
||||||
@@ -10,74 +11,27 @@ import org.springblade.modules.martial.pojo.entity.MartialSchedule;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule 服务类
|
* Schedule Service
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
*/
|
||||||
public interface IMartialScheduleService extends IService<MartialSchedule> {
|
public interface IMartialScheduleService extends IService<MartialSchedule> {
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.3: 导出赛程表
|
|
||||||
*/
|
|
||||||
List<ScheduleExportExcel> exportSchedule(Long competitionId);
|
List<ScheduleExportExcel> exportSchedule(Long competitionId);
|
||||||
|
|
||||||
/**
|
List<ScheduleExportExcel2> exportScheduleTemplate2(Long competitionId, Long venueId);
|
||||||
* 获取赛程编排结果
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @return 赛程编排结果
|
|
||||||
*/
|
|
||||||
ScheduleResultDTO getScheduleResult(Long competitionId);
|
ScheduleResultDTO getScheduleResult(Long competitionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存编排草稿
|
|
||||||
* @param dto 编排草稿数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveDraftSchedule(SaveScheduleDraftDTO dto);
|
boolean saveDraftSchedule(SaveScheduleDraftDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 完成编排并锁定
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveAndLockSchedule(Long competitionId);
|
boolean saveAndLockSchedule(Long competitionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动赛程分组到指定场地和时间段
|
|
||||||
* @param dto 移动请求数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取调度数据
|
|
||||||
* @param competitionId 赛事ID
|
|
||||||
* @param venueId 场地ID
|
|
||||||
* @param timeSlotIndex 时间段索引
|
|
||||||
* @return 调度数据
|
|
||||||
*/
|
|
||||||
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
||||||
|
|
||||||
/**
|
|
||||||
* 调整出场顺序
|
|
||||||
* @param dto 调整请求数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean adjustOrder(org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto);
|
boolean adjustOrder(org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量保存调度
|
|
||||||
* @param dto 保存调度数据
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean saveDispatch(org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto);
|
boolean saveDispatch(org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新参赛者签到状态
|
|
||||||
* @param participantId 参赛者ID
|
|
||||||
* @param status 状态:未签到/已签到/异常
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean updateParticipantCheckInStatus(Long participantId, String status);
|
boolean updateParticipantCheckInStatus(Long participantId, String status);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.springblade.modules.martial.service.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleDetailMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleDetailMapper;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||||
import org.springblade.modules.martial.mapper.MartialScheduleParticipantMapper;
|
import org.springblade.modules.martial.mapper.MartialScheduleParticipantMapper;
|
||||||
@@ -136,6 +137,40 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
|
|
||||||
return exportList;
|
return exportList;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public List<ScheduleExportExcel2> exportScheduleTemplate2(Long competitionId, Long venueId) {
|
||||||
|
List<ScheduleExportExcel2> exportList = new ArrayList<>();
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
if (details.isEmpty()) {
|
||||||
|
return exportList;
|
||||||
|
}
|
||||||
|
if (venueId != null) {
|
||||||
|
details = details.stream().filter(d -> venueId.equals(d.getVenueId())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
|
||||||
|
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
|
||||||
|
List<Long> sortedGroupIds = details.stream()
|
||||||
|
.collect(Collectors.toMap(ScheduleGroupDetailVO::getGroupId, d -> d.getDisplayOrder() != null ? d.getDisplayOrder() : 999, (a, b) -> a))
|
||||||
|
.entrySet().stream().sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).collect(Collectors.toList());
|
||||||
|
int sequenceNo = 1;
|
||||||
|
int tableNoBase = 1101;
|
||||||
|
for (Long groupId : sortedGroupIds) {
|
||||||
|
List<ScheduleGroupDetailVO> groupDetails = groupMap.get(groupId);
|
||||||
|
if (groupDetails == null || groupDetails.isEmpty()) continue;
|
||||||
|
ScheduleGroupDetailVO firstDetail = groupDetails.get(0);
|
||||||
|
long participantCount = groupDetails.stream().filter(d -> d.getParticipantId() != null).count();
|
||||||
|
int durationMinutes = (int) (participantCount * 4);
|
||||||
|
ScheduleExportExcel2 excel = new ScheduleExportExcel2();
|
||||||
|
excel.setSequenceNo(sequenceNo++);
|
||||||
|
excel.setProjectName(firstDetail.getGroupName());
|
||||||
|
excel.setParticipantCount((int) participantCount);
|
||||||
|
excel.setGroupCount(1);
|
||||||
|
excel.setDurationMinutes(durationMinutes);
|
||||||
|
excel.setTableNo(String.valueOf(tableNoBase++));
|
||||||
|
exportList.add(excel);
|
||||||
|
}
|
||||||
|
return exportList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取赛程编排结果(优化版本:使用单次JOIN查询)
|
* 获取赛程编排结果(优化版本:使用单次JOIN查询)
|
||||||
|
|||||||
Reference in New Issue
Block a user