diff --git a/packages/backend/src/sftp/sftp.controller.ts b/packages/backend/src/sftp/sftp.controller.ts index e3d54c8..f466962 100644 --- a/packages/backend/src/sftp/sftp.controller.ts +++ b/packages/backend/src/sftp/sftp.controller.ts @@ -242,7 +242,7 @@ export const downloadDirectory = async (req: Request, res: Response): Promise { // +++ 传递 signal +++ console.error(`[TransfersService] Error processing task ${taskId} in background:`, error); - // 如果不是因为中止操作导致的错误,则更新状态 + // 如果不是因为终止操作导致的错误,则更新状态 if (error.name !== 'AbortError') { this.updateOverallTaskStatus(taskId, 'failed', `Background processing error: ${error.message}`); } @@ -75,7 +75,7 @@ export class TransfersService { const abortController = this.taskAbortControllers.get(taskId); if (abortController) { console.info(`[TransfersService] Cancelling task ${taskId}.`); - abortController.abort(); // 触发中止信号 + abortController.abort(); // 触发终止信号 // 更新主任务状态 // 假设 'cancelling' 和 'cancelled' 是有效的状态 diff --git a/packages/frontend/src/components/TransferProgressModal.vue b/packages/frontend/src/components/TransferProgressModal.vue index fe1668e..0118ddf 100644 --- a/packages/frontend/src/components/TransferProgressModal.vue +++ b/packages/frontend/src/components/TransferProgressModal.vue @@ -94,7 +94,28 @@ const fetchTransferTasks = async () => { // 假设后端API路径为 /api/v1/transfers/status,且返回数据结构为 { data: TransferTask[] } // 请根据实际API调整这里的类型和数据访问 const response = await apiClient.get<{ data: TransferTask[] }>('/transfers/status'); - transferTasks.value = Array.isArray(response.data.data) ? response.data.data : (Array.isArray(response.data) ? response.data : []); + const rawTasks = Array.isArray(response.data.data) ? response.data.data : (Array.isArray(response.data) ? response.data : []); + transferTasks.value = rawTasks.map(task => { + // 优先信任后端已经是 'cancelled' 或其他最终状态 + if (['completed', 'failed', 'cancelled', 'partially-completed'].includes(task.status)) { + return task; + } + // 对于仍在进行中或正在取消中的任务 + if (['in-progress', 'cancelling', 'queued', 'connecting', 'transferring'].includes(task.status)) { + // 如果它有子任务,并且所有子任务都已是 'cancelled' + if (task.subTasks && task.subTasks.length > 0 && task.subTasks.every((st: TransferSubTask) => st.status === 'cancelled')) { + // 则认为主任务也应该被标记为 'cancelled' + // 这有助于处理后端主任务状态更新延迟或遗漏的情况 + return { ...task, status: 'cancelled' as TransferTask['status'] }; + } + // 如果任务状态是 'cancelling' 但它没有子任务 (或子任务列表为空) + // 这种情况也应视为已取消 + else if (task.status === 'cancelling' && (!task.subTasks || task.subTasks.length === 0)) { + return { ...task, status: 'cancelled' as TransferTask['status'] }; + } + } + return task; + }); } catch (error: any) { console.error("Failed to fetch transfer tasks:", error); errorLoading.value = error.response?.data?.message || error.message || t('transferProgressModal.error.unknown', '未知错误'); @@ -193,7 +214,7 @@ const isTaskCancelling = (taskStatus: TransferTask['status']): boolean => { const handleCancelTask = async (taskId: string) => { // 可以在这里添加一个确认对话框 - // const confirmed = window.confirm(t('transferProgressModal.confirmCancel', '您确定要中止此传输任务吗?')); + // const confirmed = window.confirm(t('transferProgressModal.confirmCancel', '您确定要终止此传输任务吗?')); // if (!confirmed) return; try { @@ -207,13 +228,20 @@ const handleCancelTask = async (taskId: string) => { await apiClient.post(`/transfers/cancel/${taskId}`); // 可以添加成功提示 - // uiNotificationsStore.showSuccess(t('transferProgressModal.cancelRequested', '已发送中止请求。')); + // uiNotificationsStore.showSuccess(t('transferProgressModal.cancelRequested', '已发送终止请求。')); + + // 前端优化:立即将任务状态设置为 'cancelling' 以提供即时反馈 + // 这样用户点击后能马上看到状态变为“终止中”,后续轮询会从后端获取权威状态。 + const taskBeingCancelled = transferTasks.value.find(t => t.taskId === taskId); + if (taskBeingCancelled && ['queued', 'in-progress', 'connecting', 'transferring'].includes(taskBeingCancelled.status)) { + taskBeingCancelled.status = 'cancelling'; + } // 立即刷新一次列表,或者等待下一次轮询 fetchTransferTasks(); } catch (error: any) { console.error(`Failed to cancel task ${taskId}:`, error); - // uiNotificationsStore.showError(error.response?.data?.message || error.message || t('transferProgressModal.error.cancelFailed', '中止任务失败。')); + // uiNotificationsStore.showError(error.response?.data?.message || error.message || t('transferProgressModal.error.cancelFailed', '终止任务失败。')); // 如果任务状态之前被临时修改,可能需要回滚 } }; @@ -273,10 +301,10 @@ const handleCancelTask = async (taskId: string) => { @click="handleCancelTask(task.taskId)" :disabled="isTaskCancelling(task.status)" class="px-2 py-0.5 text-xs bg-red-500 hover:bg-red-600 text-white rounded-md focus:outline-none focus:ring-2 focus:ring-red-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" - :title="isTaskCancelling(task.status) ? t('transferProgressModal.cancellingTooltip', '中止中...') : t('transferProgressModal.cancelTaskTooltip', '中止任务')" + :title="isTaskCancelling(task.status) ? t('transferProgressModal.cancellingTooltip', '终止中...') : t('transferProgressModal.cancelTaskTooltip', '终止任务')" > - {{ isTaskCancelling(task.status) ? t('transferProgressModal.cancellingButton', '中止中') : t('transferProgressModal.cancelButton', '中止') }} + {{ isTaskCancelling(task.status) ? t('transferProgressModal.cancellingButton', '终止中') : t('transferProgressModal.cancelButton', '终止') }} diff --git a/packages/frontend/src/locales/ja-JP.json b/packages/frontend/src/locales/ja-JP.json index 866f16d..eeb5465 100644 --- a/packages/frontend/src/locales/ja-JP.json +++ b/packages/frontend/src/locales/ja-JP.json @@ -1393,7 +1393,7 @@ "errorLoading": "転送タスクの読み込みに失敗しました:{error}", "error": { "unknown": "不明なエラー", - "cancelFailed": "タスクの中止に失敗しました。" + "cancelFailed": "タスクの终止に失敗しました。" }, "noTasks": "現在アクティブな転送タスクはありません。", "task": { @@ -1409,15 +1409,15 @@ "partiallyCompleted": "一部完了", "connecting": "接続中", "transferring": "転送中", - "cancelling": "中止中", - "cancelled": "中止済み" + "cancelling": "终止中", + "cancelled": "终止済み" }, - "confirmCancel": "この転送タスクを中止してもよろしいですか?", - "cancelRequested": "中止リクエストを送信しました。", - "cancelTaskTooltip": "タスクを中止", - "cancellingTooltip": "中止中...", - "cancelButton": "中止", - "cancellingButton": "中止中", + "confirmCancel": "この転送タスクを终止してもよろしいですか?", + "cancelRequested": "终止リクエストを送信しました。", + "cancelTaskTooltip": "タスクを终止", + "cancellingTooltip": "终止中...", + "cancelButton": "终止", + "cancellingButton": "终止中", "subTasks": { "titleToggle": "{count} 個のサブタスクを表示", "noSubTasks": "サブタスクはありません。" diff --git a/packages/frontend/src/locales/zh-CN.json b/packages/frontend/src/locales/zh-CN.json index 68c171f..f9235eb 100644 --- a/packages/frontend/src/locales/zh-CN.json +++ b/packages/frontend/src/locales/zh-CN.json @@ -1405,7 +1405,7 @@ "errorLoading": "加载传输任务失败:{error}", "error": { "unknown": "未知错误", - "cancelFailed": "中止任务失败。" + "cancelFailed": "终止任务失败。" }, "noTasks": "当前没有活动的传输任务。", "task": { @@ -1421,15 +1421,15 @@ "partiallyCompleted": "部分完成", "connecting": "连接中", "transferring": "传输中", - "cancelling": "中止中", - "cancelled": "已中止" + "cancelling": "正在终止...", + "cancelled": "已终止" }, - "confirmCancel": "您确定要中止此传输任务吗?", - "cancelRequested": "已发送中止请求。", - "cancelTaskTooltip": "中止任务", - "cancellingTooltip": "中止中...", - "cancelButton": "中止", - "cancellingButton": "中止中", + "confirmCancel": "您确定要终止此传输任务吗?", + "cancelRequested": "已发送终止请求。", + "cancelTaskTooltip": "终止任务", + "cancellingTooltip": "正在终止......", + "cancelButton": "终止", + "cancellingButton": "正在终止...", "subTasks": { "titleToggle": "查看 {count} 个子任务", "noSubTasks": "没有子任务。" diff --git a/packages/frontend/src/stores/session/actions/sessionActions.ts b/packages/frontend/src/stores/session/actions/sessionActions.ts index 13ef9e9..407e40c 100644 --- a/packages/frontend/src/stores/session/actions/sessionActions.ts +++ b/packages/frontend/src/stores/session/actions/sessionActions.ts @@ -130,7 +130,7 @@ export const openNewSession = ( if (sessionToUpdate) { if (sessionToUpdate.connectionId !== backendCID) { - console.warn(`[SessionActions/ssh:connected] 后端CID ${backendCID} 与会话 ${originalFrontendSessionIdForHandler} 的期望CID ${sessionToUpdate.connectionId} 不匹配。中止SID更新。`); + console.warn(`[SessionActions/ssh:connected] 后端CID ${backendCID} 与会话 ${originalFrontendSessionIdForHandler} 的期望CID ${sessionToUpdate.connectionId} 不匹配。终止SID更新。`); return; }