using YD_Event.Application.Event.Dtos; 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.PaymentStatus == 1 || r.RegistrationStatus == 2)) .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); // 设置 EPPlus 许可证上下文 ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; using var package = new ExcelPackage(); var worksheet = package.Workbook.Worksheets.Add("编排表"); // 第一行:赛事标题(合并单元格 A1:G1) worksheet.Cells["A1:G1"].Merge = true; worksheet.Cells["A1"].Value = eventInfo?.EventName ?? "赛事编排表"; // 设置标题样式 var titleStyle = worksheet.Cells["A1:G1"].Style; titleStyle.Font.Bold = true; titleStyle.Font.Size = 14; titleStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center; titleStyle.VerticalAlignment = ExcelVerticalAlignment.Center; titleStyle.Fill.PatternType = ExcelFillStyle.Solid; titleStyle.Fill.BackgroundColor.SetColor(Color.FromArgb(255, 217, 179)); // 浅橙色 // 第二行:列标题 string[] headers = { "时间", "参赛项目", "参赛组别", "参赛队伍", "运动员姓名", "场次", "场地" }; for (int i = 0; i < headers.Length; i++) { worksheet.Cells[2, i + 1].Value = headers[i]; } // 设置列标题样式 var headerStyle = worksheet.Cells["A2:G2"].Style; headerStyle.Font.Bold = true; headerStyle.Font.Size = 11; headerStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center; headerStyle.VerticalAlignment = ExcelVerticalAlignment.Center; headerStyle.Fill.PatternType = ExcelFillStyle.Solid; headerStyle.Fill.BackgroundColor.SetColor(Color.FromArgb(204, 255, 204)); // 浅绿色 headerStyle.Border.Top.Style = ExcelBorderStyle.Thin; headerStyle.Border.Bottom.Style = ExcelBorderStyle.Thin; headerStyle.Border.Left.Style = ExcelBorderStyle.Thin; headerStyle.Border.Right.Style = ExcelBorderStyle.Thin; // 填充数据行 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; // 设置数据行样式 var rowStyle = worksheet.Cells[$"A{rowIndex}:G{rowIndex}"].Style; rowStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center; rowStyle.VerticalAlignment = ExcelVerticalAlignment.Center; rowStyle.Border.Top.Style = ExcelBorderStyle.Thin; rowStyle.Border.Bottom.Style = ExcelBorderStyle.Thin; rowStyle.Border.Left.Style = ExcelBorderStyle.Thin; rowStyle.Border.Right.Style = ExcelBorderStyle.Thin; // 交替行颜色 if (i % 2 == 1) { rowStyle.Fill.PatternType = ExcelFillStyle.Solid; rowStyle.Fill.BackgroundColor.SetColor(Color.FromArgb(240, 255, 240)); // 更浅的绿色 } } // 设置列宽 worksheet.Column(1).Width = 12; // 时间 worksheet.Column(2).Width = 18; // 参赛项目 worksheet.Column(3).Width = 15; // 参赛组别 worksheet.Column(4).Width = 25; // 参赛队伍 worksheet.Column(5).Width = 15; // 运动员姓名 worksheet.Column(6).Width = 8; // 场次 worksheet.Column(7).Width = 8; // 场地 // 设置行高 worksheet.Row(1).Height = 30; // 标题行 worksheet.Row(2).Height = 25; // 列标题行 // 冻结首行和首列 worksheet.View.FreezePanes(3, 1); return await package.GetAsByteArrayAsync(); } }