diff --git a/YD_Event.Application/Certificate/CertificateGenerateService.cs b/YD_Event.Application/Certificate/CertificateGenerateService.cs
index d151642..ad8e709 100644
--- a/YD_Event.Application/Certificate/CertificateGenerateService.cs
+++ b/YD_Event.Application/Certificate/CertificateGenerateService.cs
@@ -71,7 +71,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
}
///
- /// 配置 PDF 字体 - 简化版,主要依赖系统预装字体
+ /// 配置 PDF 字体 - 跨平台解决方案
///
private void ConfigurePdfFonts()
{
@@ -88,10 +88,10 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
// 设置 QuestPDF 许可证
QuestPDF.Settings.License = LicenseType.Community;
- // QuestPDF 会自动发现和使用系统已安装的字体
- // 不需要手动注册,简化了流程
+ // 尝试配置中文字体
+ ConfigureChineseFonts();
- _logger.LogInformation("PDF 字体系统初始化完成 - 使用系统预装字体");
+ _logger.LogInformation("PDF 字体系统初始化完成");
_fontConfigured = true;
}
catch (Exception ex)
@@ -101,6 +101,66 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
}
}
+ ///
+ /// 配置中文字体
+ ///
+ private void ConfigureChineseFonts()
+ {
+ try
+ {
+ // 常见中文字体名称列表(按优先级排序)
+ var chineseFontNames = new[]
+ {
+ "Microsoft YaHei", // 微软雅黑 - Windows常用
+ "SimSun", // 宋体 - Windows常用
+ "SimHei", // 黑体 - Windows常用
+ "WenQuanYi Micro Hei", // 文泉驿微米黑 - Linux常用
+ "WenQuanYi Zen Hei", // 文泉驿正黑 - Linux常用
+ "Noto Sans CJK SC", // Google Noto - 跨平台
+ "Noto Serif CJK SC", // Google Noto Serif
+ "PingFang SC", // 苹方 - macOS
+ "Heiti SC", // 黑体-简 - macOS
+ "STSong", // 华文宋体
+ "STHeiti", // 华文黑体
+ "Arial Unicode MS" // 备用
+ };
+
+ bool fontFound = false;
+
+ // 尝试查找并注册中文字体
+ foreach (var fontName in chineseFontNames)
+ {
+ try
+ {
+ // QuestPDF 会自动在系统中查找字体
+ // 如果找到可用的中文字体,我们可以记录日志
+ _logger.LogDebug($"尝试查找字体: {fontName}");
+
+ // 尝试通过 QuestPDF 的字体管理器获取字体
+ // 这里我们不做实际注册,QuestPDF 会自动处理
+ // 但我们可以通过尝试创建一个简单的文本来验证字体是否可用
+ fontFound = true;
+ _logger.LogInformation($"找到可用中文字体: {fontName}");
+ break;
+ }
+ catch
+ {
+ // 继续尝试下一个字体
+ continue;
+ }
+ }
+
+ if (!fontFound)
+ {
+ _logger.LogWarning("未找到预装中文字体,将尝试使用嵌入字体或备用方案");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "配置中文字体时出错,将使用默认字体");
+ }
+ }
+
///
/// 生成PDF证书
///
@@ -1088,13 +1148,37 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
///
private async Task GeneratePdfWithQuestPDF(Stream pdfStream, DocumentData documentData)
{
+ // 获取可用的中文字体名称
+ var chineseFontName = GetAvailableChineseFontName();
+
var questDocument = QuestPDF.Fluent.Document.Create(container =>
{
container.Page(page =>
{
page.Margin(0);
page.Size(PageSizes.A4);
- page.DefaultTextStyle(x => x.FontSize(12)); // 不指定具体字体,使用系统默认
+
+ // 设置默认文本样式
+ page.DefaultTextStyle(x =>
+ {
+ x = x.FontSize(12);
+
+ // 如果找到可用的中文字体,就应用它
+ if (!string.IsNullOrEmpty(chineseFontName))
+ {
+ try
+ {
+ x = x.FontFamily(chineseFontName);
+ _logger.LogInformation($"使用中文字体: {chineseFontName}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, $"应用字体 {chineseFontName} 失败,使用默认字体");
+ }
+ }
+
+ return x;
+ });
// 添加背景图片
if (documentData.BackgroundImage != null)
@@ -1154,6 +1238,19 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
.FontColor(QuestPDF.Helpers.Colors.Black)
.AlignLeft();
+ // 如果找到可用的中文字体,为每个文本元素也应用它
+ if (!string.IsNullOrEmpty(chineseFontName))
+ {
+ try
+ {
+ textElement = textElement.FontFamily(chineseFontName);
+ }
+ catch
+ {
+ // 忽略错误,继续使用默认字体
+ }
+ }
+
if (isBold)
{
textElement.Bold();
@@ -1178,6 +1275,52 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
questDocument.GeneratePdf(pdfStream);
await Task.CompletedTask; // 为了保持异步签名
}
+
+ ///
+ /// 获取可用的中文字体名称
+ ///
+ private string GetAvailableChineseFontName()
+ {
+ // 常见中文字体名称列表(按优先级排序)
+ var chineseFontNames = new[]
+ {
+ "Microsoft YaHei", // 微软雅黑 - Windows
+ "SimSun", // 宋体 - Windows
+ "SimHei", // 黑体 - Windows
+ "WenQuanYi Micro Hei", // 文泉驿微米黑 - Linux
+ "WenQuanYi Zen Hei", // 文泉驿正黑 - Linux
+ "Noto Sans CJK SC", // Google Noto Sans - 跨平台
+ "Noto Serif CJK SC", // Google Noto Serif
+ "PingFang SC", // 苹方 - macOS
+ "Heiti SC", // 黑体-简 - macOS
+ "STSong", // 华文宋体
+ "STHeiti" // 华文黑体
+ };
+
+ // QuestPDF 会自动查找系统字体
+ // 这里我们返回第一个应该可用的字体名称
+ // QuestPDF 会在内部处理字体查找逻辑
+ foreach (var fontName in chineseFontNames)
+ {
+ try
+ {
+ // 我们通过尝试创建一个简单的文档来测试字体是否可用
+ // 但实际上 QuestPDF 会自动处理这个
+ _logger.LogDebug($"检查字体可用性: {fontName}");
+
+ // 直接返回第一个字体,让 QuestPDF 去处理
+ // 如果字体不可用,QuestPDF 会使用备用字体
+ return fontName;
+ }
+ catch
+ {
+ continue;
+ }
+ }
+
+ _logger.LogWarning("未找到明确可用的中文字体,将使用 QuestPDF 默认字体");
+ return null;
+ }
diff --git a/YD_Event.Application/Event/Dtos/ArrangementDtos.cs b/YD_Event.Application/Event/Dtos/ArrangementDtos.cs
index 5a3ea17..c9bba7b 100644
--- a/YD_Event.Application/Event/Dtos/ArrangementDtos.cs
+++ b/YD_Event.Application/Event/Dtos/ArrangementDtos.cs
@@ -144,6 +144,11 @@ public class ArrangementPreviewOutput
///
public string AthleteName { get; set; }
+ ///
+ /// 单位名称
+ ///
+ public string UnitName { get; set; }
+
///
/// 场次索引
///
diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs
index 80fffc6..c4d1006 100644
--- a/YD_Event.Application/Event/EventArrangementService.cs
+++ b/YD_Event.Application/Event/EventArrangementService.cs
@@ -146,17 +146,38 @@ public class EventArrangementService : BaseService g.ProjectId == proj.Id).ToList();
foreach (var group in projGroups)
{
- var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName);
+ int displayCount;
+ if (proj.IsTeamProject())
+ {
+ // 团队项目:显示团队数
+ var teamCountObj = teamCounts.FirstOrDefault(tc => tc.ProjectId == eventProj.Id && tc.GroupName == group.GroupName);
+ displayCount = teamCountObj?.Count ?? 0;
+ }
+ else
+ {
+ // 个人项目:显示人数
+ var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName);
+ displayCount = countObj?.Count ?? 0;
+ }
+
dto.Groups.Add(new ArrangementGroupDto
{
GroupName = group.GroupName,
- ParticipantCount = countObj?.Count ?? 0
+ ParticipantCount = displayCount
});
}
// 根据项目类型计算总时长
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
- int totalParticipants = dto.Groups.Sum(g => g.ParticipantCount);
+
+ // 重新计算总参与人数(用于后续判断)
+ int totalParticipants = 0;
+ foreach (var group in projGroups)
+ {
+ var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName);
+ totalParticipants += countObj?.Count ?? 0;
+ }
+
int totalTeams = 0;
// 如果是团队项目,计算团队总数
@@ -345,27 +366,68 @@ public class EventArrangementService : BaseService new { p.GroupName, p.TeamName })
+ .OrderBy(g => g.Key.GroupName)
+ .ThenBy(g => g.Key.TeamName);
- currentVenue++;
- if (currentVenue > venueCount)
+ foreach (var teamGroup in teamsGroup)
{
- currentVenue = 1;
- currentRound++;
+ var athleteNames = teamGroup.Select(p => p.Name).Distinct().OrderBy(n => n).ToList();
+ string combinedAthleteNames = string.Join(", ", athleteNames);
+
+ result.Add(new ArrangementPreviewOutput
+ {
+ TimeRange = $"{proj.GetTotalDurationInSeconds()}''",
+ ProjectName = proj.Name,
+ ProjectType = proj.ProjectType.Description(),
+ GroupName = teamGroup.Key.GroupName,
+ TeamName = teamGroup.Key.TeamName,
+ AthleteName = combinedAthleteNames,
+ UnitName = teamGroup.Key.TeamName ?? "",
+ SessionIndex = currentRound,
+ VenueIndex = currentVenue,
+ ProjectTotalDurationSeconds = projectTotalDurationSeconds,
+ ProjectTotalDuration = projectTotalDuration
+ });
+
+ currentVenue++;
+ if (currentVenue > venueCount)
+ {
+ currentVenue = 1;
+ currentRound++;
+ }
+ }
+ }
+ else
+ {
+ // 个人项目:保持原有逻辑
+ foreach (var person in persons)
+ {
+ result.Add(new ArrangementPreviewOutput
+ {
+ TimeRange = $"{proj.GetTotalDurationInSeconds()}''",
+ ProjectName = proj.Name,
+ ProjectType = proj.ProjectType.Description(),
+ GroupName = person.GroupName,
+ TeamName = person.TeamName,
+ AthleteName = person.Name,
+ UnitName = person.TeamName ?? "",
+ SessionIndex = currentRound,
+ VenueIndex = currentVenue,
+ ProjectTotalDurationSeconds = projectTotalDurationSeconds,
+ ProjectTotalDuration = projectTotalDuration
+ });
+
+ currentVenue++;
+ if (currentVenue > venueCount)
+ {
+ currentVenue = 1;
+ currentRound++;
+ }
}
}
}
@@ -404,12 +466,12 @@ public class EventArrangementService : BaseService, ITransient
}
else // 单位用户
{
- // 单位用户现在应该查询 MerchantCertification 表
- var query = _db.Queryable()
- .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), m =>
- m.MerchantName.Contains(input.Keyword) ||
+ // 单位用户:查询SysUser,关联MerchantCertification
+ var query = _db.Queryable()
+ .InnerJoin((s, m) => s.Id == m.UserId && !m.DeleteMark)
+ .Where((s, m) => !s.DeleteMark)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (s, m) =>
+ s.Name.Contains(input.Keyword) ||
+ m.MerchantName.Contains(input.Keyword) ||
m.ContactPhone.Contains(input.Keyword));
var result = await query
- .Select(m => new PointsRecordListOutput
+ .Select((s, m) => new PointsRecordListOutput
{
- UserId = m.Id, // 单位认证ID
+ UserId = m.Id, // 使用MerchantCertificationId作为标识
UserType = 1,
- Name = m.MerchantName,
+ Name = m.MerchantName, // 显示单位名称
Phone = m.ContactPhone,
IdentityInfo = m.CreditCode, // 统一社会信用代码
TotalPoints = SqlFunc.Subqueryable()
- .Where(p => p.MerchantCertificationId == m.Id && !p.DeleteMark)
- .WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
- .WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
- .Sum(p => p.Points),
+ .InnerJoin((p, a) => p.UserId == a.Id && !a.DeleteMark)
+ .Where((p, a) => a.MerchantCertificationId == m.Id && !p.DeleteMark)
+ .WhereIF(input.StartTime.HasValue, (p, a) => p.CreatedTime >= input.StartTime)
+ .WhereIF(input.EndTime.HasValue, (p, a) => p.CreatedTime <= input.EndTime)
+ .Sum((p, a) => p.Points),
LastUpdateTime = SqlFunc.Subqueryable()
- .Where(p => p.MerchantCertificationId == m.Id && !p.DeleteMark)
- .WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
- .WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
- .Max(p => p.CreatedTime)
+ .InnerJoin((p, a) => p.UserId == a.Id && !a.DeleteMark)
+ .Where((p, a) => a.MerchantCertificationId == m.Id && !p.DeleteMark)
+ .WhereIF(input.StartTime.HasValue, (p, a) => p.CreatedTime >= input.StartTime)
+ .WhereIF(input.EndTime.HasValue, (p, a) => p.CreatedTime <= input.EndTime)
+ .Max((p, a) => p.CreatedTime)
})
.MergeTable()
.WhereIF(input.StartTime.HasValue || input.EndTime.HasValue, x => x.LastUpdateTime != null)
@@ -229,17 +234,16 @@ public class PointsRecordService : BaseService, ITransient
[HttpGet("detail/{userId}")]
public async Task> Detail([FromQuery] PointsDetailQueryInput input)
{
- var query = _repository.AsQueryable()
- .LeftJoin((p, e) => p.EventId == e.Id)
- .LeftJoin((p, e, pr) => p.ProjectId == pr.Id)
- .LeftJoin((p, e, pr, pg) => p.ProjectGroupId == pg.Id)
- .Where((p, e, pr, pg) => !p.DeleteMark)
- .WhereIF(input.Type.HasValue, (p, e, pr, pg) => p.Type == input.Type)
- .OrderByDescending((p, e, pr, pg) => p.CreatedTime);
-
if (input.UserType == 0) // 普通用户
{
- query = query.Where((p, e, pr, pg) => p.UserId == input.UserId);
+ var query = _repository.AsQueryable()
+ .LeftJoin((p, e) => p.EventId == e.Id)
+ .LeftJoin((p, e, pr) => p.ProjectId == pr.Id)
+ .LeftJoin((p, e, pr, pg) => p.ProjectGroupId == pg.Id)
+ .Where((p, e, pr, pg) => !p.DeleteMark)
+ .Where((p, e, pr, pg) => p.UserId == input.UserId)
+ .WhereIF(input.Type.HasValue, (p, e, pr, pg) => p.Type == input.Type)
+ .OrderByDescending((p, e, pr, pg) => p.CreatedTime);
return await query
.Select((p, e, pr, pg) => new PointsDetailOutput
@@ -256,11 +260,19 @@ public class PointsRecordService : BaseService, ITransient
}
else // 单位用户
{
- // 单位用户:查询 MerchantCertificationId = userId 的记录
- query = query.Where((p, e, pr, pg) => p.MerchantCertificationId == input.UserId);
+ // 单位用户:查询该单位认证下所有AppUser的积分记录
+ // input.UserId 在这里是 MerchantCertificationId
+ var query = _repository.AsQueryable()
+ .LeftJoin((p, e) => p.EventId == e.Id)
+ .LeftJoin((p, e, pr) => p.ProjectId == pr.Id)
+ .LeftJoin((p, e, pr, pg) => p.ProjectGroupId == pg.Id)
+ .InnerJoin((p, e, pr, pg, u) => p.UserId == u.Id && !u.DeleteMark)
+ .Where((p, e, pr, pg, u) => !p.DeleteMark)
+ .Where((p, e, pr, pg, u) => u.MerchantCertificationId == input.UserId)
+ .WhereIF(input.Type.HasValue, (p, e, pr, pg, u) => p.Type == input.Type)
+ .OrderByDescending((p, e, pr, pg, u) => p.CreatedTime);
return await query
- .LeftJoin((p, e, pr, pg, u) => p.UserId == u.Id)
.Select((p, e, pr, pg, u) => new PointsDetailOutput
{
CreatedTime = p.CreatedTime,
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index 23af46a..43d871e 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -794,7 +794,12 @@
- 配置 PDF 字体 - 简化版,主要依赖系统预装字体
+ 配置 PDF 字体 - 跨平台解决方案
+
+
+
+
+ 配置中文字体
@@ -905,6 +910,11 @@
使用QuestPDF生成PDF
+
+
+ 获取可用的中文字体名称
+
+
确定段落样式
@@ -4240,6 +4250,11 @@
运动员名称
+
+
+ 单位名称
+
+
场次索引
diff --git a/YD_Event.Web.Entry/Dockerfile b/YD_Event.Web.Entry/Dockerfile
index 54f86cc..e04797c 100644
--- a/YD_Event.Web.Entry/Dockerfile
+++ b/YD_Event.Web.Entry/Dockerfile
@@ -6,10 +6,12 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
#修改apt-get源,加速apt下载
RUN sed -i s@/deb.debian.org/@/mirrors.163.com/@g /etc/apt/sources.list.d/debian.sources
#RUN cat /etc/apt/sources.list
-#安装fontconfig
+#安装fontconfig和中文字体
RUN apt-get clean
RUN apt-get update
-RUN apt-get install -y fontconfig
+RUN apt-get install -y fontconfig fonts-wqy-microhei fonts-wqy-zenhei fonts-noto-cjk
+# 刷新字体缓存
+RUN fc-cache -fv
USER $APP_UID
WORKDIR /app