diff --git a/YD_Event.Application/Certificate/CertificateGenerateService.cs b/YD_Event.Application/Certificate/CertificateGenerateService.cs index ce8e9a7..4050776 100644 --- a/YD_Event.Application/Certificate/CertificateGenerateService.cs +++ b/YD_Event.Application/Certificate/CertificateGenerateService.cs @@ -382,6 +382,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient private async Task ExtractDocumentData(Stream docStream) { var documentData = new DocumentData(); + var processedTexts = new HashSet(); // 记录已处理的文本,避免重复 using var wordprocessingDocument = WordprocessingDocument.Open(docStream, false); var body = wordprocessingDocument.MainDocumentPart?.Document?.Body; @@ -392,11 +393,19 @@ public class CertificateGenerateService : IDynamicApiController, ITransient if (body != null) { // 首先处理 Drawing 对象(浮于文字上方的文本框) - ExtractDrawingObjects(body, documentData); + ExtractDrawingObjects(body, documentData, processedTexts); - // 提取段落(包含位置信息) + // 提取段落(包含位置信息),但跳过包含Drawing对象的段落(避免重复) foreach (var paragraph in body.Elements()) { + // 检查段落是否包含 Drawing 对象,如果是则跳过(已在 ExtractDrawingObjects 中处理) + var hasDrawing = paragraph.Descendants().Any(); + if (hasDrawing) + { + _logger.LogDebug("跳过包含 Drawing 对象的段落"); + continue; + } + var paragraphText = paragraph.InnerText?.Trim(); if (!string.IsNullOrEmpty(paragraphText)) { @@ -414,6 +423,13 @@ public class CertificateGenerateService : IDynamicApiController, ITransient continue; } + // 检查是否已经处理过相同文本,避免重复 + if (processedTexts.Contains(cleanedText)) + { + _logger.LogDebug("跳过重复文本: {Text}", cleanedText); + continue; + } + var paragraphData = new ParagraphData { Text = cleanedText, @@ -431,6 +447,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient cleanedText, paragraphData.PositionX, paragraphData.PositionY); documentData.Paragraphs.Add(paragraphData); + processedTexts.Add(cleanedText); } } @@ -658,7 +675,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient /// /// 提取 Drawing 对象(浮于文字上方的文本框) /// - private void ExtractDrawingObjects(Body body, DocumentData documentData) + private void ExtractDrawingObjects(Body body, DocumentData documentData, HashSet processedTexts = null) { try { @@ -670,7 +687,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient foreach (var drawing in drawings) { - ExtractTextFromDrawing(drawing, documentData); + ExtractTextFromDrawing(drawing, documentData, processedTexts); } } } @@ -683,7 +700,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient /// /// 从 Drawing 对象中提取文本和位置信息 /// - private void ExtractTextFromDrawing(DocumentFormat.OpenXml.Wordprocessing.Drawing drawing, DocumentData documentData) + private void ExtractTextFromDrawing(DocumentFormat.OpenXml.Wordprocessing.Drawing drawing, DocumentData documentData, HashSet processedTexts = null) { try { @@ -714,6 +731,13 @@ public class CertificateGenerateService : IDynamicApiController, ITransient continue; } + // 检查是否已经处理过相同文本,避免重复 + if (processedTexts != null && processedTexts.Contains(cleanedText)) + { + _logger.LogDebug("跳过 Drawing 对象中的重复文本: {Text}", cleanedText); + continue; + } + var paragraphData = new ParagraphData { Text = cleanedText, @@ -731,6 +755,10 @@ public class CertificateGenerateService : IDynamicApiController, ITransient cleanedText, paragraphData.PositionX, paragraphData.PositionY); documentData.Paragraphs.Add(paragraphData); + if (processedTexts != null) + { + processedTexts.Add(cleanedText); + } } } } @@ -1033,7 +1061,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient .FitArea(); } - // 使用 Column 来放置所有内容 + // 使用 Column 来放置所有内容,但使用 Translate 而非 Padding 定位 page.Content().Column(column => { // 根据段落信息进行定位(使用从Word文档提取的位置信息) @@ -1046,14 +1074,14 @@ public class CertificateGenerateService : IDynamicApiController, ITransient _logger.LogDebug("处理段落: Text={Text}, X={X}, Y={Y}, FontSize={FontSize}, Bold={IsBold}", paragraph.Text, paragraph.PositionX, paragraph.PositionY, fontSize, isBold); - // 直接使用从Word文档提取的位置信息 - float x = (float)(paragraph.PositionX ?? 50); - float y = (float)(paragraph.PositionY ?? 50); + // 直接使用从Word文档提取的位置信息,并增加110个单位进行调整 + float x = (float)(paragraph.PositionX ?? 50) + 90; + float y = (float)(paragraph.PositionY ?? 50) + 50; _logger.LogDebug("最终位置 - X={X}, Y={Y}", x, y); - // 使用 Padding 来定位文本 - var textElement = column.Item().PaddingLeft(x).PaddingTop(y).Text(paragraph.Text) + // 使用 Translate 而非 Padding 来定位,避免流式布局累加导致分页 + var textElement = column.Item().TranslateX(x).TranslateY(y).Text(paragraph.Text) .FontSize((float)fontSize) .FontColor(QuestPDF.Helpers.Colors.Black) .AlignLeft(); diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs index 856447b..33a9ecb 100644 --- a/YD_Event.Application/Event/UserEventRegistrationService.cs +++ b/YD_Event.Application/Event/UserEventRegistrationService.cs @@ -1131,41 +1131,45 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient // 判断是否为团队项目 var isTeamProject = project.IsTeamProject(); - // 计算分配的人数(去重) - var totalAssignedMembers = input.GroupMembers - .Where(g => assignedProjectGroups.Select(pg => pg.Id).Contains(g.GroupId)) - .SelectMany(g => g.MemberIds) - .Distinct() - .Count(); - - if (project.CompletionCount > 0) + // 对每个分配的组别进行独立验证 + foreach (var assignedGroup in assignedProjectGroups) { - if (totalAssignedMembers < project.CompletionCount) + // 找到该组别的队员分配 + var groupMemberAssignment = input.GroupMembers.FirstOrDefault(gm => gm.GroupId == assignedGroup.Id); + if (groupMemberAssignment == null) continue; + + // 计算该组别的分配人数(不跨组别,也不跨项目去重) + var assignedCount = groupMemberAssignment.MemberIds?.Distinct().Count() ?? 0; + + if (project.CompletionCount > 0) { - result.HasError = true; - result.Errors.Add(new ProjectCompletionError + if (assignedCount < project.CompletionCount) { - ProjectId = eventProject.Id, - ProjectName = project.Name ?? "未知项目", - ErrorType = "Insufficient", - ErrorMessage = isTeamProject - ? $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人" - : $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人" - }); - } - else if (totalAssignedMembers >= project.CompletionCount) - { - // 只要达到或超过完成人数就提示超限(特别是个人项目) - result.HasError = true; - result.Errors.Add(new ProjectCompletionError + result.HasError = true; + result.Errors.Add(new ProjectCompletionError + { + ProjectId = eventProject.Id, + ProjectName = project.Name ?? "未知项目", + ErrorType = "Insufficient", + ErrorMessage = isTeamProject + ? $"项目\"{project.Name}\"的组别\"{assignedGroup.GroupName}\"要求完成人数为{project.CompletionCount}人,当前只分配了{assignedCount}人,请补充队员" + : $"项目\"{project.Name}\"的组别\"{assignedGroup.GroupName}\"要求完成人数为{project.CompletionCount}人,当前只分配了{assignedCount}人,请补充队员" + }); + } + else if (assignedCount > project.CompletionCount) { - ProjectId = eventProject.Id, - ProjectName = project.Name ?? "未知项目", - ErrorType = "Excessive", - ErrorMessage = isTeamProject - ? $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额" - : $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额" - }); + // 只要达到或超过完成人数就提示超限(特别是个人项目) + result.HasError = true; + result.Errors.Add(new ProjectCompletionError + { + ProjectId = eventProject.Id, + ProjectName = project.Name ?? "未知项目", + ErrorType = "Excessive", + ErrorMessage = isTeamProject + ? $"项目\"{project.Name}\"的组别\"{assignedGroup.GroupName}\"要求完成人数为{project.CompletionCount}人,当前分配了{assignedCount}人,人数超额,请移除多余队员" + : $"项目\"{project.Name}\"的组别\"{assignedGroup.GroupName}\"要求完成人数为{project.CompletionCount}人,当前分配了{assignedCount}人,人数超额,请移除多余队员" + }); + } } } } diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index 0514590..d73ddd1 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -852,12 +852,12 @@ 清理段落文本,移除不需要的内容 - + 提取 Drawing 对象(浮于文字上方的文本框) - + 从 Drawing 对象中提取文本和位置信息