This commit is contained in:
zpc 2026-03-18 01:13:44 +08:00
parent 2c7693c4e3
commit c683a28d98
5 changed files with 204 additions and 13 deletions

View File

@ -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, "数据已就绪" }`<br>2. `RegenerateReportAsync`:状态检查增加 6允许 3、4、5、6 重新生成)<br>3. `BatchRegenerateReportAsync`:同上 |
| `MiAssessment.Admin.Business/Services/AssessmentRecordService.cs` | `GetRecordReportAsync` 方法:状态检查从 `== 4` 改为 `== 6 \|\| == 4` |
### 3.3 前端 - 后台管理
| 文件 | 修改内容 |
|------|----------|
| `admin-web/src/views/business/assessment/record/index.vue` | 1. 搜索表单状态下拉:新增选项 `<el-option label="数据已就绪" :value="6" />`<br>2. "报告"按钮:`v-if` 从 `row.status === 4` 改为 `row.status === 6 \|\| row.status === 4`<br>3. "网页报告"按钮:`v-if` 从 `row.status === 4` 改为 `row.status === 6 \|\| row.status === 4`<br>4. "查看PDF"按钮:保持 `v-if="row.reportUrl"` 不变(状态 6 时 reportUrl 为空,自然不显示)<br>5. "下载PDF"按钮:保持 `v-if="row.reportUrl"` 不变<br>6. "重新生成"按钮:`v-if` 增加 `row.status === 6`<br>7. `getStatusTagType` 函数:新增 `case 6: return 'primary'`<br>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 → 3ReportUrl 清空,重新走完整流程 |
| 后台搜索状态筛选 | 下拉框包含"数据已就绪"选项,可正确筛选 |
| 批量重新生成 | 状态 3、4、5、6 的记录均可被选中 |
## 七、关于 PDF 生成失败的状态处理
当前方案PDF 生成失败时状态从 6 改为 5生成失败
这意味着用户在 PDF 生成失败后也无法查看网页报告了(因为状态 5 不允许)。
**备选方案**PDF 生成失败时状态保持 6只是 reportUrl 为空。这样用户至少还能看网页报告。但这样的话"生成失败"状态就只表示数据计算失败了。
**建议采用当前方案**(失败改为 5理由
- 状态 5 明确告诉管理员"这条记录有问题需要处理"
- 管理员可以点击"重新生成"重试
- 如果保持 6管理员可能不知道 PDF 生成失败了
如果你觉得 PDF 失败后还应该能看网页报告,我可以调整方案。

View File

@ -32,7 +32,8 @@ public class AssessmentRecordService : IAssessmentRecordService
{ 2, "测评中" },
{ 3, "生成中" },
{ 4, "已完成" },
{ 5, "生成失败" }
{ 5, "生成失败" },
{ 6, "数据已就绪" }
};
/// <summary>
@ -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++;

View File

@ -38,6 +38,7 @@
<el-option label="生成中" :value="3" />
<el-option label="已完成" :value="4" />
<el-option label="生成失败" :value="5" />
<el-option label="数据已就绪" :value="6" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
@ -115,7 +116,7 @@
详情
</el-button>
<el-button
v-if="row.status === 4"
v-if="row.status === 6 || row.status === 4"
type="success"
link
size="small"
@ -125,7 +126,7 @@
报告
</el-button>
<el-button
v-if="row.status === 4"
v-if="row.status === 6 || row.status === 4"
type="primary"
link
size="small"
@ -155,7 +156,7 @@
下载PDF
</el-button>
<el-button
v-if="row.status === 3 || row.status === 4 || row.status === 5"
v-if="row.status === 3 || row.status === 4 || row.status === 5 || row.status === 6"
type="warning"
link
size="small"
@ -491,6 +492,7 @@ function getStatusTagType(status: number): 'info' | 'primary' | 'warning' | 'suc
case 3: return 'warning'
case 4: return 'success'
case 5: return 'danger'
case 6: return 'primary'
default: return 'info'
}
}
@ -738,7 +740,7 @@ async function handleRegenerate(row: AssessmentRecordItem) {
/** 批量重新生成报告 */
async function handleBatchRegenerate() {
const eligibleRows = state.selectedRows.filter(r => 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

View File

@ -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("报告尚未生成完成");

View File

@ -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();