From c683a28d9874debe8bbec67004ca8125afa4f1c0 Mon Sep 17 00:00:00 2001 From: zpc Date: Wed, 18 Mar 2026 01:13:44 +0800 Subject: [PATCH] 21 --- docs/报告状态拆分需求文档.md | 186 ++++++++++++++++++ .../Services/AssessmentRecordService.cs | 13 +- .../business/assessment/record/index.vue | 10 +- .../Services/ReportDataService.cs | 5 +- .../Services/ReportGenerationService.cs | 3 +- 5 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 docs/报告状态拆分需求文档.md diff --git a/docs/报告状态拆分需求文档.md b/docs/报告状态拆分需求文档.md new file mode 100644 index 0000000..1e0208d --- /dev/null +++ b/docs/报告状态拆分需求文档.md @@ -0,0 +1,186 @@ +# 报告状态拆分需求文档 + +## 一、背景 + +当前测评记录的状态定义如下: + +| 状态值 | 含义 | +|--------|------| +| 1 | 待测评 | +| 2 | 测评中 | +| 3 | 生成中 | +| 4 | 已完成 | +| 5 | 生成失败 | + +"生成中"(状态 3)实际包含了两个阶段: +1. **数据计算阶段**:根据答案计算得分、排名、星级、匹配结论,写入 `assessment_results` 和 `assessment_record_conclusions` 表 +2. **PDF 生成阶段**:截图各报告网页 → 组装 PDF → 上传 COS + +这两个阶段被合并在同一个状态 3 下,导致以下问题: + +### 问题 1:截图服务截到空白页 +PDF 生成阶段需要截图报告网页,但报告网页的数据接口(`ReportDataService`)检查状态必须为 4(已完成)才返回数据。此时状态还是 3,所以所有页面都返回"报告尚未生成完成",截出来全是空白。 + +### 问题 2:网页报告不必等 PDF +数据计算完成后,网页报告所需的数据已经全部就绪,用户完全可以查看网页版报告。但因为状态还是 3,前端不显示"网页报告"按钮,用户必须等 PDF 生成完(状态变为 4)才能看到任何报告内容。 + +### 问题 3:职责不清晰 +"数据是否就绪"和"PDF 是否生成"是两件独立的事,不应该用同一个状态来表示。 + +## 二、方案 + +新增状态值 6(数据已就绪),将原来的状态 3 拆分为两个阶段: + +| 状态值 | 含义 | 说明 | +|--------|------|------| +| 1 | 待测评 | 不变 | +| 2 | 测评中 | 不变 | +| 3 | 生成中 | 数据计算阶段(得分、排名、星级、结论尚未完成) | +| **6** | **数据已就绪** | **数据计算完成,PDF 正在生成中** | +| 4 | 已完成 | PDF 生成完成,全部就绪 | +| 5 | 生成失败 | 不变(数据计算失败或 PDF 生成失败均为此状态) | + +### 状态流转图 + +``` +[1 待测评] → [2 测评中] → [3 生成中] + ↓ + 数据计算完成 + ↓ + [6 数据已就绪] + ↓ + PDF 生成完成 + ↓ + [4 已完成] + +失败路径: + [3 生成中] → [5 生成失败](数据计算失败,重试 3 次后) + [6 数据已就绪] → [5 生成失败](PDF 生成失败) +``` + +### 各状态下的功能可用性 + +| 功能 | 状态 3 | 状态 6 | 状态 4 | 状态 5 | +|------|--------|--------|--------|--------| +| 查看网页报告 | ❌ | ✅ | ✅ | ❌ | +| 查看 PDF | ❌ | ❌ | ✅(需有 reportUrl) | ❌ | +| 下载 PDF | ❌ | ❌ | ✅(需有 reportUrl) | ❌ | +| 重新生成 | ✅ | ✅ | ✅ | ✅ | +| 查看报告数据(后台) | ❌ | ✅ | ✅ | ❌ | + +## 三、涉及修改的文件清单 + +### 3.1 后端 - 核心流程 + +| 文件 | 修改内容 | +|------|----------| +| `MiAssessment.Core/Services/ReportGenerationService.cs` | `PersistResultsAsync` 方法:数据写入完成后将状态从 3 改为 6 | +| `MiAssessment.Core/Services/ReportDataService.cs` | `GetReportDataAsync` 方法:状态检查从 `== 4` 改为 `== 6 \|\| == 4` | +| `MiAssessment.Core/Services/PdfGenerationService.cs` | `GeneratePdfAsync` 方法:PDF 生成失败时状态从 6 改为 5;已修复的静态图片路径问题保留 | +| `MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs` | `ProcessMessageAsync` 方法:PDF 生成失败时调用 `UpdateRecordStatusToFailedAsync` 不变,但此时记录状态是从 6 变为 5 | + +### 3.2 后端 - 后台管理 + +| 文件 | 修改内容 | +|------|----------| +| `MiAssessment.Admin.Business/Services/AssessmentRecordService.cs` | 1. `StatusNames` 字典新增 `{ 6, "数据已就绪" }`
2. `RegenerateReportAsync`:状态检查增加 6(允许 3、4、5、6 重新生成)
3. `BatchRegenerateReportAsync`:同上 | +| `MiAssessment.Admin.Business/Services/AssessmentRecordService.cs` | `GetRecordReportAsync` 方法:状态检查从 `== 4` 改为 `== 6 \|\| == 4` | + +### 3.3 前端 - 后台管理 + +| 文件 | 修改内容 | +|------|----------| +| `admin-web/src/views/business/assessment/record/index.vue` | 1. 搜索表单状态下拉:新增选项 ``
2. "报告"按钮:`v-if` 从 `row.status === 4` 改为 `row.status === 6 \|\| row.status === 4`
3. "网页报告"按钮:`v-if` 从 `row.status === 4` 改为 `row.status === 6 \|\| row.status === 4`
4. "查看PDF"按钮:保持 `v-if="row.reportUrl"` 不变(状态 6 时 reportUrl 为空,自然不显示)
5. "下载PDF"按钮:保持 `v-if="row.reportUrl"` 不变
6. "重新生成"按钮:`v-if` 增加 `row.status === 6`
7. `getStatusTagType` 函数:新增 `case 6: return 'primary'`
8. 批量重新生成 `eligibleRows` 过滤:增加状态 6 | + +### 3.4 前端 - 小程序(如已实现相关页面) + +| 文件 | 修改内容 | +|------|----------| +| 测评结果状态轮询页 | 状态 6 时可跳转查看网页报告,不再需要等到状态 4 | +| 往期测评列表 | 状态 6 显示"报告已就绪",允许查看网页报告 | + +> 注:小程序前端如果尚未开发对应页面,此部分可后续处理。 + +## 四、详细修改说明 + +### 4.1 ReportGenerationService.PersistResultsAsync + +当前代码(状态保持 3): +```csharp +// 重新加载测评记录(带跟踪),保持 Status=3(生成中),等待 PDF 生成后再更新为4 +var record = await _dbContext.AssessmentRecords + .FirstAsync(r => r.Id == recordId); +record.UpdateTime = now; +``` + +修改为(状态改为 6): +```csharp +// 数据计算完成,状态更新为 6(数据已就绪),等待 PDF 生成后再更新为 4 +var record = await _dbContext.AssessmentRecords + .FirstAsync(r => r.Id == recordId); +record.Status = 6; +record.UpdateTime = now; +``` + +### 4.2 ReportDataService.GetReportDataAsync + +当前代码: +```csharp +if (record.Status != 3 && record.Status != 4) +``` + +修改为: +```csharp +if (record.Status != 6 && record.Status != 4) +``` + +说明:去掉状态 3 的放行(上一轮临时加的),改为只允许 6 和 4。状态 3 时数据还没算完,不应该渲染。 + +### 4.3 前端按钮逻辑汇总 + +``` +报告按钮(后台抽屉): row.status === 6 || row.status === 4 +网页报告按钮: row.status === 6 || row.status === 4 +查看 PDF 按钮: row.reportUrl(不变,状态 6 时 reportUrl 为空) +下载 PDF 按钮: row.reportUrl(不变) +重新生成按钮: row.status === 3 || row.status === 4 || row.status === 5 || row.status === 6 +``` + +## 五、数据库影响 + +### 5.1 无需修改表结构 +`assessment_records.Status` 字段为 `int` 类型,直接使用新值 6 即可,无需 DDL 变更。 + +### 5.2 历史数据兼容 +- 已有的状态 4 记录不受影响 +- 已有的状态 3 记录(如果有卡在生成中的)不受影响,重新生成时会走新流程 +- 已有的状态 5 记录不受影响 + +## 六、测试要点 + +| 场景 | 预期结果 | +|------|----------| +| 提交答案后触发报告生成 | 状态从 2 → 3 → 6 → 4 | +| 数据计算完成、PDF 未生成时 | 状态为 6,网页报告可查看,PDF 按钮不显示 | +| PDF 生成完成后 | 状态为 4,网页报告和 PDF 均可查看 | +| 数据计算失败(重试 3 次后) | 状态为 5 | +| PDF 生成失败 | 状态从 6 → 5,网页报告不可查看(因为状态变为 5) | +| 状态 6 时点击重新生成 | 状态从 6 → 3,重新走完整流程 | +| 状态 4 时点击重新生成 | 状态从 4 → 3,ReportUrl 清空,重新走完整流程 | +| 后台搜索状态筛选 | 下拉框包含"数据已就绪"选项,可正确筛选 | +| 批量重新生成 | 状态 3、4、5、6 的记录均可被选中 | + +## 七、关于 PDF 生成失败的状态处理 + +当前方案:PDF 生成失败时状态从 6 改为 5(生成失败)。 + +这意味着用户在 PDF 生成失败后也无法查看网页报告了(因为状态 5 不允许)。 + +**备选方案**:PDF 生成失败时状态保持 6,只是 reportUrl 为空。这样用户至少还能看网页报告。但这样的话"生成失败"状态就只表示数据计算失败了。 + +**建议采用当前方案**(失败改为 5),理由: +- 状态 5 明确告诉管理员"这条记录有问题需要处理" +- 管理员可以点击"重新生成"重试 +- 如果保持 6,管理员可能不知道 PDF 生成失败了 + +如果你觉得 PDF 失败后还应该能看网页报告,我可以调整方案。 diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs index f0f9149..fbeeb8b 100644 --- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs @@ -32,7 +32,8 @@ public class AssessmentRecordService : IAssessmentRecordService { 2, "测评中" }, { 3, "生成中" }, { 4, "已完成" }, - { 5, "生成失败" } + { 5, "生成失败" }, + { 6, "数据已就绪" } }; /// @@ -237,8 +238,8 @@ public class AssessmentRecordService : IAssessmentRecordService return null; } - // 如果记录状态不是 4(已完成),返回 null(调用方处理错误码 3242) - if (record.Status != 4) + // 如果记录状态不是 6(数据已就绪)或 4(已完成),返回 null(调用方处理错误码 3242) + if (record.Status != 6 && record.Status != 4) { _logger.LogWarning("测评报告尚未生成,记录ID: {RecordId}, 当前状态: {Status}", id, record.Status); return null; @@ -506,8 +507,8 @@ public class AssessmentRecordService : IAssessmentRecordService throw new BusinessException(ErrorCodes.AssessmentRecordNotFound, "测评记录不存在"); } - // 校验状态:允许状态为 3(生成中)、4(已完成)或 5(生成失败)的记录重新生成 - if (record.Status != 3 && record.Status != 4 && record.Status != 5) + // 校验状态:允许状态为 3(生成中)、4(已完成)、5(生成失败)或 6(数据已就绪)的记录重新生成 + if (record.Status != 3 && record.Status != 4 && record.Status != 5 && record.Status != 6) { throw new BusinessException(ErrorCodes.InvalidOperation, "当前状态不允许重新生成"); } @@ -572,7 +573,7 @@ public class AssessmentRecordService : IAssessmentRecordService } // 状态不符,跳过 - if (record.Status != 3 && record.Status != 4 && record.Status != 5) + if (record.Status != 3 && record.Status != 4 && record.Status != 5 && record.Status != 6) { _logger.LogWarning("批量重新生成:状态不符,ID: {RecordId}, 状态: {Status}", recordId, record.Status); skippedCount++; 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 1412c0c..394d6bf 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 @@ -38,6 +38,7 @@ + @@ -115,7 +116,7 @@ 详情 r.status === 3 || r.status === 4 || r.status === 5) + const eligibleRows = state.selectedRows.filter(r => r.status === 3 || r.status === 4 || r.status === 5 || r.status === 6) if (eligibleRows.length === 0) { ElMessage.warning('请先勾选状态为"生成中"或"生成失败"的记录') return diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/ReportDataService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/ReportDataService.cs index 1eeb904..6c9dec6 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/ReportDataService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/ReportDataService.cs @@ -42,8 +42,9 @@ public class ReportDataService : IReportDataService throw new InvalidOperationException("测评记录不存在"); } - // 2. 验证状态为4(已完成) - if (record.Status != 4) + // 2. 验证状态:允许状态 6(数据已就绪,PDF截图阶段)和 4(已完成) + // 状态 6 时结果数据已写入,PDF 生成服务需要截图渲染页面 + if (record.Status != 6 && record.Status != 4) { _logger.LogWarning("报告尚未生成完成,recordId: {RecordId}, 当前状态: {Status}", recordId, record.Status); throw new InvalidOperationException("报告尚未生成完成"); diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs index 837f4f1..96d60b0 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/ReportGenerationService.cs @@ -615,10 +615,11 @@ public class ReportGenerationService _logger.LogDebug("复制结论数据,recordId: {RecordId}, 数量: {Count}", recordId, newConclusions.Count); } - // 重新加载测评记录(带跟踪),保持 Status=3(生成中),等待 PDF 生成后再更新为4 + // 数据计算完成,状态更新为 6(数据已就绪),等待 PDF 生成后再更新为 4 var record = await _dbContext.AssessmentRecords .FirstAsync(r => r.Id == recordId); + record.Status = 6; record.UpdateTime = now; await _dbContext.SaveChangesAsync();