From e609542c212264b5e3027f4fdf4944bc78419099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Sun, 28 Jun 2026 20:32:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=8A=A5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Event/EventRegistrationService.cs | 413 ++++++++++++++++-- 1 file changed, 368 insertions(+), 45 deletions(-) diff --git a/YD_Event.Application/Event/EventRegistrationService.cs b/YD_Event.Application/Event/EventRegistrationService.cs index 9306a2c..6c1d9b0 100644 --- a/YD_Event.Application/Event/EventRegistrationService.cs +++ b/YD_Event.Application/Event/EventRegistrationService.cs @@ -2,8 +2,11 @@ using YD_Event.Application.Event.Dtos; using YD_Event.Application.Auth; using YD_Event.Core.Entities; using YD_Event.Core.Enum; -using MiniExcelLibs; using YD_Event.Core.Domain; +using Microsoft.AspNetCore.Mvc; +using OfficeOpenXml; +using OfficeOpenXml.Style; +using System.Drawing; namespace YD_Event.Application.Event; @@ -497,40 +500,40 @@ public class EventRegistrationService : BaseService, ITransie } /// - /// 导出报名 + /// 导出报名信息 /// [DisplayName("导出报名")] - public async Task Export(RegistrationQueryInput input) + public async Task Export(RegistrationQueryInput input) { var registrations = await _repository.AsQueryable() - // 排除软删除的报名记录 .Where(x => !x.DeleteMark) - // 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1) .Where(x => (x.PaymentMethod == 2 && x.PaymentStatus == 1) || (x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1))) .WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId) .WhereIF(input.RegistrationMode.HasValue, x => x.RegistrationMode == input.RegistrationMode) .WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.TeamName.Contains(input.UnitName)) .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.ContactName.Contains(input.UserName)) .WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone)) + .WhereIF(input.PaymentMethod.HasValue, x => x.PaymentMethod == input.PaymentMethod) .WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus) + .WhereIF(input.RegistrationTimeStart.HasValue, x => x.RegistrationTime >= input.RegistrationTimeStart) + .WhereIF(input.RegistrationTimeEnd.HasValue, x => x.RegistrationTime <= input.RegistrationTimeEnd) + .OrderBy(x => x.RegistrationMode, OrderByType.Asc) + .OrderBy(x => x.RegistrationTime, OrderByType.Desc) .ToListAsync(); - // 获取所有报名记录的 CreatedUserId - var createdUserIds = registrations.Select(x => x.CreatedUserId).Distinct().ToList(); + if (!registrations.Any()) + { + throw Oops.Bah("暂无数据可导出"); + } - // 获取这些创建用户的信息 - var createdUsers = await _appUserRepository.AsQueryable() - .Where(u => createdUserIds.Contains(u.Id) && !u.DeleteMark) - .ToListAsync(); + var regIds = registrations.Select(r => r.Id).ToList(); - // 获取这些用户的 MerchantCertificationId - var merchantCertIds = createdUsers - .Where(u => u.MerchantCertificationId.HasValue) - .Select(u => u.MerchantCertificationId.Value) + var merchantCertIds = registrations + .Where(r => r.MerchantCertificationId.HasValue) + .Select(r => r.MerchantCertificationId.Value) .Distinct() .ToList(); - // 获取单位认证信息 Dictionary merchantNameDict = new Dictionary(); if (merchantCertIds.Any()) { @@ -540,45 +543,365 @@ public class EventRegistrationService : BaseService, ITransie merchantNameDict = merchantCerts.ToDictionary(mc => mc.Id, mc => mc.MerchantName); } - // 创建 CreatedUserId 到单位名称的映射 - Dictionary userIdToUnitNameDict = new Dictionary(); - foreach (var user in createdUsers) + var allPersons = await _personRepository.AsQueryable() + .Includes(p => p.User) + .Where(p => regIds.Contains(p.RegistrationId) && !p.DeleteMark) + .ToListAsync(); + + var athleteUserIds = allPersons + .Where(p => p.PersonType == PersonType.Athlete && p.UserId > 0) + .Select(p => p.UserId) + .Distinct() + .ToList(); + + var allTeamMembers = new List(); + Dictionary> teamLeadersDict = new(); + Dictionary> teamCoachesDict = new(); + + if (athleteUserIds.Any()) { - if (user.MerchantCertificationId.HasValue && - merchantNameDict.TryGetValue(user.MerchantCertificationId.Value, out var unitName)) + allTeamMembers = await _teamMemberRepository.AsQueryable() + .Where(tm => athleteUserIds.Contains(tm.UserId) && !tm.DeleteMark) + .ToListAsync(); + + var allTeamIds = allTeamMembers.Select(tm => tm.TeamId).Distinct().ToList(); + + if (allTeamIds.Any()) { - userIdToUnitNameDict[user.Id] = unitName; + var fullTeamMembers = await _teamMemberRepository.AsQueryable() + .Where(tm => allTeamIds.Contains(tm.TeamId) && !tm.DeleteMark) + .ToListAsync(); + + var coachLeaderUserIds = fullTeamMembers + .Where(tm => tm.PersonType == PersonType.Leader || tm.PersonType == PersonType.Coach) + .Select(tm => tm.UserId) + .Distinct() + .ToList(); + + if (coachLeaderUserIds.Any()) + { + var coachLeaderUsers = await _appUserRepository.AsQueryable() + .Where(u => coachLeaderUserIds.Contains(u.Id) && !u.DeleteMark) + .ToListAsync(); + var userDict = coachLeaderUsers.ToDictionary(u => u.Id); + + foreach (var teamId in allTeamIds) + { + var teamLeaders = fullTeamMembers + .Where(tm => tm.TeamId == teamId && tm.PersonType == PersonType.Leader) + .Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null) + .Where(u => u != null) + .Cast() + .ToList(); + var teamCoaches = fullTeamMembers + .Where(tm => tm.TeamId == teamId && tm.PersonType == PersonType.Coach) + .Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null) + .Where(u => u != null) + .Cast() + .ToList(); + if (teamLeaders.Any()) teamLeadersDict[teamId] = teamLeaders; + if (teamCoaches.Any()) teamCoachesDict[teamId] = teamCoaches; + } + } } } - // 构建导出列表 - var list = registrations.Select(x => + var userToTeamIdsDict = allTeamMembers + .GroupBy(tm => tm.UserId) + .ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).Distinct().ToList()); + + ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; + using var package = new ExcelPackage(); + + BuildTeamSheet(package, registrations, allPersons, merchantNameDict, teamLeadersDict, teamCoachesDict, userToTeamIdsDict); + BuildPersonSheet(package, registrations, allPersons, merchantNameDict); + + var bytes = await package.GetAsByteArrayAsync(); + return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { - // 根据报名记录的 CreatedUserId 获取单位名称 - string? unitName = null; - if (userIdToUnitNameDict.TryGetValue(x.CreatedUserId, out var name)) + FileDownloadName = $"报名信息_{DateTime.Now:yyyyMMddHHmmss}.xlsx" + }; + } + + private void BuildTeamSheet( + ExcelPackage package, + List registrations, + List allPersons, + Dictionary merchantNameDict, + Dictionary> teamLeadersDict, + Dictionary> teamCoachesDict, + Dictionary> userToTeamIdsDict) + { + var ws = package.Workbook.Worksheets.Add("队伍信息"); + + string[] headers = { "类型", "参赛单位", "队伍名称/运动员名称", "领队", "联系方式", "教练", "联系方式", "人数", "缴费情况" }; + for (int i = 0; i < headers.Length; i++) + { + ws.Cells[1, i + 1].Value = headers[i]; + } + + var headerRange = ws.Cells[1, 1, 1, headers.Length]; + headerRange.Style.Font.Bold = true; + headerRange.Style.Font.Size = 14; + headerRange.Style.Font.Name = "宋体"; + headerRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + headerRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center; + headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid; + headerRange.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(217, 226, 243)); + headerRange.Style.Border.Top.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Left.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Right.Style = ExcelBorderStyle.Thin; + ws.Row(1).Height = 30; + + double[] colWidths = { 13.4, 44.6, 38.5, 23.1, 30.4, 13, 24.9, 15.3, 22.7 }; + for (int i = 0; i < colWidths.Length; i++) + { + ws.Column(i + 1).Width = colWidths[i]; + } + + int row = 2; + var personRegGroups = allPersons.GroupBy(p => p.RegistrationId).ToDictionary(g => g.Key, g => g.ToList()); + + foreach (var reg in registrations) + { + var regPersons = personRegGroups.TryGetValue(reg.Id, out var rp) ? rp : new List(); + var athletes = regPersons.Where(p => p.PersonType == PersonType.Athlete).ToList(); + bool isPersonal = reg.RegistrationMode == 1; + string type = isPersonal ? "个人" : "单位"; + + string unitName = "/"; + string teamName = reg.TeamName ?? ""; + string leaderName = reg.ContactName ?? ""; + string leaderPhone = reg.ContactPhone ?? ""; + string coachNames = "/"; + string coachPhones = "/"; + int personCount = 1; + string paymentStatus = reg.PaymentStatus == 1 ? "已支付" : "未支付"; + + if (isPersonal) { - unitName = name; + var athlete = athletes.FirstOrDefault(); + if (athlete != null) + { + teamName = athlete.Name ?? reg.TeamName ?? ""; + leaderName = athlete.Name ?? reg.ContactName ?? ""; + leaderPhone = athlete.User?.Mobile ?? athlete.Phone ?? reg.ContactPhone ?? ""; + } + personCount = 1; + } + else + { + if (reg.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(reg.MerchantCertificationId.Value, out var un)) + { + unitName = un; + } + + var regAthleteUserIds = athletes.Where(p => p.UserId > 0).Select(p => p.UserId).Distinct().ToList(); + + var regTeamIds = new HashSet(); + foreach (var uid in regAthleteUserIds) + { + if (userToTeamIdsDict.TryGetValue(uid, out var tids)) + { + foreach (var tid in tids) regTeamIds.Add(tid); + } + } + + var allLeaders = new List(); + var allCoaches = new List(); + + foreach (var tid in regTeamIds) + { + if (teamLeadersDict.TryGetValue(tid, out var leaders)) allLeaders.AddRange(leaders); + if (teamCoachesDict.TryGetValue(tid, out var coaches)) allCoaches.AddRange(coaches); + } + + allLeaders = allLeaders.GroupBy(l => l.Id).Select(g => g.First()).ToList(); + allCoaches = allCoaches.GroupBy(c => c.Id).Select(g => g.First()).ToList(); + + var regLeaders = regPersons.Where(p => p.PersonType == PersonType.Leader).ToList(); + if (!allLeaders.Any() && regLeaders.Any()) + { + allLeaders = regLeaders.Select(l => new YD_Event.Core.Entities.AppUser { Name = l.Name, Mobile = l.Phone }).ToList(); + } + + var regCoaches = regPersons.Where(p => p.PersonType == PersonType.Coach).ToList(); + if (!allCoaches.Any() && regCoaches.Any()) + { + allCoaches = regCoaches.Select(c => new YD_Event.Core.Entities.AppUser { Name = c.Name, Mobile = c.Phone }).ToList(); + } + + if (allLeaders.Any()) + { + leaderName = string.Join("\n", allLeaders.Select(l => l.Name ?? "").Where(n => !string.IsNullOrEmpty(n))); + leaderPhone = string.Join("\n", allLeaders.Select(l => l.Mobile ?? "").Where(m => !string.IsNullOrEmpty(m))); + } + + if (allCoaches.Any()) + { + coachNames = string.Join("\n", allCoaches.Select(c => c.Name ?? "").Where(n => !string.IsNullOrEmpty(n))); + coachPhones = string.Join("\n", allCoaches.Select(c => c.Mobile ?? "").Where(m => !string.IsNullOrEmpty(m))); + } + + var uniqueIds = athletes.Where(p => p.UserId > 0).Select(p => p.UserId).Distinct().Count(); + personCount = uniqueIds > 0 ? uniqueIds : athletes.Count; + if (personCount == 0) personCount = reg.TotalPersonCount; } - return new + ws.Cells[row, 1].Value = type; + ws.Cells[row, 2].Value = unitName; + ws.Cells[row, 3].Value = teamName; + ws.Cells[row, 4].Value = leaderName; + ws.Cells[row, 5].Value = leaderPhone; + ws.Cells[row, 6].Value = coachNames; + ws.Cells[row, 7].Value = coachPhones; + ws.Cells[row, 8].Value = personCount; + ws.Cells[row, 9].Value = paymentStatus; + + var dataRange = ws.Cells[row, 1, row, headers.Length]; + dataRange.Style.Font.Size = 14; + dataRange.Style.Font.Name = "宋体"; + dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center; + dataRange.Style.WrapText = true; + dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin; + + ws.Row(row).Height = 30; + row++; + } + } + + private void BuildPersonSheet( + ExcelPackage package, + List registrations, + List allPersons, + Dictionary merchantNameDict) + { + var ws = package.Workbook.Worksheets.Add("人员信息"); + + string[] headers = { "类型", "参赛单位", "参赛队伍", "参赛队员", "身份证号", "出生年月日", "性别", "项目", "组别" }; + for (int i = 0; i < headers.Length; i++) + { + ws.Cells[1, i + 1].Value = headers[i]; + } + + var headerRange = ws.Cells[1, 1, 1, headers.Length]; + headerRange.Style.Font.Bold = true; + headerRange.Style.Font.Size = 14; + headerRange.Style.Font.Name = "宋体"; + headerRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + headerRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center; + headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid; + headerRange.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(217, 226, 243)); + headerRange.Style.Border.Top.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Left.Style = ExcelBorderStyle.Thin; + headerRange.Style.Border.Right.Style = ExcelBorderStyle.Thin; + ws.Row(1).Height = 30; + + double[] colWidths = { 13.4, 30.4, 29.7, 26.4, 32.1, 23.1, 13, 56.8, 23.1 }; + for (int i = 0; i < colWidths.Length; i++) + { + ws.Column(i + 1).Width = colWidths[i]; + } + + ws.Column(5).Style.Numberformat.Format = "@"; + + int row = 2; + var personGroups = allPersons + .Where(p => p.PersonType == PersonType.Athlete) + .GroupBy(p => p.RegistrationId) + .ToDictionary(g => g.Key, g => g.ToList()); + + foreach (var reg in registrations) + { + bool isPersonal = reg.RegistrationMode == 1; + string type = isPersonal ? "个人" : "单位"; + + string unitName = "/"; + if (!isPersonal && reg.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(reg.MerchantCertificationId.Value, out var un)) { - x.RegistrationMode, - UnitName = unitName, - TeamName = x.TeamName, - x.ContactName, - x.ContactPhone, - x.TotalPersonCount, - x.PaymentMethod, - x.TotalAmount, - x.PaymentStatus, - x.RegistrationTime - }; - }).ToList(); - - using var stream = new MemoryStream(); - stream.SaveAs(list); - return stream.ToArray(); + unitName = un; + } + string teamName = reg.TeamName ?? ""; + + if (!personGroups.TryGetValue(reg.Id, out var athletes)) continue; + + var personProjectGroups = new Dictionary>(); + foreach (var athlete in athletes) + { + string key = athlete.UserId > 0 + ? $"u_{athlete.UserId}" + : $"c_{(athlete.IdCard ?? athlete.Name ?? "")}_{athlete.Name ?? ""}"; + if (!personProjectGroups.ContainsKey(key)) + personProjectGroups[key] = new List(); + personProjectGroups[key].Add(athlete); + } + + foreach (var kvp in personProjectGroups) + { + var projects = kvp.Value; + var firstPerson = projects.OrderBy(p => p.Id).First(); + + string certNumber = firstPerson.IdCard ?? ""; + string personName = firstPerson.Name ?? ""; + bool isIdCard = !string.IsNullOrEmpty(certNumber) && certNumber.Length == 18; + + for (int projIdx = 0; projIdx < projects.Count; projIdx++) + { + var projPerson = projects[projIdx]; + bool isFirstRow = projIdx == 0; + + if (isFirstRow) + { + ws.Cells[row, 1].Value = type; + ws.Cells[row, 2].Value = unitName; + ws.Cells[row, 3].Value = teamName; + ws.Cells[row, 4].Value = personName; + ws.Cells[row, 5].Value = certNumber; + + if (isIdCard) + { + ws.Cells[row, 6].Formula = $"=DATE(MID(E{row},7,4),MID(E{row},11,2),MID(E{row},13,2))"; + ws.Cells[row, 6].Style.Numberformat.Format = "yyyy-MM-dd"; + ws.Cells[row, 7].Formula = $"=IF(MOD(MID($E{row},17,1),2),\"男\",\"女\")"; + } + else + { + string genderText = firstPerson.Gender switch + { + Gender.Male => "男", + Gender.Female => "女", + _ => "" + }; + ws.Cells[row, 6].Value = firstPerson.BirthDate.ToString("yyyy-MM-dd"); + ws.Cells[row, 7].Value = genderText; + } + } + + ws.Cells[row, 8].Value = projPerson.ProjectName ?? ""; + ws.Cells[row, 9].Value = projPerson.GroupName ?? ""; + + var dataRange = ws.Cells[row, 1, row, headers.Length]; + dataRange.Style.Font.Size = 14; + dataRange.Style.Font.Name = "宋体"; + dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center; + dataRange.Style.WrapText = true; + dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin; + dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin; + + ws.Row(row).Height = 35; + row++; + } + } + } } ///