This commit is contained in:
zpc 2026-02-25 18:20:23 +08:00
parent d2a4f01e50
commit 28277c818e
9 changed files with 345 additions and 19 deletions

View File

@ -213,4 +213,38 @@ public class AssessmentRecordController : BusinessControllerBase
} }
} }
/// <summary>
/// 更新测评记录结论
/// </summary>
/// <param name="request">更新请求</param>
/// <returns>操作结果</returns>
[HttpPost("updateConclusion")]
[BusinessPermission(BusinessPermissions.AssessmentRecord.View)]
public async Task<IActionResult> UpdateConclusion([FromBody] UpdateRecordConclusionRequest request)
{
if (request.Id <= 0)
{
return ValidationError("结论ID无效");
}
if (string.IsNullOrWhiteSpace(request.Content))
{
return ValidationError("结论内容不能为空");
}
try
{
await _assessmentRecordService.UpdateRecordConclusionAsync(request);
return Ok();
}
catch (BusinessException ex)
{
return Error(ex.Code, ex.Message);
}
catch (Exception)
{
return Error(ErrorCodes.SystemError, "更新结论失败");
}
}
} }

View File

@ -88,6 +88,11 @@ public class AdminBusinessDbContext : DbContext
/// </summary> /// </summary>
public DbSet<AssessmentResult> AssessmentResults { get; set; } = null!; public DbSet<AssessmentResult> AssessmentResults { get; set; } = null!;
/// <summary>
/// 测评记录结论表
/// </summary>
public DbSet<AssessmentRecordConclusion> AssessmentRecordConclusions { get; set; } = null!;
#endregion #endregion
#region #region
@ -374,6 +379,16 @@ public class AdminBusinessDbContext : DbContext
.HasColumnType("decimal(18,2)"); .HasColumnType("decimal(18,2)");
}); });
// =============================================
// AssessmentRecordConclusion 配置
// =============================================
modelBuilder.Entity<AssessmentRecordConclusion>(entity =>
{
// Content 使用 nvarchar(max)
entity.Property(e => e.Content)
.HasColumnType("nvarchar(max)");
});
// ============================================= // =============================================
// BusinessPage 配置 // BusinessPage 配置
// ============================================= // =============================================

View File

@ -0,0 +1,78 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MiAssessment.Admin.Business.Entities;
/// <summary>
/// 测评记录结论表
/// </summary>
[Table("assessment_record_conclusions")]
public class AssessmentRecordConclusion
{
/// <summary>
/// 主键ID
/// </summary>
[Key]
public long Id { get; set; }
/// <summary>
/// 测评记录ID
/// </summary>
public long RecordId { get; set; }
/// <summary>
/// 分类ID
/// </summary>
public long CategoryId { get; set; }
/// <summary>
/// 结论类型1最强 2较强 3较弱 4最弱
/// </summary>
public int ConclusionType { get; set; }
/// <summary>
/// 星级1-5记录级别的星级可由管理员覆盖
/// </summary>
public int StarLevel { get; set; }
/// <summary>
/// 结论标题
/// </summary>
[MaxLength(100)]
public string? Title { get; set; }
/// <summary>
/// 结论内容(富文本)
/// </summary>
[Required]
[Column(TypeName = "nvarchar(max)")]
public string Content { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 软删除标记
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 关联的测评记录
/// </summary>
[ForeignKey(nameof(RecordId))]
public virtual AssessmentRecord? Record { get; set; }
/// <summary>
/// 关联的报告分类
/// </summary>
[ForeignKey(nameof(CategoryId))]
public virtual ReportCategory? Category { get; set; }
}

View File

@ -35,6 +35,11 @@ public class ReportCategoryItem
/// </summary> /// </summary>
public int StarLevel { get; set; } public int StarLevel { get; set; }
/// <summary>
/// 结论记录IDassessment_record_conclusions 表的 ID用于编辑
/// </summary>
public long? ConclusionId { get; set; }
/// <summary> /// <summary>
/// 结论内容 /// 结论内容
/// </summary> /// </summary>

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace MiAssessment.Admin.Business.Models.AssessmentRecord;
/// <summary>
/// 更新测评记录结论请求
/// </summary>
public class UpdateRecordConclusionRequest
{
/// <summary>
/// 结论记录ID
/// </summary>
[Required]
public long Id { get; set; }
/// <summary>
/// 结论内容
/// </summary>
[Required]
public string Content { get; set; } = null!;
}

View File

@ -269,22 +269,50 @@ public class AssessmentRecordService : IAssessmentRecordService
.ToDictionaryAsync(c => c.Id, c => c); .ToDictionaryAsync(c => c.Id, c => c);
// 查询结论根据分类ID和星级匹配 // 查询结论根据分类ID和星级匹配
// StarLevel (1-5) 映射到 ConclusionType (1-4): // 优先从 assessment_record_conclusions 读取记录级别结论
// StarLevel 5 → ConclusionType 1 (最强) var recordConclusions = await _dbContext.AssessmentRecordConclusions
// StarLevel 4 → ConclusionType 2 (较强) .AsNoTracking()
// StarLevel 3 → ConclusionType 2 (较强) .Where(c => c.RecordId == id && !c.IsDeleted)
// StarLevel 2 → ConclusionType 3 (较弱) .ToListAsync();
// StarLevel 1 → ConclusionType 4 (最弱)
// 构建结论字典key 为 CategoryId
// 如果有记录级别结论,使用记录级别的;否则回退到模板结论
Dictionary<long, (long? conclusionId, string? content)> conclusionDict;
if (recordConclusions.Count > 0)
{
// 使用记录级别结论
conclusionDict = recordConclusions
.GroupBy(c => c.CategoryId)
.ToDictionary(
g => g.Key,
g => ((long?)g.First().Id, (string?)g.First().Content));
}
else
{
// 回退到模板结论
var conclusions = await _dbContext.ReportConclusions var conclusions = await _dbContext.ReportConclusions
.AsNoTracking() .AsNoTracking()
.Where(c => categoryIds.Contains(c.CategoryId) && !c.IsDeleted) .Where(c => categoryIds.Contains(c.CategoryId) && !c.IsDeleted)
.ToListAsync(); .ToListAsync();
// 构建结论字典key 为 (CategoryId, ConclusionType) // 需要根据星级匹配结论类型
var conclusionDict = conclusions conclusionDict = new Dictionary<long, (long?, string?)>();
var conclusionsByKey = conclusions
.GroupBy(c => (c.CategoryId, c.ConclusionType)) .GroupBy(c => (c.CategoryId, c.ConclusionType))
.ToDictionary(g => g.Key, g => g.First().Content); .ToDictionary(g => g.Key, g => g.First().Content);
// 为每个结果匹配模板结论
foreach (var result in results)
{
var conclusionType = MapStarLevelToConclusionType(result.StarLevel);
if (conclusionsByKey.TryGetValue((result.CategoryId, conclusionType), out var content))
{
conclusionDict[result.CategoryId] = (null, content);
}
}
}
// 按分类类型分组结果 // 按分类类型分组结果
var resultGroups = results var resultGroups = results
.Where(r => r.Category != null) .Where(r => r.Category != null)
@ -309,8 +337,7 @@ public class AssessmentRecordService : IAssessmentRecordService
Items = g.Select(r => Items = g.Select(r =>
{ {
var category = categories.FirstOrDefault(c => c.Id == r.CategoryId); var category = categories.FirstOrDefault(c => c.Id == r.CategoryId);
var conclusionType = MapStarLevelToConclusionType(r.StarLevel); conclusionDict.TryGetValue(r.CategoryId, out var conclusionData);
conclusionDict.TryGetValue((r.CategoryId, conclusionType), out var conclusionContent);
return new ReportCategoryItem return new ReportCategoryItem
{ {
@ -320,7 +347,8 @@ public class AssessmentRecordService : IAssessmentRecordService
MaxScore = r.MaxScore, MaxScore = r.MaxScore,
Percentage = r.Percentage, Percentage = r.Percentage,
StarLevel = r.StarLevel, StarLevel = r.StarLevel,
ConclusionContent = conclusionContent ConclusionId = conclusionData.conclusionId,
ConclusionContent = conclusionData.content
}; };
}).ToList() }).ToList()
}) })
@ -614,6 +642,24 @@ public class AssessmentRecordService : IAssessmentRecordService
_logger.LogInformation("测评记录已删除ID: {RecordId}", id); _logger.LogInformation("测评记录已删除ID: {RecordId}", id);
} }
/// <inheritdoc />
public async Task UpdateRecordConclusionAsync(UpdateRecordConclusionRequest request)
{
var conclusion = await _dbContext.AssessmentRecordConclusions
.FirstOrDefaultAsync(c => c.Id == request.Id && !c.IsDeleted);
if (conclusion == null)
{
throw new BusinessException(ErrorCodes.ConclusionNotFound, "结论记录不存在");
}
conclusion.Content = request.Content;
conclusion.UpdateTime = DateTime.Now;
await _dbContext.SaveChangesAsync();
_logger.LogInformation("更新测评记录结论成功结论ID: {ConclusionId}, 记录ID: {RecordId}", request.Id, conclusion.RecordId);
}
#region #region
/// <summary> /// <summary>

View File

@ -54,4 +54,10 @@ public interface IAssessmentRecordService
/// </summary> /// </summary>
/// <param name="id">记录ID</param> /// <param name="id">记录ID</param>
Task DeleteRecordAsync(long id); Task DeleteRecordAsync(long id);
/// <summary>
/// 更新测评记录结论
/// </summary>
/// <param name="request">更新请求</param>
Task UpdateRecordConclusionAsync(UpdateRecordConclusionRequest request);
} }

View File

@ -73,6 +73,7 @@ export interface ReportCategoryItem {
maxScore: number maxScore: number
percentage: number percentage: number
starLevel: number starLevel: number
conclusionId: number | null
conclusionContent: string | null conclusionContent: string | null
} }
@ -167,3 +168,18 @@ export function deleteRecord(id: number): Promise<ApiResponse<null>> {
data: { id } data: { id }
}) })
} }
/** 更新记录结论请求 */
export interface UpdateRecordConclusionRequest {
id: number
content: string
}
/** 更新测评记录结论 */
export function updateRecordConclusion(data: UpdateRecordConclusionRequest): Promise<ApiResponse<null>> {
return request<null>({
url: '/admin/assessmentRecord/updateConclusion',
method: 'post',
data
})
}

View File

@ -280,12 +280,52 @@
<el-rate v-model="row.starLevel" disabled /> <el-rate v-model="row.starLevel" disabled />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="conclusionContent" label="结论" min-width="200" show-overflow-tooltip /> <el-table-column label="结论" min-width="300">
<template #default="{ row }">
<div class="conclusion-cell">
<span class="conclusion-text">{{ row.conclusionContent || '暂无结论' }}</span>
<el-button
v-if="row.conclusionId"
type="primary"
link
size="small"
@click="handleEditConclusion(row)"
>
<el-icon><Edit /></el-icon>
编辑
</el-button>
</div>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</template> </template>
</div> </div>
</el-drawer> </el-drawer>
<!-- 编辑结论对话框 -->
<el-dialog
v-model="editConclusionDialogVisible"
:title="`编辑结论 - ${state.editingConclusion?.categoryName || ''}`"
width="700px"
:close-on-click-modal="false"
@close="handleCancelEditConclusion"
>
<template v-if="state.editingConclusion">
<el-input
v-model="state.editingConclusion.content"
type="textarea"
:rows="10"
placeholder="请输入结论内容"
/>
</template>
<template #footer>
<el-button @click="handleCancelEditConclusion">取消</el-button>
<el-button type="primary" :loading="state.editConclusionLoading" @click="handleSaveConclusion">
保存
</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
@ -294,8 +334,8 @@
* 测评记录管理页面 * 测评记录管理页面
* @description 查看用户测评记录答案详情和测评报告支持搜索导出 * @description 查看用户测评记录答案详情和测评报告支持搜索导出
*/ */
import { reactive, ref, onMounted } from 'vue' import { reactive, ref, computed, onMounted } from 'vue'
import { Search, Refresh, View, Download, Document, RefreshRight, Delete } from '@element-plus/icons-vue' import { Search, Refresh, View, Download, Document, RefreshRight, Delete, Edit } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
getRecordList, getRecordList,
@ -305,6 +345,7 @@ import {
regenerateReport, regenerateReport,
batchRegenerateReport, batchRegenerateReport,
deleteRecord, deleteRecord,
updateRecordConclusion,
type AssessmentRecordItem, type AssessmentRecordItem,
type AssessmentRecordDetail, type AssessmentRecordDetail,
type AssessmentReport, type AssessmentReport,
@ -361,6 +402,8 @@ interface RecordPageState {
scoreOptionMap: Map<number, ScoreOptionItem> scoreOptionMap: Map<number, ScoreOptionItem>
batchRegenerateLoading: boolean batchRegenerateLoading: boolean
selectedRows: AssessmentRecordItem[] selectedRows: AssessmentRecordItem[]
editingConclusion: { conclusionId: number; content: string; categoryName: string } | null
editConclusionLoading: boolean
} }
// ============ Refs ============ // ============ Refs ============
@ -391,7 +434,17 @@ const state = reactive<RecordPageState>({
exportLoading: false, exportLoading: false,
scoreOptionMap: new Map(), scoreOptionMap: new Map(),
batchRegenerateLoading: false, batchRegenerateLoading: false,
selectedRows: [] selectedRows: [],
editingConclusion: null,
editConclusionLoading: false
})
/** 编辑结论对话框可见性 */
const editConclusionDialogVisible = computed({
get: () => state.editingConclusion !== null,
set: (val: boolean) => {
if (!val) state.editingConclusion = null
}
}) })
// ============ Helper Functions ============ // ============ Helper Functions ============
@ -703,6 +756,47 @@ async function handleExport() {
} }
} }
/** 开始编辑结论 */
function handleEditConclusion(item: any) {
state.editingConclusion = {
conclusionId: item.conclusionId,
content: item.conclusionContent || '',
categoryName: item.categoryName
}
}
/** 保存结论编辑 */
async function handleSaveConclusion() {
if (!state.editingConclusion) return
state.editConclusionLoading = true
try {
const res = await updateRecordConclusion({
id: state.editingConclusion.conclusionId,
content: state.editingConclusion.content
})
if (res.code === 0) {
ElMessage.success('结论更新成功')
state.editingConclusion = null
//
if (state.report) {
loadRecordReport(state.report.id)
}
} else {
throw new Error(res.message || '更新失败')
}
} catch (error) {
const message = error instanceof Error ? error.message : '更新结论失败'
ElMessage.error(message)
} finally {
state.editConclusionLoading = false
}
}
/** 取消编辑结论 */
function handleCancelEditConclusion() {
state.editingConclusion = null
}
// ============ Lifecycle ============ // ============ Lifecycle ============
onMounted(() => { onMounted(() => {
@ -804,4 +898,15 @@ onMounted(() => {
:deep(.el-descriptions) { :deep(.el-descriptions) {
--el-descriptions-item-bordered-label-background: var(--bg-light, #f5f7fa); --el-descriptions-item-bordered-label-background: var(--bg-light, #f5f7fa);
} }
.conclusion-cell {
display: flex;
align-items: flex-start;
gap: 8px;
}
.conclusion-text {
flex: 1;
word-break: break-all;
}
</style> </style>