21
This commit is contained in:
parent
d2a4f01e50
commit
28277c818e
|
|
@ -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, "更新结论失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 配置
|
||||
// =============================================
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -35,6 +35,11 @@ public class ReportCategoryItem
|
|||
/// </summary>
|
||||
public int StarLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结论记录ID(assessment_record_conclusions 表的 ID,用于编辑)
|
||||
/// </summary>
|
||||
public long? ConclusionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结论内容
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user