导出功能合并,项目参与人数重新获取
This commit is contained in:
parent
10fb96434c
commit
5abbae9e6c
@ -137,8 +137,8 @@ public class AppUserService : BaseService<AppUserEntity>, ITransient
|
||||
[DisplayName("统一新增/编辑成员")]
|
||||
public async Task SaveUnifiedMember(CreateOrUpdateMemberInput input)
|
||||
{
|
||||
// 仅在新增领队/教练时验证验证码
|
||||
if (!input.Id.HasValue && (input.PersonType == PersonType.Leader || input.PersonType == PersonType.Coach))
|
||||
// 仅在新增运动员时验证验证码
|
||||
if (!input.Id.HasValue && input.PersonType == PersonType.Athlete)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.VerificationCode))
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using YD_Event.Application.Event.Dtos;
|
||||
using YD_Event.Core.Domain.Export;
|
||||
using YD_Event.Core.Entities;
|
||||
using MiniExcelLibs;
|
||||
using SqlSugar;
|
||||
@ -154,9 +155,10 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
|
||||
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
|
||||
|
||||
// 获取参赛人员(排序:组别 -> ID)
|
||||
// 放宽查询条件,只要不是取消状态的报名都包含进来
|
||||
var persons = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => p.ProjectId == eventProj.Id && (r.PaymentStatus == 1 || r.RegistrationStatus == 2))
|
||||
.Where((p, r) => p.ProjectId == eventProj.Id && r.RegistrationStatus != 4)
|
||||
.OrderBy(p => p.GroupName)
|
||||
.OrderBy(p => p.Id)
|
||||
.Select((p, r) => new {
|
||||
@ -205,92 +207,67 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
|
||||
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++)
|
||||
var exporter = new ArrangementExporter(data, eventInfo);
|
||||
return await exporter.ExportAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编排导出器
|
||||
/// </summary>
|
||||
private class ArrangementExporter : ExcelExportBase
|
||||
{
|
||||
private readonly List<ArrangementPreviewOutput> _data;
|
||||
private readonly EventInfo _eventInfo;
|
||||
|
||||
public ArrangementExporter(List<ArrangementPreviewOutput> data, EventInfo eventInfo)
|
||||
{
|
||||
worksheet.Cells[2, i + 1].Value = headers[i];
|
||||
_data = data ?? new List<ArrangementPreviewOutput>();
|
||||
_eventInfo = eventInfo;
|
||||
}
|
||||
|
||||
// 设置列标题样式
|
||||
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++)
|
||||
|
||||
public override async Task<byte[]> ExportAsync()
|
||||
{
|
||||
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)
|
||||
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++)
|
||||
{
|
||||
rowStyle.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
rowStyle.Fill.BackgroundColor.SetColor(Color.FromArgb(240, 255, 240)); // 更浅的绿色
|
||||
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();
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using YD_Event.Application.Event.Dtos;
|
||||
using YD_Event.Application.Project;
|
||||
using YD_Event.Core.Entities;
|
||||
using YD_Event.Core.Enum;
|
||||
using SqlSugar;
|
||||
@ -49,6 +50,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
private readonly WeChatPayService _weChatPayService;
|
||||
private readonly IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ProjectService _projectService;
|
||||
|
||||
public UserEventRegistrationService(
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
@ -70,7 +72,8 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
IIdGenerator idGenerator,
|
||||
WeChatPayService weChatPayService,
|
||||
IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ProjectService projectService)
|
||||
{
|
||||
_eventRepository = eventRepository;
|
||||
_unitRepository = unitRepository;
|
||||
@ -92,6 +95,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
_weChatPayService = weChatPayService;
|
||||
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_projectService = projectService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -188,15 +192,24 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
||||
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable);
|
||||
|
||||
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
|
||||
projectIds,
|
||||
ProjectService.ProjectStatisticType.CompletionCount);
|
||||
|
||||
output.Projects = eventProjects.Select(ep =>
|
||||
{
|
||||
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
||||
var completionCount = 0;
|
||||
if (p != null && completionStats.TryGetValue(p.Id, out var count))
|
||||
{
|
||||
completionCount = count;
|
||||
}
|
||||
return new UserEventProjectListOutput
|
||||
{
|
||||
Id = ep.Id,
|
||||
Name = p?.Name ?? "",
|
||||
Fee = ep.Price / 100m,
|
||||
CompletionCount = p?.CompletionCount ?? 0
|
||||
CompletionCount = completionCount
|
||||
};
|
||||
}).Where(x => !string.IsNullOrEmpty(x.Name)).ToList();
|
||||
|
||||
@ -227,16 +240,25 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
||||
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable);
|
||||
|
||||
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
|
||||
projectIds,
|
||||
ProjectService.ProjectStatisticType.CompletionCount);
|
||||
|
||||
return eventProjects.Select(ep =>
|
||||
{
|
||||
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
||||
var completionCount = 0;
|
||||
if (p != null && completionStats.TryGetValue(p.Id, out var count))
|
||||
{
|
||||
completionCount = count;
|
||||
}
|
||||
return new UserEventProjectListOutput
|
||||
{
|
||||
Id = ep.Id,
|
||||
Name = p?.Name ?? "",
|
||||
Fee = ep.Price / 100m,
|
||||
TeamSize = 1, // 默认为1
|
||||
CompletionCount = p?.CompletionCount ?? 0
|
||||
TeamSize = 1,
|
||||
CompletionCount = completionCount
|
||||
};
|
||||
}).Where(x => !string.IsNullOrEmpty(x.Name)).ToList();
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ using YD_Event.Application.Project.Dtos;
|
||||
using YD_Event.Core.Entities;
|
||||
using Mapster;
|
||||
using SqlSugar;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace YD_Event.Application.Project;
|
||||
|
||||
@ -13,18 +14,27 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _repository;
|
||||
private readonly ISqlSugarRepository<ProjectGroup> _groupRepository;
|
||||
private readonly ISqlSugarRepository<ProjectType> _typeRepository;
|
||||
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
|
||||
private readonly ISqlSugarRepository<EventParticipationRecord> _participationRecordRepository;
|
||||
private readonly IIdGenerator _idGenerator;
|
||||
private readonly ILogger<ProjectService> _logger;
|
||||
|
||||
public ProjectService(
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Project> repository,
|
||||
ISqlSugarRepository<ProjectGroup> groupRepository,
|
||||
ISqlSugarRepository<ProjectType> typeRepository,
|
||||
IIdGenerator idGenerator) : base(repository)
|
||||
ISqlSugarRepository<EventProject> eventProjectRepository,
|
||||
ISqlSugarRepository<EventParticipationRecord> participationRecordRepository,
|
||||
IIdGenerator idGenerator,
|
||||
ILogger<ProjectService> logger) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
_groupRepository = groupRepository;
|
||||
_typeRepository = typeRepository;
|
||||
_eventProjectRepository = eventProjectRepository;
|
||||
_participationRecordRepository = participationRecordRepository;
|
||||
_idGenerator = idGenerator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -60,6 +70,19 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
})
|
||||
.ToPagedListAsync(input);
|
||||
|
||||
var projectIds = result.Rows.Select(x => x.Id).ToList();
|
||||
if (projectIds.Any())
|
||||
{
|
||||
var stats = await GetProjectStatisticsBatchAsync(projectIds, ProjectStatisticType.CompletionCount);
|
||||
foreach (var item in result.Rows)
|
||||
{
|
||||
if (stats.TryGetValue(item.Id, out var count))
|
||||
{
|
||||
item.CompletionCount = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -70,7 +93,6 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
[UnitOfWork]
|
||||
public async Task Add(AddProjectInput input)
|
||||
{
|
||||
// 检查项目名称是否重复
|
||||
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name);
|
||||
if (isExist) throw new InvalidOperationException("该项目名称已存在");
|
||||
|
||||
@ -104,7 +126,6 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
var entity = await _repository.GetByIdAsync(input.Id);
|
||||
if (entity == null) throw new InvalidOperationException("项目不存在");
|
||||
|
||||
// 如果修改了名称,检查是否重复
|
||||
if (entity.Name != input.Name)
|
||||
{
|
||||
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name && x.Id != input.Id);
|
||||
@ -115,7 +136,6 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
entity.UpdatedTime = DateTime.Now;
|
||||
await _repository.UpdateAsync(entity);
|
||||
|
||||
// Update groups: Delete old, Insert new
|
||||
await _groupRepository.DeleteAsync(x => x.ProjectId == entity.Id);
|
||||
|
||||
if (input.Groups != null && input.Groups.Any())
|
||||
@ -161,7 +181,137 @@ public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITran
|
||||
|
||||
var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id);
|
||||
dto.Groups = groups.Adapt<List<ProjectGroupDto>>();
|
||||
|
||||
dto.CompletionCount = await GetProjectStatisticAsync(id, ProjectStatisticType.CompletionCount);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 项目统计类型
|
||||
/// </summary>
|
||||
public enum ProjectStatisticType
|
||||
{
|
||||
/// <summary>
|
||||
/// 参赛人数
|
||||
/// </summary>
|
||||
ParticipationCount = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 完成人数
|
||||
/// </summary>
|
||||
CompletionCount = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取项目统计数据(单个项目)
|
||||
/// </summary>
|
||||
/// <param name="projectId">项目ID</param>
|
||||
/// <param name="statisticType">统计类型</param>
|
||||
/// <returns>统计数值</returns>
|
||||
[DisplayName("获取项目统计数据")]
|
||||
public async Task<int> GetProjectStatisticAsync(long projectId, ProjectStatisticType statisticType)
|
||||
{
|
||||
if (projectId <= 0)
|
||||
{
|
||||
_logger.LogWarning("Invalid projectId: {ProjectId}", projectId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var eventProjectIds = await _eventProjectRepository.AsQueryable()
|
||||
.Where(ep => ep.ProjectId == projectId)
|
||||
.Select(ep => ep.Id)
|
||||
.ToListAsync();
|
||||
|
||||
if (!eventProjectIds.Any())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var query = _participationRecordRepository.AsQueryable()
|
||||
.Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark);
|
||||
|
||||
switch (statisticType)
|
||||
{
|
||||
case ProjectStatisticType.ParticipationCount:
|
||||
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed);
|
||||
break;
|
||||
|
||||
case ProjectStatisticType.CompletionCount:
|
||||
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed);
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = await query.CountAsync();
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量获取项目统计数据(性能优化,避免N+1查询)
|
||||
/// </summary>
|
||||
/// <param name="projectIds">项目ID列表</param>
|
||||
/// <param name="statisticType">统计类型</param>
|
||||
/// <returns>字典,Key为项目ID,Value为统计数值</returns>
|
||||
[DisplayName("批量获取项目统计数据")]
|
||||
public async Task<Dictionary<long, int>> GetProjectStatisticsBatchAsync(List<long> projectIds, ProjectStatisticType statisticType)
|
||||
{
|
||||
var result = new Dictionary<long, int>();
|
||||
|
||||
if (projectIds == null || !projectIds.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var projectId in projectIds)
|
||||
{
|
||||
result[projectId] = 0;
|
||||
}
|
||||
|
||||
var eventProjects = await _eventProjectRepository.AsQueryable()
|
||||
.Where(ep => projectIds.Contains(ep.ProjectId))
|
||||
.Select(ep => new { ep.Id, ep.ProjectId })
|
||||
.ToListAsync();
|
||||
|
||||
if (!eventProjects.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var eventProjectIds = eventProjects.Select(ep => ep.Id).ToList();
|
||||
|
||||
var query = _participationRecordRepository.AsQueryable()
|
||||
.Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark);
|
||||
|
||||
switch (statisticType)
|
||||
{
|
||||
case ProjectStatisticType.ParticipationCount:
|
||||
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed);
|
||||
break;
|
||||
|
||||
case ProjectStatisticType.CompletionCount:
|
||||
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed);
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType);
|
||||
return result;
|
||||
}
|
||||
|
||||
var records = await query.ToListAsync();
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
var projectId = eventProjects.FirstOrDefault(ep => ep.Id == record.ProjectId)?.ProjectId;
|
||||
if (projectId.HasValue && result.ContainsKey(projectId.Value))
|
||||
{
|
||||
result[projectId.Value]++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4965,6 +4965,11 @@
|
||||
导出编排
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.EventArrangementService.ArrangementExporter">
|
||||
<summary>
|
||||
编排导出器
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.EventRecordService">
|
||||
<summary>
|
||||
参赛记录管理
|
||||
@ -7634,6 +7639,37 @@
|
||||
获取项目详情
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Project.ProjectService.ProjectStatisticType">
|
||||
<summary>
|
||||
项目统计类型
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Application.Project.ProjectService.ProjectStatisticType.ParticipationCount">
|
||||
<summary>
|
||||
参赛人数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Application.Project.ProjectService.ProjectStatisticType.CompletionCount">
|
||||
<summary>
|
||||
完成人数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Project.ProjectService.GetProjectStatisticAsync(System.Int64,YD_Event.Application.Project.ProjectService.ProjectStatisticType)">
|
||||
<summary>
|
||||
获取项目统计数据(单个项目)
|
||||
</summary>
|
||||
<param name="projectId">项目ID</param>
|
||||
<param name="statisticType">统计类型</param>
|
||||
<returns>统计数值</returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Project.ProjectService.GetProjectStatisticsBatchAsync(System.Collections.Generic.List{System.Int64},YD_Event.Application.Project.ProjectService.ProjectStatisticType)">
|
||||
<summary>
|
||||
批量获取项目统计数据(性能优化,避免N+1查询)
|
||||
</summary>
|
||||
<param name="projectIds">项目ID列表</param>
|
||||
<param name="statisticType">统计类型</param>
|
||||
<returns>字典,Key为项目ID,Value为统计数值</returns>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Project.ProjectTypeService">
|
||||
<summary>
|
||||
项目类型管理
|
||||
|
||||
146
YD_Event.Core/Domain/Export/ExcelExportBase.cs
Normal file
146
YD_Event.Core/Domain/Export/ExcelExportBase.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.Style;
|
||||
using System.Drawing;
|
||||
|
||||
namespace YD_Event.Core.Domain.Export;
|
||||
|
||||
/// <summary>
|
||||
/// Excel 导出基类
|
||||
/// </summary>
|
||||
public abstract class ExcelExportBase
|
||||
{
|
||||
protected ExcelPackage Package { get; private set; }
|
||||
protected ExcelWorksheet Worksheet { get; private set; }
|
||||
|
||||
protected ExcelExportBase()
|
||||
{
|
||||
ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
|
||||
Package = new ExcelPackage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建工作表
|
||||
/// </summary>
|
||||
protected void CreateWorksheet(string sheetName)
|
||||
{
|
||||
Worksheet = Package.Workbook.Worksheets.Add(sheetName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置合并单元格标题
|
||||
/// </summary>
|
||||
protected void SetMergeTitle(string title, int startRow, int startCol, int endRow, int endCol, Color? bgColor = null, int fontSize = 14)
|
||||
{
|
||||
var range = Worksheet.Cells[startRow, startCol, endRow, endCol];
|
||||
range.Merge = true;
|
||||
range.Value = title;
|
||||
|
||||
var style = range.Style;
|
||||
style.Font.Bold = true;
|
||||
style.Font.Size = fontSize;
|
||||
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
|
||||
if (bgColor.HasValue)
|
||||
{
|
||||
style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
style.Fill.BackgroundColor.SetColor(bgColor.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置列标题
|
||||
/// </summary>
|
||||
protected void SetHeaders(string[] headers, int rowIndex, Color? bgColor = null, int fontSize = 11)
|
||||
{
|
||||
for (int i = 0; i < headers.Length; i++)
|
||||
{
|
||||
Worksheet.Cells[rowIndex, i + 1].Value = headers[i];
|
||||
}
|
||||
|
||||
var headerRange = Worksheet.Cells[rowIndex, 1, rowIndex, headers.Length];
|
||||
var style = headerRange.Style;
|
||||
style.Font.Bold = true;
|
||||
style.Font.Size = fontSize;
|
||||
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
|
||||
if (bgColor.HasValue)
|
||||
{
|
||||
style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
style.Fill.BackgroundColor.SetColor(bgColor.Value);
|
||||
}
|
||||
|
||||
SetCellBorders(headerRange, ExcelBorderStyle.Thin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置单元格边框
|
||||
/// </summary>
|
||||
protected void SetCellBorders(ExcelRange range, ExcelBorderStyle borderStyle)
|
||||
{
|
||||
range.Style.Border.Top.Style = borderStyle;
|
||||
range.Style.Border.Bottom.Style = borderStyle;
|
||||
range.Style.Border.Left.Style = borderStyle;
|
||||
range.Style.Border.Right.Style = borderStyle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据行样式
|
||||
/// </summary>
|
||||
protected void SetDataRowStyle(int rowIndex, int colCount, Color? alternateBgColor = null, bool isAlternate = false)
|
||||
{
|
||||
var range = Worksheet.Cells[rowIndex, 1, rowIndex, colCount];
|
||||
var style = range.Style;
|
||||
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
|
||||
SetCellBorders(range, ExcelBorderStyle.Thin);
|
||||
|
||||
if (isAlternate && alternateBgColor.HasValue)
|
||||
{
|
||||
style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
style.Fill.BackgroundColor.SetColor(alternateBgColor.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置列宽
|
||||
/// </summary>
|
||||
protected void SetColumnWidths(double[] widths)
|
||||
{
|
||||
for (int i = 0; i < widths.Length; i++)
|
||||
{
|
||||
Worksheet.Column(i + 1).Width = widths[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置行高
|
||||
/// </summary>
|
||||
protected void SetRowHeight(int rowIndex, double height)
|
||||
{
|
||||
Worksheet.Row(rowIndex).Height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结窗格
|
||||
/// </summary>
|
||||
protected void FreezePanes(int rowOffset, int colOffset)
|
||||
{
|
||||
Worksheet.View.FreezePanes(rowOffset, colOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成字节数组
|
||||
/// </summary>
|
||||
protected async Task<byte[]> GetAsByteArrayAsync()
|
||||
{
|
||||
return await Package.GetAsByteArrayAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行导出
|
||||
/// </summary>
|
||||
public abstract Task<byte[]> ExportAsync();
|
||||
}
|
||||
@ -46,9 +46,9 @@ public class EventParticipationRecord : Entity<long>, ISoftDelete, ICreatedUserI
|
||||
public string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参赛状态:0-未参赛,1-已参赛,2-已完赛,3-退赛
|
||||
/// 参赛状态
|
||||
/// </summary>
|
||||
public int ParticipationStatus { get; set; }
|
||||
public ParticipationStatus ParticipationStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参赛时间
|
||||
|
||||
33
YD_Event.Core/Enum/ParticipationStatus.cs
Normal file
33
YD_Event.Core/Enum/ParticipationStatus.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace YD_Event.Core.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 参赛状态枚举
|
||||
/// </summary>
|
||||
public enum ParticipationStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 未参赛
|
||||
/// </summary>
|
||||
[Description("未参赛")]
|
||||
NotParticipated = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 已参赛
|
||||
/// </summary>
|
||||
[Description("已参赛")]
|
||||
Participated = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已完赛
|
||||
/// </summary>
|
||||
[Description("已完赛")]
|
||||
Completed = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 退赛
|
||||
/// </summary>
|
||||
[Description("退赛")]
|
||||
Withdrawn = 3
|
||||
}
|
||||
@ -27,6 +27,7 @@
|
||||
<PackageReference Include="QuestPDF" Version="2025.12.1" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="2.20.0" />
|
||||
<PackageReference Include="EPPlus" Version="7.1.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.1" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.210" />
|
||||
|
||||
@ -433,6 +433,61 @@
|
||||
是否绘制静区
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Domain.Export.ExcelExportBase">
|
||||
<summary>
|
||||
Excel 导出基类
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.CreateWorksheet(System.String)">
|
||||
<summary>
|
||||
创建工作表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetMergeTitle(System.String,System.Int32,System.Int32,System.Int32,System.Int32,System.Nullable{System.Drawing.Color},System.Int32)">
|
||||
<summary>
|
||||
设置合并单元格标题
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetHeaders(System.String[],System.Int32,System.Nullable{System.Drawing.Color},System.Int32)">
|
||||
<summary>
|
||||
设置列标题
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetCellBorders(OfficeOpenXml.ExcelRange,OfficeOpenXml.Style.ExcelBorderStyle)">
|
||||
<summary>
|
||||
设置单元格边框
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetDataRowStyle(System.Int32,System.Int32,System.Nullable{System.Drawing.Color},System.Boolean)">
|
||||
<summary>
|
||||
设置数据行样式
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetColumnWidths(System.Double[])">
|
||||
<summary>
|
||||
设置列宽
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.SetRowHeight(System.Int32,System.Double)">
|
||||
<summary>
|
||||
设置行高
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.FreezePanes(System.Int32,System.Int32)">
|
||||
<summary>
|
||||
冻结窗格
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.GetAsByteArrayAsync">
|
||||
<summary>
|
||||
生成字节数组
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Core.Domain.Export.ExcelExportBase.ExportAsync">
|
||||
<summary>
|
||||
执行导出
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Entities.Entity`1">
|
||||
<summary>
|
||||
表实体继承
|
||||
@ -1341,7 +1396,7 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.EventParticipationRecord.ParticipationStatus">
|
||||
<summary>
|
||||
参赛状态:0-未参赛,1-已参赛,2-已完赛,3-退赛
|
||||
参赛状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.EventParticipationRecord.ParticipationTime">
|
||||
@ -4297,6 +4352,31 @@
|
||||
已完成
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Enum.ParticipationStatus">
|
||||
<summary>
|
||||
参赛状态枚举
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.ParticipationStatus.NotParticipated">
|
||||
<summary>
|
||||
未参赛
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.ParticipationStatus.Participated">
|
||||
<summary>
|
||||
已参赛
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.ParticipationStatus.Completed">
|
||||
<summary>
|
||||
已完赛
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.ParticipationStatus.Withdrawn">
|
||||
<summary>
|
||||
退赛
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Enum.PersonType">
|
||||
<summary>
|
||||
人员角色类型
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user