using YD_Event.Application.Event.Dtos; using YD_Event.Core.Domain.Export; using YD_Event.Core.Entities; 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 _projectTypeRepository; private readonly ISqlSugarRepository _eventInfoRepository; public EventArrangementService( ISqlSugarRepository eventProjectRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository groupRepository, ISqlSugarRepository personRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository projectTypeRepository, ISqlSugarRepository eventInfoRepository) : base(projectRepository) { _eventProjectRepository = eventProjectRepository; _projectRepository = projectRepository; _groupRepository = groupRepository; _personRepository = personRepository; _registrationRepository = registrationRepository; _projectTypeRepository = projectTypeRepository; _eventInfoRepository = eventInfoRepository; } /// /// 获取编排项目列表 /// [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)); projects = projects.OrderBy(x => x.SortIndex).ToList(); var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); // 获取项目类型信息 var typeIds = projects.Select(x => x.ProjectTypeId).Distinct().ToList(); var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id)); // 统计各组别报名人数(仅统计已支付或审核通过的) var counts = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => r.EventId == eventId && (r.PaymentStatus == 1 || r.RegistrationStatus == 2)) .GroupBy(p => new { p.ProjectId, p.GroupName }) .Select(p => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) }) .ToListAsync(); var result = new List(); foreach (var eventProj in eventProjects) { var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId); if (proj == null) continue; var type = types.FirstOrDefault(x => x.Id == proj.ProjectTypeId); var dto = new ArrangementProjectListOutput { ProjectId = eventProj.Id, ProjectName = proj.Name, ProjectType = type?.Name ?? "未知类型", 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 rounds = (int)Math.Ceiling((double)totalParticipants / venueCount); dto.TotalDurationMinutes = (rounds * proj.DurationSeconds) / 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)); projects = projects.OrderBy(x => x.SortIndex).ToList(); var result = new List(); // 2. 生成排期 foreach (var eventProj in eventProjects) { var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId); if (proj == null) continue; int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1; // 获取参赛人员(排序:组别 -> ID) // 放宽查询条件,只要不是取消状态的报名都包含进来 var persons = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) .Where((p, r) => p.ProjectId == eventProj.Id && r.RegistrationStatus != 4) .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 currentRound = 1; int currentVenue = 1; foreach (var person in persons) { result.Add(new ArrangementPreviewOutput { TimeRange = $"{proj.DurationSeconds}''", ProjectName = proj.Name, GroupName = person.GroupName, TeamName = person.TeamName, AthleteName = person.Name, SessionIndex = currentRound, VenueIndex = currentVenue }); 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); for (int i = 0; i < _data.Count; i++) { var item = _data[i]; int rowIndex = i + 3; Worksheet.Cells[rowIndex, 1].Value = item.TimeRange; 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); } // 设置列宽 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(); } } }