From 5f46609ecec5cef6a6374a22321b0bc86ef9fe35 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Tue, 7 Apr 2026 15:15:16 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=B5=8B=E8=AF=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AssessmentController.cs | 53 ++++++++++++- .../Interfaces/IAssessmentService.cs | 16 +++- .../Services/AssessmentService.cs | 79 ++++++++++++++++++- .../Models/Assessment/RetestRequest.cs | 12 +++ uniapp/api/assessment.js | 12 ++- uniapp/pages/assessment/history/index.vue | 37 ++++++--- uniapp/pages/assessment/info/index.vue | 44 ++++++++++- uniapp/pages/assessment/loading/index.vue | 49 +++++++++--- uniapp/pages/order/list/index.vue | 33 +++++--- 9 files changed, 295 insertions(+), 40 deletions(-) create mode 100644 server/MiAssessment/src/MiAssessment.Model/Models/Assessment/RetestRequest.cs diff --git a/server/MiAssessment/src/MiAssessment.Api/Controllers/AssessmentController.cs b/server/MiAssessment/src/MiAssessment.Api/Controllers/AssessmentController.cs index a0fa63c..b251a96 100644 --- a/server/MiAssessment/src/MiAssessment.Api/Controllers/AssessmentController.cs +++ b/server/MiAssessment/src/MiAssessment.Api/Controllers/AssessmentController.cs @@ -413,7 +413,7 @@ public class AssessmentController : ControllerBase /// /// GET /api/assessment/getPendingRecord?typeId=1 /// - /// 查询当前用户状态为待测评或测评中的最新测评记录,用于断点续答。 + /// 查询当前用户状态为待测评、测评中、生成失败或需重测的最新测评记录,用于断点续答和失败重测。 /// 需要用户登录认证。 /// /// 测评类型ID @@ -446,4 +446,55 @@ public class AssessmentController : ControllerBase return ApiResponse.Fail("查询进行中的测评记录失败"); } } + + /// + /// 重新测评(生成失败或需重测时免费重测) + /// + /// + /// POST /api/assessment/retest + /// + /// 将状态为生成失败(5)或需重测(7)的测评记录重置为待测评(1), + /// 清除旧的答案、结果和结论数据,允许用户免费重新答题。 + /// 需要用户登录认证。 + /// + /// 重测请求 + /// 重测结果 + [HttpPost("retest")] + [Authorize] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)] + public async Task> Retest([FromBody] RetestRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.RecordId <= 0) + { + return ApiResponse.Fail("测评记录ID无效"); + } + + var result = await _assessmentService.RetestAsync(userId.Value, request.RecordId); + return ApiResponse.Success(result); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogWarning(ex, "Unauthorized retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId); + return ApiResponse.Fail("无权限访问该测评记录", -1); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning(ex, "Invalid retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId); + return ApiResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId); + return ApiResponse.Fail("重新测评失败"); + } + } } diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAssessmentService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAssessmentService.cs index f639b64..0843428 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAssessmentService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAssessmentService.cs @@ -116,11 +116,23 @@ public interface IAssessmentService /// 获取用户进行中的测评记录 /// /// - /// 查询当前用户状态为待测评(1)或测评中(2)的最新测评记录。 - /// 用于页面加载时检测是否有未完成的测评,支持断点续答。 + /// 查询当前用户状态为待测评(1)、测评中(2)、生成失败(5)或需重测(7)的最新测评记录。 + /// 用于页面加载时检测是否有未完成的测评,支持断点续答和失败重测。 /// /// 当前用户ID /// 测评类型ID /// 进行中的测评记录,如果没有则返回null Task GetPendingRecordAsync(long userId, long typeId); + + /// + /// 重新测评(生成失败或需重测时免费重测) + /// + /// + /// 将状态为生成失败(5)或需重测(7)的测评记录重置为待测评(1), + /// 清除旧的答案、结果和结论数据,允许用户免费重新答题。 + /// + /// 当前用户ID + /// 测评记录ID + /// 重置是否成功 + Task RetestAsync(long userId, long recordId); } diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs index 5d90d33..bd585c1 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/AssessmentService.cs @@ -695,7 +695,7 @@ public class AssessmentService : IAssessmentService .AsNoTracking() .Where(r => r.UserId == userId && r.AssessmentTypeId == typeId - && (r.Status == 1 || r.Status == 2 || r.Status == 7) + && (r.Status == 1 || r.Status == 2 || r.Status == 5 || r.Status == 7) && !r.IsDeleted) .OrderByDescending(r => r.CreateTime) .Select(r => new PendingRecordDto @@ -724,4 +724,81 @@ public class AssessmentService : IAssessmentService return record; } + + /// + public async Task RetestAsync(long userId, long recordId) + { + _logger.LogInformation("重新测评请求,userId: {UserId}, recordId: {RecordId}", userId, recordId); + + var record = await _dbContext.AssessmentRecords + .Where(r => r.Id == recordId && !r.IsDeleted) + .FirstOrDefaultAsync(); + + if (record == null) + { + _logger.LogWarning("测评记录不存在,recordId: {RecordId}", recordId); + throw new InvalidOperationException("测评记录不存在"); + } + + if (record.UserId != userId) + { + _logger.LogWarning("测评记录不属于当前用户,userId: {UserId}, recordUserId: {RecordUserId}", userId, record.UserId); + throw new UnauthorizedAccessException("无权限访问该测评记录"); + } + + // 只有生成失败(5)和需重测(7)状态才允许重测 + if (record.Status != 5 && record.Status != 7) + { + _logger.LogWarning("测评记录状态不允许重测,status: {Status}, recordId: {RecordId}", record.Status, recordId); + throw new InvalidOperationException("当前测评状态不允许重新测评"); + } + + using var transaction = await _dbContext.Database.BeginTransactionAsync(); + try + { + // 清除旧答案 + var oldAnswers = await _dbContext.AssessmentAnswers + .Where(a => a.RecordId == recordId) + .ToListAsync(); + if (oldAnswers.Any()) + { + _dbContext.AssessmentAnswers.RemoveRange(oldAnswers); + } + + // 清除旧结果 + var oldResults = await _dbContext.AssessmentResults + .Where(r => r.RecordId == recordId) + .ToListAsync(); + if (oldResults.Any()) + { + _dbContext.AssessmentResults.RemoveRange(oldResults); + } + + // 清除旧结论 + var oldConclusions = await _dbContext.AssessmentRecordConclusions + .Where(c => c.RecordId == recordId) + .ToListAsync(); + if (oldConclusions.Any()) + { + _dbContext.AssessmentRecordConclusions.RemoveRange(oldConclusions); + } + + // 重置状态为待测评 + record.Status = 1; + record.SubmitTime = null; + record.UpdateTime = DateTime.Now; + + await _dbContext.SaveChangesAsync(); + await transaction.CommitAsync(); + + _logger.LogInformation("重新测评重置成功,recordId: {RecordId}, userId: {UserId}", recordId, userId); + return true; + } + catch (Exception ex) + { + await transaction.RollbackAsync(); + _logger.LogError(ex, "重新测评重置失败,recordId: {RecordId}", recordId); + throw; + } + } } diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/RetestRequest.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/RetestRequest.cs new file mode 100644 index 0000000..4f013ec --- /dev/null +++ b/server/MiAssessment/src/MiAssessment.Model/Models/Assessment/RetestRequest.cs @@ -0,0 +1,12 @@ +namespace MiAssessment.Model.Models.Assessment; + +/// +/// 重新测评请求 +/// +public class RetestRequest +{ + /// + /// 测评记录ID + /// + public long RecordId { get; set; } +} diff --git a/uniapp/api/assessment.js b/uniapp/api/assessment.js index 17f2af8..b31e757 100644 --- a/uniapp/api/assessment.js +++ b/uniapp/api/assessment.js @@ -89,6 +89,15 @@ export function getPendingRecord(typeId) { return get('/assessment/getPendingRecord', { typeId }) } +/** + * 重新测评(生成失败或需重测时免费重测) + * @param {number} recordId - 测评记录ID + * @returns {Promise} + */ +export function retestRecord(recordId) { + return post('/assessment/retest', { recordId }) +} + export default { getIntro, getQuestionList, @@ -98,5 +107,6 @@ export default { verifyInviteCode, getHistoryList, getScoreOptions, - getPendingRecord + getPendingRecord, + retestRecord } diff --git a/uniapp/pages/assessment/history/index.vue b/uniapp/pages/assessment/history/index.vue index aa77264..fd09ec7 100644 --- a/uniapp/pages/assessment/history/index.vue +++ b/uniapp/pages/assessment/history/index.vue @@ -7,7 +7,7 @@ import { ref, computed, onMounted } from 'vue' import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app' import { useUserStore } from '@/store/user.js' import { useAuth } from '@/composables/useAuth.js' -import { getHistoryList } from '@/api/assessment.js' +import { getHistoryList, retestRecord } from '@/api/assessment.js' import Empty from '@/components/Empty/index.vue' import Loading from '@/components/Loading/index.vue' @@ -158,23 +158,42 @@ function viewResult(record) { return } - // 需重测 - 跳转到测评首页重新开始 + // 需重测 - 调用重测接口后跳转答题页(免费重测) if (record.status === ASSESSMENT_STATUS.NEED_RETEST) { - uni.navigateTo({ - url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}` - }) + await handleRetestFromHistory(record) return } - // 生成失败 - 跳转到测评首页重新开始 + // 生成失败 - 调用重测接口后跳转答题页(免费重测) if (record.status === ASSESSMENT_STATUS.FAILED) { - uni.navigateTo({ - url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}` - }) + await handleRetestFromHistory(record) return } } +/** + * 从往期测评列表发起重测(免费) + */ +async function handleRetestFromHistory(record) { + try { + uni.showLoading({ title: '重置中...' }) + const res = await retestRecord(record.id) + uni.hideLoading() + + if (res && res.code === 0) { + uni.navigateTo({ + url: `/pages/assessment/questions/index?typeId=${record.assessmentTypeId || 1}&recordId=${record.id}` + }) + } else { + uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' }) + } + } catch (error) { + uni.hideLoading() + console.error('重测重置失败:', error) + uni.showToast({ title: '重置失败,请重试', icon: 'none' }) + } +} + /** * 页面显示时检查登录状态并加载数据 */ diff --git a/uniapp/pages/assessment/info/index.vue b/uniapp/pages/assessment/info/index.vue index 6dbd7c7..1d0788a 100644 --- a/uniapp/pages/assessment/info/index.vue +++ b/uniapp/pages/assessment/info/index.vue @@ -29,7 +29,8 @@ import { getIntro, verifyInviteCode, - getPendingRecord + getPendingRecord, + retestRecord } from '@/api/assessment.js' import { createOrder @@ -203,11 +204,26 @@ ) }) + /** + * 弹窗提示文案(根据记录状态区分) + */ + const pendingPopupMessage = computed(() => { + if (!pendingRecord.value) return '' + const s = pendingRecord.value.status + if (s === 5) return '您有一份生成失败的测评记录,可免费重新测评' + if (s === 7) return '您有一份需要重新测评的记录,可免费重新测评' + return '您有一份未完成的测评记录,是否继续?' + }) + /** * 底部按钮文案 */ const payBtnText = computed(() => { - if (isContinueMode.value) return '继续测评' + if (isContinueMode.value) { + const s = pendingRecord.value?.status + if (s === 5 || s === 7) return '免费重新测评' + return '继续测评' + } return `支付¥${introData.value.price || 20}元开始测评` }) @@ -329,8 +345,28 @@ // 继续测评模式:直接跳转到答题页 if (isContinueMode.value && pendingRecord.value) { + const r = pendingRecord.value + + // 生成失败(5)或需重测(7)的记录,先调重测接口重置状态 + if (r.status === 5 || r.status === 7) { + try { + uni.showLoading({ title: '重置中...' }) + const res = await retestRecord(r.recordId) + uni.hideLoading() + if (!res || res.code !== 0) { + uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' }) + return + } + } catch (error) { + uni.hideLoading() + console.error('重测重置失败:', error) + uni.showToast({ title: '重置失败,请重试', icon: 'none' }) + return + } + } + uni.redirectTo({ - url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${pendingRecord.value.recordId}` + url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${r.recordId}` }) return } @@ -613,7 +649,7 @@ - 您有一份未完成的测评记录,是否继续? + {{ pendingPopupMessage }} 姓名:{{ pendingRecord?.name }} diff --git a/uniapp/pages/assessment/loading/index.vue b/uniapp/pages/assessment/loading/index.vue index 8d2c005..04a06ba 100644 --- a/uniapp/pages/assessment/loading/index.vue +++ b/uniapp/pages/assessment/loading/index.vue @@ -13,7 +13,7 @@ import { ref, onMounted, onUnmounted } from 'vue' import { onLoad } from '@dcloudio/uni-app' import { useUserStore } from '@/store/user.js' -import { getResultStatus } from '@/api/assessment.js' +import { getResultStatus, retestRecord } from '@/api/assessment.js' import Navbar from '@/components/Navbar/index.vue' const userStore = useUserStore() @@ -125,19 +125,37 @@ async function checkStatus() { } } +// 重测加载状态 +const retestLoading = ref(false) + /** - * 重新测试 + * 重新测试(调用重测接口,免费重新答题) */ -function handleRetest() { - // 跳转到测评首页重新开始 - if (typeId.value) { - uni.redirectTo({ - url: `/pages/assessment/info/index?typeId=${typeId.value}` - }) - } else { - uni.switchTab({ - url: '/pages/index/index' +async function handleRetest() { + if (retestLoading.value) return + retestLoading.value = true + + try { + const res = await retestRecord(recordId.value) + if (res && res.code === 0) { + // 重置成功,直接跳转答题页 + uni.redirectTo({ + url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${recordId.value}` + }) + } else { + uni.showToast({ + title: res?.message || '重新测评失败,请稍后重试', + icon: 'none' + }) + } + } catch (error) { + console.error('重新测评失败:', error) + uni.showToast({ + title: '重新测评失败,请稍后重试', + icon: 'none' }) + } finally { + retestLoading.value = false } } @@ -191,8 +209,8 @@ onUnmounted(() => { - - 重新测试 + + {{ retestLoading ? '重置中...' : '重新测试' }} @@ -301,5 +319,10 @@ onUnmounted(() => { &:active { opacity: 0.85; } + + &.btn-loading { + opacity: 0.7; + pointer-events: none; + } } diff --git a/uniapp/pages/order/list/index.vue b/uniapp/pages/order/list/index.vue index f30702a..df25026 100644 --- a/uniapp/pages/order/list/index.vue +++ b/uniapp/pages/order/list/index.vue @@ -9,6 +9,7 @@ import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app' import { useUserStore } from '@/store/user.js' import { useAuth } from '@/composables/useAuth.js' import { getOrderList } from '@/api/order.js' +import { retestRecord } from '@/api/assessment.js' import Empty from '@/components/Empty/index.vue' import Loading from '@/components/Loading/index.vue' @@ -58,7 +59,7 @@ function getDisplayStatus(order) { // 已支付/已完成的订单,根据测评记录状态显示 if (assessmentStatus) { - const map = { 1: '待测评', 2: '测评中', 3: '测评生成中', 4: '已测评', 5: '生成失败', 7: '待测评' } + const map = { 1: '待测评', 2: '测评中', 3: '测评生成中', 4: '已测评', 5: '生成失败', 7: '需重测' } return map[assessmentStatus] || order.statusText || '已支付' } @@ -86,7 +87,7 @@ function getStatusClass(order) { const dst = getDisplayStatus(order) if (dst === '已测评' || dst === '待测评' || dst === '已完成') return 'status-green' if (dst === '测评生成中' || dst === '退款中' || dst === '已退款' || dst === '报告生成中') return 'status-red' - if (dst === '待联系' || dst === '联系中') return 'status-orange' + if (dst === '待联系' || dst === '联系中' || dst === '需重测' || dst === '生成失败') return 'status-orange' return 'status-gray' } @@ -171,13 +172,27 @@ function viewResult(order) { /** * 开始测评 */ -function startAssessment(order) { +async function startAssessment(order) { const dst = getDisplayStatus(order) - if (dst === '生成失败') { - // 生成失败:跳转到测评首页重新开始 - uni.navigateTo({ - url: `/pages/assessment/info/index?typeId=${order.productId || 1}` - }) + if (dst === '生成失败' || dst === '需重测') { + // 生成失败或需重测:调用重测接口后跳转答题页(免费重测) + try { + uni.showLoading({ title: '重置中...' }) + const res = await retestRecord(order.assessmentRecordId) + uni.hideLoading() + + if (res && res.code === 0) { + uni.navigateTo({ + url: `/pages/assessment/questions/index?typeId=${order.productId || 1}&recordId=${order.assessmentRecordId}` + }) + } else { + uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' }) + } + } catch (error) { + uni.hideLoading() + console.error('重测重置失败:', error) + uni.showToast({ title: '重置失败,请重试', icon: 'none' }) + } } else { uni.navigateTo({ url: `/pages/assessment/questions/index?recordId=${order.assessmentRecordId}` @@ -197,7 +212,7 @@ function showViewResultBtn(order) { */ function showStartBtn(order) { const dst = getDisplayStatus(order) - return dst === '待测评' || dst === '生成失败' + return dst === '待测评' || dst === '生成失败' || dst === '需重测' } onShow(() => {