2026-03-08 15:46:19 +08:00
|
|
|
|
using YD_Event.Application.Event.Dtos;
|
2026-03-31 17:38:36 +08:00
|
|
|
|
using YD_Event.Core.Domain.Export;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
using YD_Event.Core.Entities;
|
|
|
|
|
|
using MiniExcelLibs;
|
|
|
|
|
|
using SqlSugar;
|
2026-03-31 15:54:27 +08:00
|
|
|
|
using OfficeOpenXml;
|
|
|
|
|
|
using OfficeOpenXml.Style;
|
|
|
|
|
|
using System.Drawing;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
namespace YD_Event.Application.Event;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 赛事编排服务
|
|
|
|
|
|
/// </summary>
|
2026-03-08 15:46:19 +08:00
|
|
|
|
public class EventArrangementService : BaseService<YD_Event.Core.Entities.Project>, ITransient
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
|
2026-03-01 22:39:00 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<ProjectGroup> _groupRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
|
2026-03-31 15:54:27 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventInfo> _eventInfoRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
public EventArrangementService(
|
2026-03-18 16:25:49 +08:00
|
|
|
|
ISqlSugarRepository<EventProject> eventProjectRepository,
|
2026-03-01 22:39:00 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
|
|
|
|
|
ISqlSugarRepository<ProjectGroup> groupRepository,
|
2026-02-08 18:09:05 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
2026-03-01 22:39:00 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistration> registrationRepository,
|
2026-03-31 15:54:27 +08:00
|
|
|
|
ISqlSugarRepository<EventInfo> eventInfoRepository) : base(projectRepository)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
_eventProjectRepository = eventProjectRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
_projectRepository = projectRepository;
|
|
|
|
|
|
_groupRepository = groupRepository;
|
|
|
|
|
|
_personRepository = personRepository;
|
|
|
|
|
|
_registrationRepository = registrationRepository;
|
2026-03-31 15:54:27 +08:00
|
|
|
|
_eventInfoRepository = eventInfoRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取编排项目列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("获取编排项目列表")]
|
|
|
|
|
|
public async Task<List<ArrangementProjectListOutput>> GetList(long eventId)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
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));
|
2026-02-08 18:09:05 +08:00
|
|
|
|
projects = projects.OrderBy(x => x.SortIndex).ToList();
|
|
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
2026-03-01 22:39:00 +08:00
|
|
|
|
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var counts = await _personRepository.AsQueryable()
|
|
|
|
|
|
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
.Where((p, r) => r.EventId == eventId
|
2026-04-09 23:12:51 +08:00
|
|
|
|
&& r.PaymentStatus != 4 // 排除已取消的报名
|
2026-04-07 09:04:38 +08:00
|
|
|
|
&& !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) })
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<ArrangementProjectListOutput>();
|
2026-03-18 16:25:49 +08:00
|
|
|
|
foreach (var eventProj in eventProjects)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId);
|
|
|
|
|
|
if (proj == null) continue;
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var dto = new ArrangementProjectListOutput
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
ProjectId = eventProj.Id,
|
2026-03-01 22:39:00 +08:00
|
|
|
|
ProjectName = proj.Name,
|
2026-04-12 16:11:20 +08:00
|
|
|
|
ProjectType = proj.ProjectType.Description(),
|
2026-02-08 18:09:05 +08:00
|
|
|
|
DurationSeconds = proj.DurationSeconds,
|
|
|
|
|
|
VenueCount = proj.VenueCount,
|
|
|
|
|
|
SortIndex = proj.SortIndex,
|
|
|
|
|
|
Groups = new List<ArrangementGroupDto>()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var projGroups = groups.Where(g => g.ProjectId == proj.Id).ToList();
|
|
|
|
|
|
foreach (var group in projGroups)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新编排设置
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("更新编排设置")]
|
|
|
|
|
|
public async Task Update(ArrangementUpdateInput input)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var eventProjectIds = input.Settings.Select(x => x.ProjectId).ToList();
|
|
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
foreach (var setting in input.Settings)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 编排预览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("编排预览")]
|
|
|
|
|
|
public async Task<List<ArrangementPreviewOutput>> Preview(long eventId)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 获取项目(按顺序)
|
2026-03-18 16:25:49 +08:00
|
|
|
|
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));
|
2026-02-08 18:09:05 +08:00
|
|
|
|
projects = projects.OrderBy(x => x.SortIndex).ToList();
|
2026-03-01 22:39:00 +08:00
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var result = new List<ArrangementPreviewOutput>();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 生成排期
|
2026-03-18 16:25:49 +08:00
|
|
|
|
foreach (var eventProj in eventProjects)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId);
|
|
|
|
|
|
if (proj == null) continue;
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取参赛人员(排序:组别 -> ID)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 查询条件:不是取消状态的报名,并且只统计运动员
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var persons = await _personRepository.AsQueryable()
|
|
|
|
|
|
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
.Where((p, r) => p.ProjectId == eventProj.Id
|
2026-04-09 23:12:51 +08:00
|
|
|
|
&& r.PaymentStatus != 4
|
2026-04-07 09:04:38 +08:00
|
|
|
|
&& !p.DeleteMark
|
|
|
|
|
|
&& p.PersonType == PersonType.Athlete)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.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}''",
|
2026-03-01 22:39:00 +08:00
|
|
|
|
ProjectName = proj.Name,
|
2026-04-13 05:20:09 +08:00
|
|
|
|
ProjectType = proj.ProjectType.Description(),
|
2026-02-08 18:09:05 +08:00
|
|
|
|
GroupName = person.GroupName,
|
|
|
|
|
|
TeamName = person.TeamName,
|
|
|
|
|
|
AthleteName = person.Name,
|
|
|
|
|
|
SessionIndex = currentRound,
|
|
|
|
|
|
VenueIndex = currentVenue
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
currentVenue++;
|
|
|
|
|
|
if (currentVenue > venueCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentVenue = 1;
|
|
|
|
|
|
currentRound++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 导出编排
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("导出编排")]
|
|
|
|
|
|
public async Task<byte[]> Export(long eventId)
|
|
|
|
|
|
{
|
2026-03-31 15:54:27 +08:00
|
|
|
|
var eventInfo = await _eventInfoRepository.GetFirstAsync(x => x.Id == eventId);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var data = await Preview(eventId);
|
2026-03-31 15:54:27 +08:00
|
|
|
|
|
2026-03-31 17:38:36 +08:00
|
|
|
|
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)
|
2026-03-31 15:54:27 +08:00
|
|
|
|
{
|
2026-03-31 17:38:36 +08:00
|
|
|
|
_data = data ?? new List<ArrangementPreviewOutput>();
|
|
|
|
|
|
_eventInfo = eventInfo;
|
2026-03-31 15:54:27 +08:00
|
|
|
|
}
|
2026-03-31 17:38:36 +08:00
|
|
|
|
|
|
|
|
|
|
public override async Task<byte[]> ExportAsync()
|
2026-03-31 15:54:27 +08:00
|
|
|
|
{
|
2026-03-31 17:38:36 +08:00
|
|
|
|
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++)
|
2026-03-31 15:54:27 +08:00
|
|
|
|
{
|
2026-03-31 17:38:36 +08:00
|
|
|
|
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);
|
2026-03-31 15:54:27 +08:00
|
|
|
|
}
|
2026-03-31 17:38:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 设置列宽
|
|
|
|
|
|
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();
|
2026-03-31 15:54:27 +08:00
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|