From 04b9fa82209368ba689ac18acd3e8abbec414b28 Mon Sep 17 00:00:00 2001 From: zpc Date: Mon, 30 Mar 2026 18:00:30 +0800 Subject: [PATCH 1/7] feat(pdf): Add page numbering to generated PDF documents - Add page counter display at bottom center of each PDF page - Track total page count and current page index during PDF generation - Use Arial font (10pt) with gray color (#999999) for page numbers - Format page numbers as "current / total" (e.g., "1 / 5") - Position page numbers 20pt from bottom of page - Refactor loop from foreach to indexed for loop to access page index - Improve PDF document readability with page navigation information --- .../Services/PdfGenerationService.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs index 995d822..7d69b2f 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs @@ -409,17 +409,27 @@ public class PdfGenerationService : IPdfGenerationService private static void BuildAndSavePdf(List images, string filePath) { using var document = new PdfDocument(); + var totalPages = images.Count; + var font = new XFont("Arial", 10, XFontStyleEx.Regular); + var brush = new XSolidBrush(XColor.FromArgb(153, 153, 153)); // #999999 - foreach (var imageBytes in images) + for (var i = 0; i < totalPages; i++) { + var imageBytes = images[i]; var page = document.AddPage(); page.Width = XUnit.FromPoint(PageWidthPt); page.Height = XUnit.FromPoint(PageHeightPt); - using var stream = new MemoryStream(imageBytes); using var xImage = XImage.FromStream(() => new MemoryStream(imageBytes)); using var gfx = XGraphics.FromPdfPage(page); gfx.DrawImage(xImage, 0, 0, page.Width, page.Height); + + // 绘制页码(底部居中) + var pageNumber = $"{i + 1} / {totalPages}"; + var size = gfx.MeasureString(pageNumber, font); + var x = (page.Width - size.Width) / 2; + var y = page.Height - 20; // 距底部 20pt + gfx.DrawString(pageNumber, font, brush, x, y); } document.Save(filePath); From f8a9aaf71fcf95b4839779c35e200cb0d2d38840 Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 31 Mar 2026 15:08:23 +0800 Subject: [PATCH 2/7] feat(assessment): Add retest status for equal score detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new assessment status 7 ("需重测") for cases where all scores are equal - Create AllScoresEqualException to handle scenarios where 8 intelligences or 40 ability dimensions have identical scores - Implement CheckAllScoresEqual validation in ReportGenerationService to detect and prevent invalid report generation - Add UpdateRecordStatusToRetestAsync method in ReportQueueConsumer to handle retest status updates - Update admin UI status tag mapping to display retest status with warning indicator - Add user-friendly message for retest status in AssessmentService - Update status description mappings across services to include new retest status - Prevent PDF generation when all scores are equal, prompting users to retake the assessment --- .../Services/AssessmentRecordService.cs | 3 +- .../business/assessment/record/index.vue | 1 + .../BackgroundServices/ReportQueueConsumer.cs | 45 +++++++ .../Exceptions/AllScoresEqualException.cs | 27 +++++ .../Services/AssessmentService.cs | 6 + .../Services/ReportGenerationService.cs | 37 ++++++ .../Models/Assessment/ResultStatusDto.cs | 2 +- uniapp/pages/assessment/history/index.vue | 43 ++++++- uniapp/pages/assessment/loading/index.vue | 112 +++++++++++++++++- uniapp/pages/assessment/questions/index.vue | 2 +- 10 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 server/MiAssessment/src/MiAssessment.Core/Exceptions/AllScoresEqualException.cs diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs index 1865ccc..f7240ff 100644 --- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs @@ -34,7 +34,8 @@ public class AssessmentRecordService : IAssessmentRecordService { 3, "生成中" }, { 4, "已完成" }, { 5, "生成失败" }, - { 6, "数据已就绪" } + { 6, "数据已就绪" }, + { 7, "需重测" } }; /// diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue index 0443cef..289aa5e 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue @@ -495,6 +495,7 @@ function getStatusTagType(status: number): 'info' | 'primary' | 'warning' | 'suc case 4: return 'success' case 5: return 'danger' case 6: return 'primary' + case 7: return 'warning' default: return 'info' } } diff --git a/server/MiAssessment/src/MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs b/server/MiAssessment/src/MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs index 55f68b3..f057e83 100644 --- a/server/MiAssessment/src/MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs +++ b/server/MiAssessment/src/MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using MiAssessment.Core.Exceptions; using MiAssessment.Core.Interfaces; using MiAssessment.Core.Models; using MiAssessment.Core.Services; @@ -165,6 +166,13 @@ public class ReportQueueConsumer : BackgroundService await UpdateRecordStatusToFailedAsync(message.RecordId); } } + catch (AllScoresEqualException asEx) + { + // 同分异常:不重试,直接设置状态为需重测(Status=7) + _logger.LogWarning("检测到全同分情况,RecordId: {RecordId}, ScoreType: {ScoreType}, Message: {Message}", + message.RecordId, asEx.ScoreType, asEx.Message); + await UpdateRecordStatusToRetestAsync(message.RecordId); + } catch (Exception ex) { // 报告生成失败,执行重试或死信逻辑 @@ -257,4 +265,41 @@ public class ReportQueueConsumer : BackgroundService _logger.LogError(ex, "更新测评记录状态为生成失败时发生异常,RecordId: {RecordId}", recordId); } } + + /// + /// 更新测评记录状态为需重测(Status=7) + /// + /// + /// 当检测到八大智能8项全同分或40项细分维度全同分时调用, + /// 不生成PDF,提示用户重新测评。 + /// + /// 测评记录ID + private async Task UpdateRecordStatusToRetestAsync(long recordId) + { + try + { + using var scope = _serviceScopeFactory.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var record = await dbContext.AssessmentRecords + .FirstOrDefaultAsync(r => r.Id == recordId); + + if (record != null) + { + record.Status = 7; + record.UpdateTime = DateTime.Now; + await dbContext.SaveChangesAsync(); + + _logger.LogInformation("测评记录状态已更新为需重测,RecordId: {RecordId}", recordId); + } + else + { + _logger.LogWarning("更新状态失败,测评记录不存在,RecordId: {RecordId}", recordId); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "更新测评记录状态为需重测时发生异常,RecordId: {RecordId}", recordId); + } + } } diff --git a/server/MiAssessment/src/MiAssessment.Core/Exceptions/AllScoresEqualException.cs b/server/MiAssessment/src/MiAssessment.Core/Exceptions/AllScoresEqualException.cs new file mode 100644 index 0000000..dc38b7a --- /dev/null +++ b/server/MiAssessment/src/MiAssessment.Core/Exceptions/AllScoresEqualException.cs @@ -0,0 +1,27 @@ +namespace MiAssessment.Core.Exceptions; + +/// +/// 所有分数相同异常(八大智能全同分或40项细分全同分) +/// +/// +/// 当检测到八大智能8项全部同分或40项细分维度全部同分时抛出, +/// 表示无法生成有效的差异化报告,需要用户重新测评。 +/// +public class AllScoresEqualException : Exception +{ + /// + /// 同分类型:Intelligence=八大智能同分,Ability=细分维度同分 + /// + public string ScoreType { get; } + + /// + /// 构造函数 + /// + /// 同分类型 + /// 异常信息 + public AllScoresEqualException(string scoreType, string message) + : base(message) + { + ScoreType = scoreType; + } +} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs index 96f90a7..32b5507 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs @@ -119,6 +119,11 @@ public class AssessmentService : IAssessmentService { record.Message = "报告生成失败,请联系客服"; } + // 对需重测状态设置提示信息 + else if (record.Status == 7) + { + record.Message = "分析得出多个智能处于同一梯队,我们需要更细致的分析维度,接下来请您重新进行测评"; + } _logger.LogDebug("查询报告状态成功,status: {Status}, isCompleted: {IsCompleted}", record.Status, record.IsCompleted); } @@ -655,6 +660,7 @@ public class AssessmentService : IAssessmentService 4 => "已完成", 5 => "生成失败", 6 => "报告生成中", + 7 => "需重测", _ => "未知" }; } diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs index 96d60b0..098d9d7 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs @@ -1,3 +1,4 @@ +using MiAssessment.Core.Exceptions; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using Microsoft.EntityFrameworkCore; @@ -60,6 +61,9 @@ public class ReportGenerationService _logger.LogDebug("父级汇总完成,recordId: {RecordId}, 新增父级数量: {Count}, 总数量: {Total}", recordId, parentScores.Count, categoryScores.Count); + // 步骤3.6:检测同分情况(八大智能全同分或细分维度全同分) + CheckAllScoresEqual(categoryScores); + // 步骤4:按 CategoryType 分组排名 var rankedScores = CalculateRanks(categoryScores); @@ -300,6 +304,39 @@ public class ReportGenerationService return parentScores; } + /// + /// 检测同分情况:八大智能8项全同分或细分维度全同分 + /// + /// 所有分类得分(含叶子和父级) + /// 当检测到全同分时抛出 + internal static void CheckAllScoresEqual(List categoryScores) + { + // CategoryType=1 为八大智能(父级汇总后的得分) + var intelligenceScores = categoryScores + .Where(s => s.CategoryType == 1) + .Select(s => s.Percentage) + .ToList(); + + if (intelligenceScores.Count >= 8 && intelligenceScores.Distinct().Count() == 1) + { + throw new AllScoresEqualException("Intelligence", + $"八大智能8项得分全部相同({intelligenceScores.First()}%),无法生成差异化报告"); + } + + // CategoryType=3 为细分能力维度 + var abilityScores = categoryScores + .Where(s => s.CategoryType == 3) + .Select(s => s.Percentage) + .ToList(); + + if (abilityScores.Count >= 40 && abilityScores.Distinct().Count() == 1) + { + throw new AllScoresEqualException("Ability", + $"40项细分维度得分全部相同({abilityScores.First()}%),无法生成差异化报告"); + } + } + + /// /// 分类得分计算结果 /// diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/ResultStatusDto.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/ResultStatusDto.cs index 36f63b3..c6cccf9 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/ResultStatusDto.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/ResultStatusDto.cs @@ -6,7 +6,7 @@ namespace MiAssessment.Model.Models.Assessment; public class ResultStatusDto { /// - /// 状态:0待支付 1待测评 2测评中 3生成中 4已完成 5生成失败 + /// 状态:0待支付 1待测评 2测评中 3生成中 4已完成 5生成失败 6数据已就绪 7需重测 /// public int Status { get; set; } diff --git a/uniapp/pages/assessment/history/index.vue b/uniapp/pages/assessment/history/index.vue index c252b56..abbdd7f 100644 --- a/uniapp/pages/assessment/history/index.vue +++ b/uniapp/pages/assessment/history/index.vue @@ -21,7 +21,8 @@ const ASSESSMENT_STATUS = { GENERATING: 3, // 生成中 COMPLETED: 4, // 已完成 FAILED: 5, // 生成失败 - DATA_READY: 6 // 数据已就绪(PDF生成中) + DATA_READY: 6, // 数据已就绪(PDF生成中) + NEED_RETEST: 7 // 需重测(全同分) } // 状态 @@ -46,7 +47,8 @@ function getStatusClass(status) { [ASSESSMENT_STATUS.GENERATING]: 'status-generating', [ASSESSMENT_STATUS.COMPLETED]: 'status-completed', [ASSESSMENT_STATUS.FAILED]: 'status-failed', - [ASSESSMENT_STATUS.DATA_READY]: 'status-generating' + [ASSESSMENT_STATUS.DATA_READY]: 'status-generating', + [ASSESSMENT_STATUS.NEED_RETEST]: 'status-retest' } return classMap[status] || '' } @@ -155,6 +157,14 @@ function viewResult(record) { }) return } + + // 需重测 - 跳转到测评首页重新开始 + if (record.status === ASSESSMENT_STATUS.NEED_RETEST) { + uni.navigateTo({ + url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}` + }) + return + } } /** @@ -208,13 +218,19 @@ onMounted(() => { - + 查看报告 + + + 重新测试 + + + @@ -296,6 +312,18 @@ onMounted(() => { color: $success-color; background-color: rgba(82, 196, 26, 0.1); } + + // 生成失败 - 红色 + &.status-failed { + color: $error-color; + background-color: rgba(255, 77, 79, 0.1); + } + + // 需重测 - 橙色 + &.status-retest { + color: $warning-color; + background-color: rgba(250, 173, 20, 0.1); + } } } @@ -331,6 +359,15 @@ onMounted(() => { align-items: center; color: $primary-color; font-size: $font-size-md; + + &.retest-btn { + color: $warning-color; + + .arrow-icon { + border-right-color: $warning-color; + border-bottom-color: $warning-color; + } + } .arrow-icon { width: 12rpx; diff --git a/uniapp/pages/assessment/loading/index.vue b/uniapp/pages/assessment/loading/index.vue index cf8c7b7..874929d 100644 --- a/uniapp/pages/assessment/loading/index.vue +++ b/uniapp/pages/assessment/loading/index.vue @@ -7,6 +7,7 @@ * - 显示提示文字 * - 轮询查询报告生成状态(3秒间隔) * - 生成完成自动跳转结果页 + * - Status=7 需重测:显示提示和重新测试按钮 */ import { ref, onMounted, onUnmounted } from 'vue' @@ -19,6 +20,7 @@ const userStore = useUserStore() // 页面参数 const recordId = ref('') +const typeId = ref('') // 轮询定时器 let pollTimer = null @@ -32,6 +34,10 @@ const MAX_POLL_COUNT = 100 // 当前轮询次数 const pollCount = ref(0) +// 是否需要重新测评(Status=7) +const needRetest = ref(false) +const retestMessage = ref('') + /** * 开始轮询查询状态 */ @@ -90,7 +96,7 @@ async function checkStatus() { if (res && res.code === 0 && res.data) { const status = res.data.status - // 状态:3-生成中 4-已完成 5-失败 + // 状态:3-生成中 4-已完成 5-失败 7-需重测 if (status === 4) { // 生成完成,跳转结果页 stopPolling() @@ -100,6 +106,11 @@ async function checkStatus() { uni.redirectTo({ url: `/pages/assessment/result/index?recordId=${recordId.value}&reportUrl=${encodeURIComponent(reportUrl)}` }) + } else if (status === 7) { + // 需重测:停止轮询,显示提示和重新测试按钮 + stopPolling() + needRetest.value = true + retestMessage.value = res.data.message || '分析得出多个智能处于同一梯队,我们需要更细致的分析维度,接下来请您重新进行测评' } else if (status === 5) { // 生成失败 stopPolling() @@ -115,7 +126,7 @@ async function checkStatus() { } }) } - // status === 3 继续轮询 + // status === 3 或 6 继续轮询 } } catch (error) { console.error('查询状态失败:', error) @@ -123,11 +134,28 @@ async function checkStatus() { } } +/** + * 重新测试 + */ +function handleRetest() { + // 跳转到测评首页重新开始 + if (typeId.value) { + uni.redirectTo({ + url: `/pages/assessment/info/index?typeId=${typeId.value}` + }) + } else { + uni.switchTab({ + url: '/pages/index/index' + }) + } +} + /** * 页面加载 */ onLoad((options) => { recordId.value = options.recordId || '' + typeId.value = options.typeId || '' // 恢复用户登录状态 userStore.restoreFromStorage() @@ -149,8 +177,17 @@ onUnmounted(() => { + + + + + + {{ retestMessage }} + + + - + @@ -160,6 +197,13 @@ onUnmounted(() => { 可在往期测评中查看测评结果 + + + + + 重新测试 + + @@ -170,6 +214,8 @@ onUnmounted(() => { .assessment-loading-page { min-height: 100vh; background-color: $bg-white; + display: flex; + flex-direction: column; } .loading-content { @@ -205,4 +251,64 @@ onUnmounted(() => { font-size: $font-size-md; color: $text-placeholder; } + +// 需重测内容 +.retest-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: $spacing-xl 64rpx; +} + +.retest-image { + width: 360rpx; + height: 360rpx; + margin-bottom: $spacing-xl; +} + +.retest-text { + text-align: center; +} + +.retest-title { + display: block; + font-size: $font-size-lg; + color: $text-secondary; + line-height: 1.6; +} + +// 底部操作栏 +.bottom-action { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: $spacing-lg $spacing-xl; + padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom)); + background-color: transparent; + z-index: 100; +} + +// 重新测试按钮 +.retest-btn { + width: 100%; + height: 88rpx; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #FF8A6B 0%, #FF6B6B 100%); + border-radius: $border-radius-round; + + text { + font-size: $font-size-lg; + color: $text-white; + font-weight: $font-weight-medium; + } + + &:active { + opacity: 0.85; + } +} diff --git a/uniapp/pages/assessment/questions/index.vue b/uniapp/pages/assessment/questions/index.vue index 7d9437b..d910dc2 100644 --- a/uniapp/pages/assessment/questions/index.vue +++ b/uniapp/pages/assessment/questions/index.vue @@ -198,7 +198,7 @@ async function handleSubmit() { if (res && res.code === 0) { const resRecordId = res.data?.recordId || res.data?.id || recordId.value - uni.redirectTo({ url: `/pages/assessment/loading/index?recordId=${resRecordId}` }) + uni.redirectTo({ url: `/pages/assessment/loading/index?recordId=${resRecordId}&typeId=${typeId.value}` }) } else { uni.showToast({ title: res?.message || '提交失败,请重试', icon: 'none' }) } From 307eb3f0a0ac92ac7ae29f3f56815c66bf070cb6 Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 31 Mar 2026 16:03:13 +0800 Subject: [PATCH 3/7] 21 --- uniapp/manifest.json | 2 +- uniapp/pages.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/uniapp/manifest.json b/uniapp/manifest.json index 58eb3ae..b56f978 100644 --- a/uniapp/manifest.json +++ b/uniapp/manifest.json @@ -1,6 +1,6 @@ { "name" : "学业邑规划", - "appid" : "__UNI__A612028", + "appid" : "__UNI__1BAACAB", "description" : "", "versionName" : "1.0.0", "versionCode" : "100", diff --git a/uniapp/pages.json b/uniapp/pages.json index 6381445..70c562f 100644 --- a/uniapp/pages.json +++ b/uniapp/pages.json @@ -12,7 +12,7 @@ "path": "pages/team/index", "style": { "navigationStyle": "custom", - "navigationBarTitleText": "团队" + "navigationBarTitleText": "产品" } }, { @@ -147,7 +147,7 @@ }, { "pagePath": "pages/team/index", - "text": "团队", + "text": "产品", "iconPath": "static/tabbar/message.png", "selectedIconPath": "static/tabbar/message_s.png" }, From 20575e5ef5a79ce13034fed6796ccb4d2c6f3e82 Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 31 Mar 2026 16:20:56 +0800 Subject: [PATCH 4/7] fix(pdf): Replace XFontStyleEx with XFontStyle enum - Update font style parameter from XFontStyleEx.Regular to XFontStyle.Regular - Resolve API compatibility issue in PdfGenerationService - Ensure correct enum type usage for PDF font styling --- .../src/MiAssessment.Core/Services/PdfGenerationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs index 7d69b2f..1c75e03 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs @@ -410,7 +410,7 @@ public class PdfGenerationService : IPdfGenerationService { using var document = new PdfDocument(); var totalPages = images.Count; - var font = new XFont("Arial", 10, XFontStyleEx.Regular); + var font = new XFont("Arial", 10, XFontStyle.Regular); var brush = new XSolidBrush(XColor.FromArgb(153, 153, 153)); // #999999 for (var i = 0; i < totalPages; i++) From 0948ec601313cc9a58ecb89053863710cc3715ab Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 31 Mar 2026 16:35:37 +0800 Subject: [PATCH 5/7] 21 --- .../business/assessment/record/index.vue | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue index 289aa5e..353dcfc 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/assessment/record/index.vue @@ -106,10 +106,14 @@ + + + -