From 6498f1049a47d078ab2a551cf716ea2f4aaaab20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Wed, 2 Sep 2026 16:47:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=AF=BC=E5=85=A5=E7=8A=B6=E6=80=81=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langs/en-US/documentCollection.json | 1 + .../langs/zh-CN/documentCollection.json | 1 + .../DocumentImportBatchStatus.test.ts | 65 +++++++++++++++- .../DocumentImportBatchStatus.vue | 74 ++++++++++++++++++- 4 files changed, 138 insertions(+), 3 deletions(-) diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json index b3cabae6..8f47aa25 100644 --- a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json @@ -116,6 +116,7 @@ "pendingCount": "Pending", "skippedCount": "Skipped", "continueBatch": "Continue", + "dismissBatchStatus": "Dismiss automatic import status", "batchStatusLoadFailed": "Unable to load the automatic import status. Retrying automatically.", "interruptFallback": "Automatic import was interrupted. Resume after the service recovers.", "interruptCode": "Error code", diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json index 4457b60e..538d2ed0 100644 --- a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json @@ -116,6 +116,7 @@ "pendingCount": "等待", "skippedCount": "跳过", "continueBatch": "继续", + "dismissBatchStatus": "关闭自动导入状态提示", "batchStatusLoadFailed": "自动导入状态暂时无法获取,将自动重试", "interruptFallback": "自动导入发生异常,批次已中断,请确认服务恢复后继续", "interruptCode": "错误标识", diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.test.ts b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.test.ts index a3c28fdd..f33eef97 100644 --- a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.test.ts +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.test.ts @@ -1,6 +1,6 @@ import { flushPromises, mount } from '@vue/test-utils'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import DocumentImportBatchStatus from './DocumentImportBatchStatus.vue'; @@ -31,6 +31,10 @@ function createBatch(status: 'COMPLETED' | 'RUNNING', completedCount: number) { } describe('documentImportBatchStatus', () => { + beforeEach(() => { + localStorage.clear(); + }); + afterEach(() => { vi.clearAllMocks(); vi.useRealTimers(); @@ -77,6 +81,65 @@ describe('documentImportBatchStatus', () => { wrapper.unmount(); }); + it('关闭当前批次提示后停止轮询且不影响导入接口', async () => { + vi.useFakeTimers(); + apiMocks.get.mockResolvedValue({ + data: createBatch('RUNNING', 1), + errorCode: 0, + }); + + const wrapper = mount(DocumentImportBatchStatus, { + props: { knowledgeId: 'knowledge-1' }, + }); + await flushPromises(); + + const closeButton = wrapper.find('.batch-status__close'); + expect(closeButton.attributes('aria-label')).toBe( + 'documentCollection.importDoc.dismissBatchStatus', + ); + await closeButton.trigger('click'); + + expect(wrapper.find('.batch-status').exists()).toBe(false); + await vi.advanceTimersByTimeAsync(30_000); + expect(apiMocks.get).toHaveBeenCalledTimes(1); + expect(apiMocks.post).not.toHaveBeenCalled(); + wrapper.unmount(); + }); + + it('当前批次关闭状态在本地保留且新批次仍会展示', async () => { + apiMocks.get + .mockResolvedValueOnce({ + data: createBatch('RUNNING', 1), + errorCode: 0, + }) + .mockResolvedValueOnce({ + data: createBatch('RUNNING', 1), + errorCode: 0, + }) + .mockResolvedValueOnce({ + data: { ...createBatch('RUNNING', 0), batchId: 'batch-2' }, + errorCode: 0, + }); + + const firstWrapper = mount(DocumentImportBatchStatus, { + props: { knowledgeId: 'knowledge-1' }, + }); + await flushPromises(); + await firstWrapper.find('.batch-status__close').trigger('click'); + firstWrapper.unmount(); + + const secondWrapper = mount(DocumentImportBatchStatus, { + props: { knowledgeId: 'knowledge-1' }, + }); + await flushPromises(); + expect(secondWrapper.find('.batch-status').exists()).toBe(false); + + await secondWrapper.setProps({ refreshKey: 1 }); + await flushPromises(); + expect(secondWrapper.find('.batch-status').exists()).toBe(true); + secondWrapper.unmount(); + }); + it('失败批次关联任务实际完成后不再展示提示', async () => { apiMocks.get.mockResolvedValue({ data: { diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.vue index 647e2b46..6ca2d10e 100644 --- a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.vue +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentImportBatchStatus.vue @@ -3,6 +3,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { $t } from '@easyflow/locales'; +import { Close } from '@element-plus/icons-vue'; import { ElAlert, ElButton, ElProgress } from 'element-plus'; import { api } from '#/api/request'; @@ -48,6 +49,7 @@ const props = defineProps({ const emit = defineEmits(['continued']); const batch = ref(); const continuing = ref(false); +const dismissedBatchId = ref(''); const loadError = ref(''); const refreshing = ref(false); let pollTimer: null | ReturnType = null; @@ -55,6 +57,14 @@ let disposed = false; let refreshGeneration = 0; let pollDelayMs = 3000; const TERMINAL_RECHECK_DELAY_MS = 15_000; +const DISMISSED_BATCH_STORAGE_PREFIX = + 'easyflow.documentImport.dismissedBatch.'; + +const batchDismissed = computed( + () => + Boolean(batch.value?.batchId) && + batch.value?.batchId === dismissedBatchId.value, +); const canContinue = computed( () => @@ -124,6 +134,35 @@ const interruptMetadata = computed(() => { return details.join(' · '); }); +function dismissalStorageKey(knowledgeId: string) { + return `${DISMISSED_BATCH_STORAGE_PREFIX}${knowledgeId}`; +} + +function readDismissedBatchId(knowledgeId: string) { + if (!knowledgeId || typeof window === 'undefined') { + return ''; + } + try { + return ( + window.localStorage.getItem(dismissalStorageKey(knowledgeId))?.trim() || + '' + ); + } catch { + return ''; + } +} + +function persistDismissedBatchId(knowledgeId: string, batchId: string) { + if (!knowledgeId || !batchId || typeof window === 'undefined') { + return; + } + try { + window.localStorage.setItem(dismissalStorageKey(knowledgeId), batchId); + } catch { + // 浏览器禁用存储时,关闭操作在当前组件生命周期内仍然有效。 + } +} + async function refresh() { if (!props.knowledgeId) return; const currentGeneration = ++refreshGeneration; @@ -173,6 +212,9 @@ function schedulePoll() { pollTimer = setTimeout(refresh, retryDelay); return; } + if (batchDismissed.value) { + return; + } if ( !batch.value || batch.value.status === 'COMPLETED' || @@ -188,6 +230,18 @@ function schedulePoll() { pollTimer = setTimeout(refresh, delay); } +function dismissBatchStatus() { + if (!batch.value?.batchId) { + return; + } + dismissedBatchId.value = batch.value.batchId; + persistDismissedBatchId(props.knowledgeId, batch.value.batchId); + loadError.value = ''; + refreshGeneration += 1; + if (pollTimer) clearTimeout(pollTimer); + pollTimer = null; +} + async function continueBatch() { if (!batch.value || continuing.value) return; continuing.value = true; @@ -208,6 +262,7 @@ async function continueBatch() { onMounted(() => { disposed = false; + dismissedBatchId.value = readDismissedBatchId(props.knowledgeId); refresh(); }); @@ -223,6 +278,7 @@ watch( const knowledgeChanged = knowledgeId !== previousKnowledgeId; if (knowledgeChanged) { batch.value = undefined; + dismissedBatchId.value = readDismissedBatchId(knowledgeId); loadError.value = ''; pollDelayMs = 3000; } @@ -233,7 +289,7 @@ watch(