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>
public DbSet<AssessmentResult> AssessmentResults { get; set; } = null!;
/// <summary>
/// 测评记录结论表
/// </summary>
public DbSet<AssessmentRecordConclusion> AssessmentRecordConclusions { get; set; } = null!;
#endregion
#region
@ -374,6 +379,16 @@ public class AdminBusinessDbContext : DbContext
.HasColumnType("decimal(18,2)");
});
// =============================================
// AssessmentRecordConclusion 配置
// =============================================
modelBuilder.Entity<AssessmentRecordConclusion>(entity =>
{
// Content 使用 nvarchar(max)
entity.Property(e => e.Content)
.HasColumnType("nvarchar(max)");
});
// =============================================
// 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>
public int StarLevel { get; set; }
/// <summary>
/// 结论记录IDassessment_record_conclusions 表的 ID用于编辑
/// </summary>
public long? ConclusionId { get; set; }
/// <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,21 +269,49 @@ public class AssessmentRecordService : IAssessmentRecordService
.ToDictionaryAsync(c => c.Id, c => c);
// 查询结论根据分类ID和星级匹配
// StarLevel (1-5) 映射到 ConclusionType (1-4):
// StarLevel 5 → ConclusionType 1 (最强)
// StarLevel 4 → ConclusionType 2 (较强)
// StarLevel 3 → ConclusionType 2 (较强)
// StarLevel 2 → ConclusionType 3 (较弱)
// StarLevel 1 → ConclusionType 4 (最弱)
var conclusions = await _dbContext.ReportConclusions
// 优先从 assessment_record_conclusions 读取记录级别结论
var recordConclusions = await _dbContext.AssessmentRecordConclusions
.AsNoTracking()
.Where(c => categoryIds.Contains(c.CategoryId) && !c.IsDeleted)
.Where(c => c.RecordId == id && !c.IsDeleted)
.ToListAsync();
// 构建结论字典key 为 (CategoryId, ConclusionType)
var conclusionDict = conclusions
.GroupBy(c => (c.CategoryId, c.ConclusionType))
.ToDictionary(g => g.Key, g => g.First().Content);
// 构建结论字典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
.AsNoTracking()
.Where(c => categoryIds.Contains(c.CategoryId) && !c.IsDeleted)
.ToListAsync();
// 需要根据星级匹配结论类型
conclusionDict = new Dictionary<long, (long?, string?)>();
var conclusionsByKey = conclusions
.GroupBy(c => (c.CategoryId, c.ConclusionType))
.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
@ -309,8 +337,7 @@ public class AssessmentRecordService : IAssessmentRecordService
Items = g.Select(r =>
{
var category = categories.FirstOrDefault(c => c.Id == r.CategoryId);
var conclusionType = MapStarLevelToConclusionType(r.StarLevel);
conclusionDict.TryGetValue((r.CategoryId, conclusionType), out var conclusionContent);
conclusionDict.TryGetValue(r.CategoryId, out var conclusionData);
return new ReportCategoryItem
{
@ -320,7 +347,8 @@ public class AssessmentRecordService : IAssessmentRecordService
MaxScore = r.MaxScore,
Percentage = r.Percentage,
StarLevel = r.StarLevel,
ConclusionContent = conclusionContent
ConclusionId = conclusionData.conclusionId,
ConclusionContent = conclusionData.content
};
}).ToList()
})
@ -614,6 +642,24 @@ public class AssessmentRecordService : IAssessmentRecordService
_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
/// <summary>

View File

@ -54,4 +54,10 @@ public interface IAssessmentRecordService
/// </summary>
/// <param name="id">记录ID</param>
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
percentage: number
starLevel: number
conclusionId: number | null
conclusionContent: string | null
}
@ -167,3 +168,18 @@ export function deleteRecord(id: number): Promise<ApiResponse<null>> {
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 />
</template>
</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>
</div>
</template>
</div>
</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>
</template>
@ -294,8 +334,8 @@
* 测评记录管理页面
* @description 查看用户测评记录答案详情和测评报告支持搜索导出
*/
import { reactive, ref, onMounted } from 'vue'
import { Search, Refresh, View, Download, Document, RefreshRight, Delete } from '@element-plus/icons-vue'
import { reactive, ref, computed, onMounted } from 'vue'
import { Search, Refresh, View, Download, Document, RefreshRight, Delete, Edit } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getRecordList,
@ -305,6 +345,7 @@ import {
regenerateReport,
batchRegenerateReport,
deleteRecord,
updateRecordConclusion,
type AssessmentRecordItem,
type AssessmentRecordDetail,
type AssessmentReport,
@ -361,6 +402,8 @@ interface RecordPageState {
scoreOptionMap: Map<number, ScoreOptionItem>
batchRegenerateLoading: boolean
selectedRows: AssessmentRecordItem[]
editingConclusion: { conclusionId: number; content: string; categoryName: string } | null
editConclusionLoading: boolean
}
// ============ Refs ============
@ -391,7 +434,17 @@ const state = reactive<RecordPageState>({
exportLoading: false,
scoreOptionMap: new Map(),
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 ============
@ -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 ============
onMounted(() => {
@ -804,4 +898,15 @@ onMounted(() => {
:deep(.el-descriptions) {
--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>