2026-04-06 09:21:47 +08:00

318 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using YD_Event.Application.Event.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using MiniExcelLibs;
using SqlSugar;
namespace YD_Event.Application.Event;
/// <summary>
/// 成绩与证书管理
/// </summary>
public class EventScoreService : BaseService<EventResult>, ITransient
{
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
private readonly ISqlSugarRepository<EventResult> _resultRepository;
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly IIdGenerator _idGenerator;
private readonly ILogger<EventScoreService> _logger;
public EventScoreService(
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventResult> resultRepository,
ISqlSugarRepository<EventCertificate> certificateRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
IIdGenerator idGenerator,
ILogger<EventScoreService> logger) : base(resultRepository)
{
_personRepository = personRepository;
_resultRepository = resultRepository;
_certificateRepository = certificateRepository;
_registrationRepository = registrationRepository;
_idGenerator = idGenerator;
_logger = logger;
}
/// <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)
.Where((p, r, res, c) => r.EventId == input.EventId && (p.PersonType == PersonType.Athlete || p.PersonType == PersonType.User))
.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)
.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))
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), p => p.Phone.Contains(input.Mobile))
.WhereIF(input.Gender.HasValue, p => p.Gender == input.Gender.Value)
.Select((p, c) => new CoachLeaderListOutput
{
Id = p.Id,
Name = p.Name,
Age = p.Age,
Gender = p.Gender.ToString(),
IdCardNumber = p.IdCard,
Mobile = p.Phone,
CertificateId = c.Id
})
.ToPagedListAsync(input);
}
/// <summary>
/// 导入成绩
/// </summary>
[UnitOfWork]
[DisplayName("导入成绩")]
public async Task ImportScore(long eventId, IFormFile file)
{
using var stream = file.OpenReadStream();
var rows = stream.Query<ScoreImportDto>().ToList();
foreach(var row in rows)
{
// 先获取所有匹配的人员ID
var personIdList = await _personRepository.AsQueryable()
.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)
.Select((p, r) => p.Id)
.ToListAsync();
if (personIdList == null || personIdList.Count == 0) continue;
var person = await _personRepository.GetByIdAsync(personIdList[0]);
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;
result.Remark = row.RankText;
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;
if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User)
{
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);
}
}
}
}
/// <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 });
}
/// <summary>
/// 下载模板
/// </summary>
[DisplayName("下载模板")]
[HttpGet]
public IActionResult DownloadTemplate()
{
// 动态生成空数据及正确列名、样式的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")
{
FileDownloadName = "模板.xlsx"
};
}
/// <summary>
/// 导出证书
/// </summary>
[DisplayName("导出证书")]
public async Task<string> ExportCertificate(ScoreQueryInput input)
{
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);
return Convert.ToBase64String(stream.ToArray());
}
}