This commit is contained in:
zpc 2026-02-25 11:20:05 +08:00
parent 8b6fe85774
commit 138caae4ad
7 changed files with 116 additions and 9 deletions

View File

@ -257,6 +257,11 @@ public static class BusinessPermissions
/// 导出测评记录
/// </summary>
public const string Export = "assessmentRecord:export";
/// <summary>
/// 删除测评记录
/// </summary>
public const string Delete = "assessmentRecord:delete";
}
#endregion
@ -344,6 +349,7 @@ public static class BusinessPermissions
// 测评记录管理模块
new PermissionDefinition(AssessmentRecord.View, "查看测评记录", AssessmentRecord.Module, "查看测评记录列表和详情"),
new PermissionDefinition(AssessmentRecord.Export, "导出测评记录", AssessmentRecord.Module, "导出测评记录为Excel文件"),
new PermissionDefinition(AssessmentRecord.Delete, "删除测评记录", AssessmentRecord.Module, "删除测评记录"),
// 业务介绍页管理模块
new PermissionDefinition(BusinessPage.View, "查看业务介绍页", BusinessPage.Module, "查看业务介绍页列表和详情"),

View File

@ -184,4 +184,33 @@ public class AssessmentRecordController : BusinessControllerBase
}
}
/// <summary>
/// 删除测评记录
/// </summary>
/// <param name="request">删除请求</param>
/// <returns>操作结果</returns>
[HttpPost("delete")]
[BusinessPermission(BusinessPermissions.AssessmentRecord.Delete)]
public async Task<IActionResult> Delete([FromBody] RegenerateReportRequest request)
{
if (request.Id <= 0)
{
return ValidationError("测评记录ID无效");
}
try
{
await _assessmentRecordService.DeleteRecordAsync(request.Id);
return Ok();
}
catch (BusinessException ex)
{
return Error(ex.Code, ex.Message);
}
catch (Exception)
{
return Error(ErrorCodes.SystemError, "删除测评记录失败");
}
}
}

View File

@ -595,6 +595,25 @@ public class AssessmentRecordService : IAssessmentRecordService
};
}
/// <inheritdoc />
public async Task DeleteRecordAsync(long id)
{
var record = await _dbContext.AssessmentRecords
.FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted);
if (record == null)
{
throw new BusinessException(ErrorCodes.AssessmentRecordNotFound, "测评记录不存在");
}
// 软删除
record.IsDeleted = true;
record.UpdateTime = DateTime.Now;
await _dbContext.SaveChangesAsync();
_logger.LogInformation("测评记录已删除ID: {RecordId}", id);
}
#region
/// <summary>

View File

@ -48,4 +48,10 @@ public interface IAssessmentRecordService
/// <param name="recordIds">测评记录ID列表</param>
/// <returns>批量操作结果</returns>
Task<BatchRegenerateResult> BatchRegenerateReportAsync(List<long> recordIds);
/// <summary>
/// 删除测评记录(软删除)
/// </summary>
/// <param name="id">记录ID</param>
Task DeleteRecordAsync(long id);
}

View File

@ -158,3 +158,12 @@ export function batchRegenerateReport(ids: number[]): Promise<ApiResponse<BatchR
data: { ids }
})
}
/** 删除测评记录 */
export function deleteRecord(id: number): Promise<ApiResponse<null>> {
return request<null>({
url: '/admin/assessmentRecord/delete',
method: 'post',
data: { id }
})
}

View File

@ -108,7 +108,7 @@
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
<el-table-column label="操作" width="230" fixed="right" align="center">
<el-table-column label="操作" width="280" fixed="right" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleViewDetail(row)">
<el-icon><View /></el-icon>
@ -134,6 +134,15 @@
<el-icon><RefreshRight /></el-icon>
重新生成
</el-button>
<el-button
type="danger"
link
size="small"
@click="handleDelete(row)"
>
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
@ -286,7 +295,7 @@
* @description 查看用户测评记录答案详情和测评报告支持搜索导出
*/
import { reactive, ref, onMounted } from 'vue'
import { Search, Refresh, View, Download, Document, RefreshRight } from '@element-plus/icons-vue'
import { Search, Refresh, View, Download, Document, RefreshRight, Delete } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getRecordList,
@ -295,6 +304,7 @@ import {
exportRecords,
regenerateReport,
batchRegenerateReport,
deleteRecord,
type AssessmentRecordItem,
type AssessmentRecordDetail,
type AssessmentReport,
@ -629,6 +639,28 @@ async function handleBatchRegenerate() {
}
}
/** 删除测评记录 */
async function handleDelete(row: AssessmentRecordItem) {
try {
await ElMessageBox.confirm(
`确定要删除记录 ID=${row.id} 吗?删除后不可恢复。`,
'删除测评记录',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
)
const res = await deleteRecord(row.id)
if (res.code === 0) {
ElMessage.success('删除成功')
loadRecordList()
} else {
throw new Error(res.message || '删除失败')
}
} catch (error) {
if (error === 'cancel' || (error as any)?.toString?.().includes('cancel')) return
const message = error instanceof Error ? error.message : '删除失败'
ElMessage.error(message)
}
}
async function handleExport() {
state.exportLoading = true
try {

View File

@ -120,14 +120,20 @@ public class RedisService : IRedisService, IDisposable
if (_database == null || !_isConnected)
return null;
// 使用 BRPOP 命令实现阻塞弹出
var result = await _database.ExecuteAsync("BRPOP", key, (int)timeout.TotalSeconds);
if (result.IsNull)
return null;
// 使用非阻塞 RPOP + 轮询代替 BRPOP避免 StackExchange.Redis 命令超时
// BRPOP 的阻塞时间会超过客户端的 syncTimeout/asyncTimeout 导致 RedisTimeoutException
var deadline = DateTime.UtcNow.Add(timeout);
while (DateTime.UtcNow < deadline)
{
var value = await _database.ListRightPopAsync(key);
if (!value.IsNullOrEmpty)
return value.ToString();
// BRPOP 返回数组:[key, value]
var results = (RedisResult[])result!;
return results[1].ToString();
// 空队列时等待 1 秒后重试,避免频繁轮询
await Task.Delay(1000);
}
return null;
}
/// <inheritdoc />