重构项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:
@@ -10,26 +10,19 @@ 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.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.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导出打印 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/export")
|
||||
@@ -40,67 +33,47 @@ public class MartialExportController {
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialScheduleService scheduleService;
|
||||
|
||||
/**
|
||||
* Task 3.1: 导出成绩单
|
||||
*/
|
||||
@GetMapping("/results")
|
||||
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
||||
public void exportResults(
|
||||
@RequestParam Long competitionId,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportResults(@RequestParam Long competitionId, @RequestParam(required = false) Long projectId, HttpServletResponse response) {
|
||||
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
||||
String fileName = "成绩单_" + DateUtil.today();
|
||||
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
||||
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.2: 导出运动员名单
|
||||
*/
|
||||
@GetMapping("/athletes")
|
||||
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
||||
public void exportAthletes(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportAthletes(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
||||
String fileName = "运动员名单_" + DateUtil.today();
|
||||
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.3: 导出赛程表
|
||||
*/
|
||||
@GetMapping("/schedule")
|
||||
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
||||
public void exportSchedule(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportSchedule(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
||||
String fileName = "赛程表_" + DateUtil.today();
|
||||
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 生成单个证书(HTML格式)
|
||||
*/
|
||||
@GetMapping("/certificate/{resultId}")
|
||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面,可打印为PDF")
|
||||
public void generateCertificate(
|
||||
@PathVariable Long resultId,
|
||||
HttpServletResponse response
|
||||
) throws IOException {
|
||||
// 1. 获取证书数据
|
||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||
@GetMapping("/schedule2")
|
||||
@Operation(summary = "导出赛程表-模板2", description = "按场地导出比赛时间表格式的赛程安排")
|
||||
public void exportScheduleTemplate2(@RequestParam Long competitionId, @RequestParam(required = false) Long venueId,
|
||||
@RequestParam(required = false) String venueName, @RequestParam(required = false) String timeSlot, HttpServletResponse response) {
|
||||
List<ScheduleExportExcel2> list = scheduleService.exportScheduleTemplate2(competitionId, venueId);
|
||||
String fileName = "比赛时间_" + (venueName != null ? venueName : "全部场地") + "_" + DateUtil.today();
|
||||
String sheetName = (venueName != null ? venueName : "全部场地") + (timeSlot != null ? "_" + timeSlot : "");
|
||||
ExcelUtil.export(response, fileName, sheetName, list, ScheduleExportExcel2.class);
|
||||
}
|
||||
|
||||
// 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");
|
||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||
|
||||
// 3. 替换模板变量
|
||||
String html = template
|
||||
.replace("${playerName}", certificate.getPlayerName())
|
||||
.replace("${competitionName}", certificate.getCompetitionName())
|
||||
@@ -109,15 +82,10 @@ public class MartialExportController {
|
||||
.replace("${medalClass}", certificate.getMedalClass())
|
||||
.replace("${organization}", certificate.getOrganization())
|
||||
.replace("${issueDate}", certificate.getIssueDate());
|
||||
|
||||
// 4. 返回HTML
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.getWriter().write(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 批量生成证书数据
|
||||
*/
|
||||
@GetMapping("/certificates/batch")
|
||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||
@@ -125,14 +93,10 @@ public class MartialExportController {
|
||||
return R.data(certificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 获取单个证书数据(JSON格式)
|
||||
*/
|
||||
@GetMapping("/certificate/data/{resultId}")
|
||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式),供前端渲染")
|
||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式)")
|
||||
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||
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.MiniLoginVO;
|
||||
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 org.springblade.modules.martial.service.*;
|
||||
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.MartialResult;
|
||||
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 java.math.BigDecimal;
|
||||
@@ -34,6 +39,8 @@ import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -60,6 +67,8 @@ public class MartialMiniController extends BladeController {
|
||||
private final IMartialScoreService scoreService;
|
||||
private final BladeRedis bladeRedis;
|
||||
private final IMartialResultService resultService;
|
||||
private final MartialScheduleStatusMapper scheduleStatusMapper;
|
||||
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||
|
||||
// Redis缓存key前缀
|
||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||
@@ -817,4 +826,145 @@ public class MartialMiniController extends BladeController {
|
||||
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 com.alibaba.fastjson.JSON;
|
||||
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 java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -45,9 +54,17 @@ public class MartialMiniController extends BladeController {
|
||||
private final IMartialJudgeService judgeService;
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialVenueService venueService;
|
||||
private final IMtVenueService mtVenueService;
|
||||
private final IMartialProjectService projectService;
|
||||
private final IMartialAthleteService athleteService;
|
||||
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());
|
||||
judgeInviteService.updateById(invite);
|
||||
|
||||
MartialVenue venue = null;
|
||||
// 从 martial_venue 表获取场地信息
|
||||
MartialVenue martialVenue = 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();
|
||||
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.setMatchName(competition.getCompetitionName());
|
||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||
competition.getCompetitionStartTime().toString() : "");
|
||||
vo.setJudgeId(judge.getId());
|
||||
vo.setJudgeName(judge.getName());
|
||||
vo.setVenueId(venue != null ? venue.getId() : null);
|
||||
vo.setVenueName(venue != null ? venue.getVenueName() : null);
|
||||
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||
vo.setProjects(projects);
|
||||
|
||||
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||
|
||||
return R.data(vo);
|
||||
}
|
||||
|
||||
@@ -152,8 +198,136 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
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("评分提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并更新选手总分
|
||||
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||
* 特殊情况:裁判数量<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
|
||||
@@ -171,8 +345,8 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 普通裁判:获取所有选手,标记是否已评分
|
||||
* - 裁判长:获取已有评分的选手列表
|
||||
* - 裁判员:获取所有选手,标记是否已评分
|
||||
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||
*/
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
@@ -181,6 +355,7 @@ public class MartialMiniController extends BladeController {
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@RequestParam(required = false) Long competitionId,
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
@@ -188,6 +363,11 @@ public class MartialMiniController extends BladeController {
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||
|
||||
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||
if (competitionId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
@@ -196,35 +376,48 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||
|
||||
// 2. 获取所有评分记录
|
||||
// 2. 获取该场地所有主裁判的judge_id列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
|
||||
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
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);
|
||||
|
||||
// 按选手ID分组统计评分
|
||||
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||
|
||||
// 3. 根据裁判类型处理选手列表
|
||||
// 4. 获取该场地的应评裁判数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 5. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 裁判长:返回已有评分的选手
|
||||
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
filteredList = athletes.stream()
|
||||
.filter(athlete -> {
|
||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
||||
return scores != null && !scores.isEmpty();
|
||||
})
|
||||
.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());
|
||||
} else {
|
||||
// 普通裁判:返回所有选手,标记是否已评分
|
||||
// 裁判员:返回所有选手,标记是否已评分
|
||||
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());
|
||||
}
|
||||
|
||||
// 4. 手动分页
|
||||
// 6. 手动分页
|
||||
int total = filteredList.size();
|
||||
int fromIndex = (current - 1) * size;
|
||||
int toIndex = Math.min(fromIndex + size, total);
|
||||
@@ -236,13 +429,31 @@ public class MartialMiniController extends BladeController {
|
||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
// 5. 构建分页结果
|
||||
// 7. 构建分页结果
|
||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||
page.setRecords(pageRecords);
|
||||
|
||||
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")
|
||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
||||
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||
@@ -268,26 +479,107 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@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("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* Token验证
|
||||
* Token验证(从Redis恢复登录状态)
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
||||
public R verify() {
|
||||
return R.success("Token有效");
|
||||
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String 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
|
||||
* 新增:只有评分完成时才显示总分
|
||||
*/
|
||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||
MartialAthlete athlete,
|
||||
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();
|
||||
vo.setAthleteId(athlete.getId());
|
||||
vo.setName(athlete.getPlayerName());
|
||||
@@ -296,7 +588,9 @@ public class MartialMiniController extends BladeController {
|
||||
vo.setTeam(athlete.getTeamName());
|
||||
vo.setOrderNum(athlete.getOrderNum());
|
||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||
vo.setTotalScore(athlete.getTotalScore());
|
||||
|
||||
// 设置应评分裁判数量
|
||||
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||
|
||||
// 设置项目名称
|
||||
if (athlete.getProjectId() != null) {
|
||||
@@ -307,8 +601,10 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
// 设置评分状态
|
||||
int scoredCount = 0;
|
||||
if (scores != null && !scores.isEmpty()) {
|
||||
vo.setScoredJudgeCount(scores.size());
|
||||
scoredCount = scores.size();
|
||||
vo.setScoredJudgeCount(scoredCount);
|
||||
|
||||
// 查找当前裁判的评分
|
||||
MartialScore myScore = scores.stream()
|
||||
@@ -326,6 +622,23 @@ public class MartialMiniController extends BladeController {
|
||||
vo.setScored(false);
|
||||
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;
|
||||
}
|
||||
@@ -378,4 +691,130 @@ public class MartialMiniController extends BladeController {
|
||||
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 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.SaveScheduleDraftDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
||||
@@ -10,74 +11,27 @@ import org.springblade.modules.martial.pojo.entity.MartialSchedule;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Schedule 服务类
|
||||
*
|
||||
* @author BladeX
|
||||
* Schedule Service
|
||||
*/
|
||||
public interface IMartialScheduleService extends IService<MartialSchedule> {
|
||||
|
||||
/**
|
||||
* Task 3.3: 导出赛程表
|
||||
*/
|
||||
List<ScheduleExportExcel> exportSchedule(Long competitionId);
|
||||
|
||||
/**
|
||||
* 获取赛程编排结果
|
||||
* @param competitionId 赛事ID
|
||||
* @return 赛程编排结果
|
||||
*/
|
||||
List<ScheduleExportExcel2> exportScheduleTemplate2(Long competitionId, Long venueId);
|
||||
|
||||
ScheduleResultDTO getScheduleResult(Long competitionId);
|
||||
|
||||
/**
|
||||
* 保存编排草稿
|
||||
* @param dto 编排草稿数据
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean saveDraftSchedule(SaveScheduleDraftDTO dto);
|
||||
|
||||
/**
|
||||
* 完成编排并锁定
|
||||
* @param competitionId 赛事ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean saveAndLockSchedule(Long competitionId);
|
||||
|
||||
/**
|
||||
* 移动赛程分组到指定场地和时间段
|
||||
* @param dto 移动请求数据
|
||||
* @return 是否成功
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 调整出场顺序
|
||||
* @param dto 调整请求数据
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean adjustOrder(org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto);
|
||||
|
||||
/**
|
||||
* 批量保存调度
|
||||
* @param dto 保存调度数据
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean saveDispatch(org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto);
|
||||
|
||||
/**
|
||||
* 更新参赛者签到状态
|
||||
* @param participantId 参赛者ID
|
||||
* @param status 状态:未签到/已签到/异常
|
||||
* @return 是否成功
|
||||
*/
|
||||
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.extension.service.impl.ServiceImpl;
|
||||
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.MartialScheduleGroupMapper;
|
||||
import org.springblade.modules.martial.mapper.MartialScheduleParticipantMapper;
|
||||
@@ -136,6 +137,40 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
|
||||
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查询)
|
||||
|
||||
Reference in New Issue
Block a user