318 lines
13 KiB
C#
Raw Normal View History

2026-02-08 18:09:05 +08:00
using Microsoft.AspNetCore.Http;
2026-03-19 23:36:06 +08:00
using Microsoft.AspNetCore.Mvc;
2026-04-06 09:21:47 +08:00
using Microsoft.Extensions.Logging;
2026-03-08 15:46:19 +08:00
using YD_Event.Application.Event.Dtos;
2026-02-08 18:09:05 +08:00
using YD_Event.Core.Entities;
2026-03-20 02:00:34 +08:00
using YD_Event.Core.Enum;
2026-02-08 18:09:05 +08:00
using MiniExcelLibs;
using SqlSugar;
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 EventScoreService : BaseService<EventResult>, ITransient
2026-02-08 18:09:05 +08:00
{
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
private readonly ISqlSugarRepository<EventResult> _resultRepository;
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly IIdGenerator _idGenerator;
2026-04-06 09:21:47 +08:00
private readonly ILogger<EventScoreService> _logger;
2026-02-08 18:09:05 +08:00
public EventScoreService(
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventResult> resultRepository,
ISqlSugarRepository<EventCertificate> certificateRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
2026-04-06 09:21:47 +08:00
IIdGenerator idGenerator,
ILogger<EventScoreService> logger) : base(resultRepository)
2026-02-08 18:09:05 +08:00
{
_personRepository = personRepository;
_resultRepository = resultRepository;
_certificateRepository = certificateRepository;
_registrationRepository = registrationRepository;
_idGenerator = idGenerator;
2026-04-06 09:21:47 +08:00
_logger = logger;
2026-02-08 18:09:05 +08:00
}
/// <summary>
/// 运动员成绩列表
/// </summary>
[DisplayName("运动员成绩列表")]
public async Task<PageResult<ScoreListOutput>> PageAthlete(ScoreQueryInput input)
{
return await _personRepository.AsQueryable()
.LeftJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.LeftJoin<EventResult>((p, r, res) => p.Id == res.RegistrationPersonId)
.LeftJoin<EventCertificate>((p, r, res, c) => res.Id == c.ResultId)
2026-03-20 02:00:34 +08:00
.Where((p, r, res, c) => r.EventId == input.EventId && (p.PersonType == PersonType.Athlete || p.PersonType == PersonType.User))
2026-02-08 18:09:05 +08:00
.WhereIF(input.ProjectId.HasValue, p => p.ProjectId == input.ProjectId)
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), p => p.GroupName == input.GroupName)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), p => p.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), (p, r) => r.TeamName.Contains(input.UnitName))
.WhereIF(input.Gender.HasValue, p => p.Gender == input.Gender.Value)
2026-02-08 18:09:05 +08:00
.Select((p, r, res, c) => new ScoreListOutput
{
Id = p.Id,
Name = p.Name,
UnitName = r.TeamName,
Gender = p.Gender.ToString(),
Score = res.Score,
Rank = res.Rank,
ResultId = res.Id,
CertificateId = c.Id
})
.ToPagedListAsync(input);
}
/// <summary>
/// 教练/领队列表
/// </summary>
[DisplayName("教练领队列表")]
public async Task<PageResult<CoachLeaderListOutput>> PageCoachLeader(CoachLeaderQueryInput input)
{
return await _personRepository.AsQueryable()
.LeftJoin<EventCertificate>((p, c) => p.Id == c.ParticipationRecordId)
.InnerJoin<EventRegistration>((p, c, r) => p.RegistrationId == r.Id)
.Where((p, c, r) => r.EventId == input.EventId && p.PersonType == input.PersonType)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), p => p.Name.Contains(input.Name))
2026-03-25 09:09:45 +08:00
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), p => p.Phone.Contains(input.Mobile))
.WhereIF(input.Gender.HasValue, p => p.Gender == input.Gender.Value)
2026-02-08 18:09:05 +08:00
.Select((p, c) => new CoachLeaderListOutput
{
Id = p.Id,
Name = p.Name,
Age = p.Age,
Gender = p.Gender.ToString(),
2026-03-25 09:09:45 +08:00
IdCardNumber = p.IdCard,
Mobile = p.Phone,
2026-02-08 18:09:05 +08:00
CertificateId = c.Id
})
.ToPagedListAsync(input);
}
/// <summary>
/// 导入成绩
/// </summary>
[UnitOfWork]
[DisplayName("导入成绩")]
public async Task ImportScore(long eventId, IFormFile file)
{
using var stream = file.OpenReadStream();
2026-04-06 09:21:47 +08:00
var rows = stream.Query<ScoreImportDto>().ToList();
2026-02-08 18:09:05 +08:00
foreach(var row in rows)
{
2026-04-06 09:21:47 +08:00
// 先获取所有匹配的人员ID
var personIdList = await _personRepository.AsQueryable()
2026-02-08 18:09:05 +08:00
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId && p.Name == row.Name && p.ProjectName == row.ProjectName && p.GroupName == row.GroupName)
2026-04-06 09:21:47 +08:00
.Select((p, r) => p.Id)
.ToListAsync();
2026-02-08 18:09:05 +08:00
2026-04-06 09:21:47 +08:00
if (personIdList == null || personIdList.Count == 0) continue;
var person = await _personRepository.GetByIdAsync(personIdList[0]);
2026-02-08 18:09:05 +08:00
if (person == null) continue;
var result = await _resultRepository.GetFirstAsync(x => x.RegistrationPersonId == person.Id);
if (result == null)
{
result = new EventResult
{
Id = _idGenerator.NewLong(),
EventId = eventId,
RegistrationPersonId = person.Id,
RegistrationId = person.RegistrationId,
ProjectId = person.ProjectId,
ParticipantName = person.Name,
GroupName = person.GroupName,
CreatedTime = DateTime.Now
};
await _resultRepository.InsertAsync(result);
}
result.Score = row.Score;
result.Rank = row.Rank;
2026-04-06 09:21:47 +08:00
result.Remark = row.RankText;
2026-02-08 18:09:05 +08:00
result.UpdatedTime = DateTime.Now;
await _resultRepository.UpdateAsync(result);
}
}
/// <summary>
/// 发放证书
/// </summary>
[UnitOfWork]
[DisplayName("发放证书")]
public async Task IssueCertificate(IssueCertificateInput input)
{
foreach (var personId in input.PersonIds)
{
var person = await _personRepository.GetByIdAsync(personId);
if (person == null) continue;
2026-03-20 02:00:34 +08:00
if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User)
2026-02-08 18:09:05 +08:00
{
var result = await _resultRepository.GetFirstAsync(x => x.RegistrationPersonId == person.Id);
if (result == null) continue;
var cert = await _certificateRepository.GetFirstAsync(x => x.ResultId == result.Id);
if (cert == null)
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
ResultId = result.Id,
RegistrationId = person.RegistrationId,
ParticipationRecordId = person.Id,
ParticipantName = person.Name,
GroupName = person.GroupName,
CertificateStatus = 1,
GeneratedTime = DateTime.Now,
CertificateNo = $"C{DateTime.Now:yyyyMMdd}{person.Id}",
CreatedTime = DateTime.Now
};
await _certificateRepository.InsertAsync(cert);
}
}
else
{
var cert = await _certificateRepository.GetFirstAsync(x => x.ParticipationRecordId == person.Id);
if (cert == null)
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
RegistrationId = person.RegistrationId,
ParticipationRecordId = person.Id,
ParticipantName = person.Name,
GroupName = person.GroupName,
CertificateStatus = 1,
GeneratedTime = DateTime.Now,
CertificateNo = $"CC{DateTime.Now:yyyyMMdd}{person.Id}",
CreatedTime = DateTime.Now
};
await _certificateRepository.InsertAsync(cert);
}
}
}
}
2026-03-19 23:36:06 +08:00
/// <summary>
/// 查看证书接口
/// </summary>
/// <param name="id">报名人员ID (EventRegistrationPerson的Id)</param>
/// <returns>证书详情</returns>
[DisplayName("查看证书")]
[HttpGet("/api/EventScore/GetCertificateDetail/{id}")]
public async Task<IActionResult> GetCertificateDetail([FromRoute] long id)
{
if (id <= 0)
{
return new BadRequestObjectResult(new { message = "无效的 ID" });
}
var personExists = await _personRepository.IsAnyAsync(x => x.Id == id);
if (!personExists)
{
return new NotFoundObjectResult(new { message = "人员记录不存在" });
}
var cert = await _certificateRepository.GetFirstAsync(x => x.ParticipationRecordId == id);
if (cert == null)
{
return new NotFoundObjectResult(new { message = "未找到对应的证书信息" });
}
if (string.IsNullOrWhiteSpace(cert.CertificateUrl))
{
return new NoContentResult();
}
return new JsonResult(new { url = cert.CertificateUrl });
}
2026-02-08 18:09:05 +08:00
/// <summary>
/// 下载模板
/// </summary>
2026-03-19 23:36:06 +08:00
[DisplayName("下载模板")]
[HttpGet]
public IActionResult DownloadTemplate()
2026-02-08 18:09:05 +08:00
{
2026-03-19 23:36:06 +08:00
// 动态生成空数据及正确列名、样式的Excel模板
var headers = new string[] {
"赛事地址",
"项目",
"组别",
"姓名",
"联系方式",
"单位",
"成绩",
"名次",
"名次 "
};
// 创建一个包含动态表头的列表,MiniExcel可以通过字典或匿名对象集合来生成带指定列名的Excel
var value = new List<Dictionary<string, object>>
{
// 插入一行空数据以确保生成正确的表头
headers.ToDictionary(h => h, h => (object)null)
};
using var memoryStream = new MemoryStream();
// MiniExcel 保存,使用默认样式。如果需要复杂的背景色和边框(如图片中的浅绿色表头),
// MiniExcel 目前不支持高级样式导出,因此这里我们使用 OpenXml 直接操作或基础导出。
// 但由于需求要求保持代码清晰并尽量匹配原图,这里先用 MiniExcel 导出带中文表头的文件。
// 为了实现完全一致的浅绿色样式,最好结合 EPPlus 或 ClosedXML。
// 考虑到项目中可能没有引入 EPPlus,使用 MiniExcel 的基础导出,并辅以说明。
memoryStream.SaveAs(value);
return new FileContentResult(memoryStream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
2026-02-08 18:09:05 +08:00
{
2026-03-19 23:36:06 +08:00
FileDownloadName = "模板.xlsx"
2026-02-08 18:09:05 +08:00
};
}
2026-03-08 15:46:19 +08:00
/// <summary>
/// 导出证书
/// </summary>
[DisplayName("导出证书")]
2026-04-06 09:21:47 +08:00
public async Task<string> ExportCertificate(ScoreQueryInput input)
2026-03-08 15:46:19 +08:00
{
var list = await _certificateRepository.AsQueryable()
.LeftJoin<EventResult>((c, r) => c.ResultId == r.Id)
.LeftJoin<EventRegistration>((c, r, reg) => c.RegistrationId == reg.Id)
.LeftJoin<YD_Event.Core.Entities.Project>((c, r, reg, p) => c.ProjectId == p.Id)
.Where((c, r, reg, p) => c.EventId == input.EventId)
.WhereIF(input.ProjectId.HasValue, (c, r, reg, p) => c.ProjectId == input.ProjectId)
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), (c, r, reg, p) => c.GroupName == input.GroupName)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), (c, r, reg, p) => c.ParticipantName.Contains(input.Name))
.Select((c, r, reg, p) => new CertificateExportDto
{
CertificateNo = c.CertificateNo,
ParticipantName = c.ParticipantName,
ProjectName = p.Name,
GroupName = c.GroupName,
UnitName = reg.TeamName,
Score = r.Score,
Rank = r.Rank,
CertificateType = c.CertificateType == 1 ? "冠军证书" :
c.CertificateType == 2 ? "亚军证书" :
c.CertificateType == 3 ? "季军证书" :
c.CertificateType == 4 ? "优胜证书" : "参与证书",
IssuedTime = c.IssuedTime
})
.ToListAsync();
using var stream = new MemoryStream();
stream.SaveAs(list);
2026-04-06 09:21:47 +08:00
return Convert.ToBase64String(stream.ToArray());
2026-03-08 15:46:19 +08:00
}
2026-02-08 18:09:05 +08:00
}