diff --git a/bms-front/src/views/ar/bill/index.vue b/bms-front/src/views/ar/bill/index.vue index 1a129f47..566bbdce 100644 --- a/bms-front/src/views/ar/bill/index.vue +++ b/bms-front/src/views/ar/bill/index.vue @@ -3,7 +3,7 @@
+ :disabled="!canCancelActiveGenerateTask" @click="cancelActiveGenerateTask"> @@ -50,6 +50,16 @@ + +

账单正在后台生成,确定取消当前任务吗?

+ +
@@ -83,13 +93,27 @@ const generateTasks = reactive(new Map()) const activeGenerateTasks = computed(() => Array.from(generateTasks.values())) const activeGenerateTask = computed(() => activeGenerateTasks.value[0]) const generateStatusVisible = computed(() => activeGenerateTasks.value.length > 0) +const cancelConfirmVisible = ref(false) +const cancelConfirmLoading = ref(false) +const cancelConfirmTaskId = ref() +const cancelConfirmTask = computed(() => cancelConfirmTaskId.value ? generateTasks.get(cancelConfirmTaskId.value) : null) +const canCancelActiveGenerateTask = computed(() => ['WAITING', 'RUNNING'].includes(activeGenerateTask.value?.status)) +const cancelConfirmDisabled = computed(() => { + return cancelConfirmLoading.value || !['WAITING', 'RUNNING'].includes(cancelConfirmTask.value?.status) +}) const generateStatusTitle = computed(() => { const status = activeGenerateTask.value?.status if (status === 'WAITING') return '账单排队中' + if (status === 'SUCCESS') return '账单生成完成' + if (status === 'FAILED') return '账单生成失败' + if (status === 'CANCELED') return '账单已取消' if (status === 'CANCEL_REQUESTED') return '账单取消中' return '账单生成中' }) const generateStatusTooltip = computed(() => { + if (['SUCCESS', 'FAILED', 'CANCELED'].includes(activeGenerateTask.value?.status)) { + return '任务状态已更新' + } return activeGenerateTask.value?.status === 'CANCEL_REQUESTED' ? '正在清理已生成的账单数据,请稍候' : '点击取消当前账单生成任务' @@ -107,6 +131,8 @@ const generateProgressDegree = computed(() => { const generateStatusSummary = computed(() => { const task = activeGenerateTask.value if (!task) return '任务提交中' + if (task.status === 'FAILED') return '任务执行失败' + if (task.status === 'CANCELED') return '任务已取消' if (task.status === 'CANCEL_REQUESTED') return '正在清理数据' if (task.status === 'WAITING') return '等待后台处理' if (Number(task.totalCount) > 0) return `${Number(task.processedCount || 0)} / ${Number(task.totalCount)} 条` @@ -164,6 +190,7 @@ function startGeneratePolling(taskId) { function finishGenerateTask(taskId, task) { stopGeneratePolling(taskId) + closeCancelConfirm(taskId) if (generateFinishTimers.has(taskId)) return // 即使后台很快返回 SUCCESS,也延迟清理浮层,保证用户能看到结果反馈。 const visibleSince = generateVisibleSince.get(taskId) || Date.now() @@ -200,12 +227,50 @@ function stopGeneratePolling(taskId) { function cancelActiveGenerateTask() { const task = activeGenerateTask.value - if (!task?.taskId) return - proxy.$modal.confirm('账单正在后台生成,确定取消当前任务吗?').then(() => cancelArBillGenerateTask(task.taskId)).then(res => { + if (!task?.taskId || !canCancelActiveGenerateTask.value) return + cancelConfirmTaskId.value = task.taskId + cancelConfirmLoading.value = false + cancelConfirmVisible.value = true +} + +async function confirmCancelActiveGenerateTask() { + const taskId = cancelConfirmTaskId.value + const task = taskId ? generateTasks.get(taskId) : null + if (!taskId || !['WAITING', 'RUNNING'].includes(task?.status)) { + cancelConfirmVisible.value = false + return + } + cancelConfirmLoading.value = true + try { + const res = await cancelArBillGenerateTask(taskId) const updated = res.data - if (updated) generateTasks.set(updated.taskId, updated) - proxy.$modal.msgSuccess('已提交取消请求') - }).catch(() => {}) + let cancellationSubmitted = true + if (updated?.taskId) { + generateTasks.set(updated.taskId, updated) + if (['SUCCESS', 'FAILED', 'CANCELED'].includes(updated.status)) { + finishGenerateTask(updated.taskId, updated) + cancellationSubmitted = false + } + } + cancelConfirmVisible.value = false + if (cancellationSubmitted) proxy.$modal.msgSuccess('已提交取消请求') + } catch (error) { + // 请求失败时保留弹窗,允许用户重新确认;全局请求拦截器负责展示错误信息。 + } finally { + cancelConfirmLoading.value = false + } +} + +function closeCancelConfirm(taskId) { + if (cancelConfirmTaskId.value !== taskId) return + cancelConfirmVisible.value = false + cancelConfirmLoading.value = false + cancelConfirmTaskId.value = undefined +} + +function handleCancelConfirmClosed() { + cancelConfirmLoading.value = false + cancelConfirmTaskId.value = undefined } function loadActiveGenerateTasks() { @@ -390,6 +455,11 @@ function statusType(value) { return AR_BILL_STATUS_TYPES[value] || 'info' } .ar-bill-page .amount { color: #f56c6c; font-weight: 600; } .ar-bill-page .written { color: #67c23a; } .ar-bill-page .unwritten { color: #e6a23c; } +.ar-bill-page .cancel-generate-message { + margin: 0; + color: var(--el-text-color-primary); + line-height: 1.7; +} .ar-bill-page .generate-status-float { position: fixed;