using YD_Event.Application.Event.Dtos; using YD_Event.Core.Domain.Export; using YD_Event.Core.Entities; using YD_Event.Core.Extensions; using MiniExcelLibs; using SqlSugar; using OfficeOpenXml; using OfficeOpenXml.Style; using System.Drawing; namespace YD_Event.Application.Event; /// /// 赛事编排服务 /// public class EventArrangementService : BaseService, ITransient { private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _groupRepository; private readonly ISqlSugarRepository _personRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _eventInfoRepository; public EventArrangementService( ISqlSugarRepository eventProjectRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository groupRepository, ISqlSugarRepository personRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository eventInfoRepository) : base(projectRepository) { _eventProjectRepository = eventProjectRepository; _projectRepository = projectRepository; _groupRepository = groupRepository; _personRepository = personRepository; _registrationRepository = registrationRepository; _eventInfoRepository = eventInfoRepository; } // 格式化时长显示(符号格式:X'X") private string FormatDurationSymbol(int totalSeconds) { if (totalSeconds <= 0) { return "0'0\""; } var hours = totalSeconds / 3600; var remainingSecondsAfterHours = totalSeconds % 3600; var minutes = remainingSecondsAfterHours / 60; var seconds = remainingSecondsAfterHours % 60; if (hours == 0) { if (seconds == 0) { return $"{minutes}'"; } else { var paddedSeconds = seconds.ToString().PadLeft(2, '0'); return $"{minutes}'{paddedSeconds}\""; } } else { var result = $"{hours}h"; if (minutes > 0 || seconds > 0) { result += $"{minutes}'"; if (seconds > 0) { var paddedSeconds = seconds.ToString().PadLeft(2, '0'); result += $"{paddedSeconds}\""; } } return result; } } /// /// 获取编排项目列表 /// [DisplayName("获取编排项目列表")] public async Task> GetList(long eventId) { var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); // 按照 SortIndex 对 projects 排序 var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList(); var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); // 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员 var counts = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => r.EventId == eventId && r.PaymentStatus != 4 // 排除已取消的报名 && !p.DeleteMark // 排除已删除的人员 && p.PersonType == PersonType.Athlete // 只统计运动员 ) .GroupBy((p, r) => new { p.ProjectId, p.GroupName }) .Select((p, r) => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) }) .ToListAsync(); // 获取所有相关的人员和报名记录(用于统计团队数) var personWithTeam = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => r.EventId == eventId && r.PaymentStatus != 4 && !p.DeleteMark && p.PersonType == PersonType.Athlete && r.TeamName != null) .Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName }) .ToListAsync(); // 在内存中统计团队数 var teamCounts = personWithTeam .GroupBy(x => new { x.ProjectId, x.GroupName, x.TeamName }) .GroupBy(g => new { g.Key.ProjectId, g.Key.GroupName }) .Select(g => new { g.Key.ProjectId, g.Key.GroupName, Count = g.Count() }) .ToList(); var result = new List(); // 按照排序后的项目顺序处理 foreach (var proj in sortedProjects) { var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id); if (eventProj == null) continue; var dto = new ArrangementProjectListOutput { ProjectId = eventProj.Id, ProjectName = proj.Name, ProjectType = proj.ProjectType.Description(), DurationSeconds = proj.DurationSeconds, VenueCount = proj.VenueCount, SortIndex = proj.SortIndex, Groups = new List() }; var projGroups = groups.Where(g => g.ProjectId == proj.Id).ToList(); foreach (var group in projGroups) { var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName); dto.Groups.Add(new ArrangementGroupDto { GroupName = group.GroupName, ParticipantCount = countObj?.Count ?? 0 }); } // 根据项目类型计算总时长 int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1; int totalParticipants = dto.Groups.Sum(g => g.ParticipantCount); int totalTeams = 0; // 如果是团队项目,计算团队总数 if (proj.IsTeamProject()) { totalTeams = teamCounts .Where(tc => tc.ProjectId == eventProj.Id) .Sum(tc => tc.Count); } // 检查是否有有效参与者:个人项目检查报名人数,团队项目检查团队数 bool hasValidParticipants; if (proj.IsTeamProject()) { hasValidParticipants = totalTeams > 0; } else { hasValidParticipants = totalParticipants > 0; } // 如果没有有效参与者,跳过这个项目 if (!hasValidParticipants) { continue; } int totalDurationSeconds; if (proj.IsTeamProject()) { // 团队项目:(队伍数 ÷ 场地数) × 项目时长 int teamCount = totalTeams > 0 ? totalTeams : 1; int rounds = (int)Math.Ceiling((double)teamCount / venueCount); totalDurationSeconds = rounds * proj.DurationSeconds; } else { // 个人项目:(报名人数 ÷ 场地数) × 项目时长 int participantCount = totalParticipants > 0 ? totalParticipants : 1; int rounds = (int)Math.Ceiling((double)participantCount / venueCount); totalDurationSeconds = rounds * proj.DurationSeconds; } // 设置总时长秒数和分钟数 dto.TotalDurationSeconds = totalDurationSeconds; dto.TotalDurationMinutes = (int)Math.Ceiling((double)totalDurationSeconds / 60); result.Add(dto); } return result; } /// /// 更新编排设置 /// [UnitOfWork] [DisplayName("更新编排设置")] public async Task Update(ArrangementUpdateInput input) { var eventProjectIds = input.Settings.Select(x => x.ProjectId).ToList(); var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); foreach (var setting in input.Settings) { var ep = eventProjects.FirstOrDefault(x => x.Id == setting.ProjectId); if (ep != null) { await _projectRepository.UpdateAsync(x => new YD_Event.Core.Entities.Project { VenueCount = setting.VenueCount, SortIndex = setting.SortIndex, DurationSeconds = setting.DurationSeconds, DurationMinutes = setting.DurationSeconds / 60 }, x => x.Id == ep.ProjectId); } } } /// /// 编排预览 /// [DisplayName("编排预览")] public async Task> Preview(long eventId) { // 1. 获取项目列表和统计数据 var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); // 按照 SortIndex 对 projects 排序 var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList(); var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); // 统计各组别报名人数 var counts = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => r.EventId == eventId && r.PaymentStatus != 4 && !p.DeleteMark && p.PersonType == PersonType.Athlete) .GroupBy((p, r) => new { p.ProjectId, p.GroupName }) .Select((p, r) => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) }) .ToListAsync(); // 获取团队数 var personWithTeam = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => r.EventId == eventId && r.PaymentStatus != 4 && !p.DeleteMark && p.PersonType == PersonType.Athlete && r.TeamName != null) .Select((p, r) => new { p.ProjectId, p.GroupName, r.TeamName }) .ToListAsync(); var teamCounts = personWithTeam .GroupBy(x => new { x.ProjectId, x.GroupName, x.TeamName }) .GroupBy(g => new { g.Key.ProjectId, g.Key.GroupName }) .Select(g => new { g.Key.ProjectId, g.Key.GroupName, Count = g.Count() }) .ToList(); var result = new List(); // 2. 生成排期 - 按照排序后的项目顺序 foreach (var proj in sortedProjects) { var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id); if (eventProj == null) continue; int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1; // 获取参赛人员 var persons = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => p.ProjectId == eventProj.Id && r.PaymentStatus != 4 && !p.DeleteMark && p.PersonType == PersonType.Athlete) .OrderBy(p => p.GroupName) .OrderBy(p => p.Id) .Select((p, r) => new { p.Name, p.GroupName, r.TeamName, Duration = proj.DurationSeconds }) .ToListAsync(); if (!persons.Any()) continue; // 计算项目总时长 int totalParticipants = 0; var projGroups = groups.Where(g => g.ProjectId == proj.Id).ToList(); foreach (var group in projGroups) { var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName); totalParticipants += countObj?.Count ?? 0; } int totalTeams = 0; if (proj.IsTeamProject()) { totalTeams = teamCounts .Where(tc => tc.ProjectId == eventProj.Id) .Sum(tc => tc.Count); } int projectTotalDurationSeconds; if (proj.IsTeamProject()) { int teamCount = totalTeams > 0 ? totalTeams : 1; int rounds = (int)Math.Ceiling((double)teamCount / venueCount); projectTotalDurationSeconds = rounds * proj.DurationSeconds; } else { int participantCount = totalParticipants > 0 ? totalParticipants : 1; int rounds = (int)Math.Ceiling((double)participantCount / venueCount); projectTotalDurationSeconds = rounds * proj.DurationSeconds; } string projectTotalDuration = FormatDurationSymbol(projectTotalDurationSeconds); // 分组逻辑 int currentRound = 1; int currentVenue = 1; foreach (var person in persons) { result.Add(new ArrangementPreviewOutput { TimeRange = $"{proj.DurationSeconds}''", ProjectName = proj.Name, ProjectType = proj.ProjectType.Description(), GroupName = person.GroupName, TeamName = person.TeamName, AthleteName = person.Name, SessionIndex = currentRound, VenueIndex = currentVenue, ProjectTotalDurationSeconds = projectTotalDurationSeconds, ProjectTotalDuration = projectTotalDuration }); currentVenue++; if (currentVenue > venueCount) { currentVenue = 1; currentRound++; } } } return result; } /// /// 导出编排 /// [DisplayName("导出编排")] public async Task Export(long eventId) { var eventInfo = await _eventInfoRepository.GetFirstAsync(x => x.Id == eventId); var data = await Preview(eventId); var exporter = new ArrangementExporter(data, eventInfo); return await exporter.ExportAsync(); } /// /// 编排导出器 /// private class ArrangementExporter : ExcelExportBase { private readonly List _data; private readonly EventInfo _eventInfo; public ArrangementExporter(List data, EventInfo eventInfo) { _data = data ?? new List(); _eventInfo = eventInfo; } public override async Task ExportAsync() { CreateWorksheet("编排表"); // 第一行:赛事标题(合并单元格 A1:G1) var titleColor = Color.FromArgb(255, 217, 179); SetMergeTitle(_eventInfo?.EventName ?? "赛事编排表", 1, 1, 1, 7, titleColor, 14); // 第二行:列标题 var headers = new[] { "时间", "参赛项目", "参赛组别", "参赛队伍", "运动员姓名", "场次", "场地" }; var headerColor = Color.FromArgb(204, 255, 204); SetHeaders(headers, 2, headerColor, 11); // 填充数据行 var alternateBgColor = Color.FromArgb(240, 255, 240); // 按项目分组处理,合并时间列 string currentProjectName = null; int projectStartRow = 0; for (int i = 0; i < _data.Count; i++) { var item = _data[i]; int rowIndex = i + 3; // 如果是新项目或最后一行,合并前面的项目时间列 if (currentProjectName != item.ProjectName) { // 如果不是第一个项目,合并前一个项目的时间列 if (currentProjectName != null && projectStartRow > 0 && i - 1 >= 0) { int mergeEndRow = (i - 1) + 3; if (mergeEndRow > projectStartRow) { var range = Worksheet.Cells[projectStartRow, 1, mergeEndRow, 1]; range.Merge = true; // 设置时间列居中对齐 for (int r = projectStartRow; r <= mergeEndRow; r++) { Worksheet.Cells[r, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; Worksheet.Cells[r, 1].Style.VerticalAlignment = ExcelVerticalAlignment.Center; } } } currentProjectName = item.ProjectName; projectStartRow = rowIndex; // 设置项目总时长 Worksheet.Cells[rowIndex, 1].Value = item.ProjectTotalDuration; } else { // 同一项目的其他行不设置时间值(合并后会自动应用) } Worksheet.Cells[rowIndex, 2].Value = item.ProjectName; Worksheet.Cells[rowIndex, 3].Value = item.GroupName; Worksheet.Cells[rowIndex, 4].Value = item.TeamName ?? ""; Worksheet.Cells[rowIndex, 5].Value = item.AthleteName; Worksheet.Cells[rowIndex, 6].Value = item.SessionIndex; Worksheet.Cells[rowIndex, 7].Value = item.VenueIndex; SetDataRowStyle(rowIndex, 7, alternateBgColor, i % 2 == 1); } // 处理最后一个项目的合并 if (currentProjectName != null && projectStartRow > 0 && _data.Count > 0) { int mergeEndRow = (_data.Count - 1) + 3; if (mergeEndRow > projectStartRow) { var range = Worksheet.Cells[projectStartRow, 1, mergeEndRow, 1]; range.Merge = true; // 设置时间列居中对齐 for (int r = projectStartRow; r <= mergeEndRow; r++) { Worksheet.Cells[r, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; Worksheet.Cells[r, 1].Style.VerticalAlignment = ExcelVerticalAlignment.Center; } } } // 设置列宽 var widths = new double[] { 12, 18, 15, 25, 15, 8, 8 }; SetColumnWidths(widths); // 设置行高 SetRowHeight(1, 30); SetRowHeight(2, 25); // 冻结首行和首列 FreezePanes(3, 1); return await GetAsByteArrayAsync(); } } }