From 9548fd7435d5577be7376c56dbfb2612f45b594b Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Tue, 30 Jun 2026 16:45:08 +0800 Subject: [PATCH 1/2] feat: Add a progress bar for uploading documents to the general knowledge base --- .../items/upload/LocalFileUpload.vue | 12 +- .../views/document/upload/UploadComponent.vue | 162 ++++++++++++++++-- 2 files changed, 161 insertions(+), 13 deletions(-) diff --git a/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue b/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue index f1450b500c3..0aae0579459 100644 --- a/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue +++ b/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue @@ -157,6 +157,14 @@ const retryAll = () => { } // 上传on-change事件 const fileHandleChange = (file: any, fileList: UploadFiles) => { + // 按文件唯一标识精确定位并移除当前文件 + // 注意:不能使用 splice(-1, 1) 盲删末尾元素,文件夹上传时会误删正常文件而放走超限文件 + const removeCurrentFile = () => { + const index = fileList.findIndex((item: any) => item.uid === file.uid) + if (index !== -1) { + fileList.splice(index, 1) + } + } const item = reactive({ name: file.name, size: file.size, @@ -184,11 +192,13 @@ const fileHandleChange = (file: any, fileList: UploadFiles) => { if (file?.name !== '.DS_Store') { MsgError(t('views.document.upload.errorMessage2')) } + removeCurrentFile() return false } if (file?.size === 0) { MsgError(t('views.document.upload.errorMessage3')) + removeCurrentFile() return false } @@ -233,7 +243,7 @@ function deleteFile(index: any, item?: any) { if (item?.status === 'uploading' && typeof item.abort === 'function') { item.aborted = true item.abort() - }else if (item?.status === 'success' && item?.file_id) { + } else if (item?.status === 'success' && item?.file_id) { applicationApi.deleteFile(item.file_id) } fileArray.value.splice(index, 1) diff --git a/ui/src/views/document/upload/UploadComponent.vue b/ui/src/views/document/upload/UploadComponent.vue index d0ed124bff0..65bec83077f 100644 --- a/ui/src/views/document/upload/UploadComponent.vue +++ b/ui/src/views/document/upload/UploadComponent.vue @@ -48,7 +48,7 @@ class="w-full mb-4" drag multiple - v-model:file-list="form.fileList" + :file-list="form.fileList" action="#" :auto-upload="false" :show-file-list="false" @@ -106,7 +106,7 @@ class="w-full mb-4" drag multiple - v-model:file-list="form.fileList" + :file-list="form.fileList" action="#" :auto-upload="false" :show-file-list="false" @@ -152,7 +152,7 @@ class="w-full" drag multiple - v-model:file-list="form.fileList" + :file-list="form.fileList" action="#" :auto-upload="false" :show-file-list="false" @@ -184,24 +184,76 @@ - +
+ + {{ + $t('dynamicsForm.UploadInput.uploadStatus', { + success: successCount, + total: form.fileList.length, + }) + }} + + + + {{ $t('dynamicsForm.UploadInput.uploading') }} + + + + + {{ $t('dynamicsForm.UploadInput.failedStatus', { count: errorCount }) }} + + + + {{ $t('dynamicsForm.UploadInput.reUpload') }} + + +
+ @@ -213,6 +265,7 @@ import { useRoute } from 'vue-router' import type { UploadFiles } from 'element-plus' import { filesize, getImgUrl, isRightType } from '@/utils/common' import { MsgError } from '@/utils/message' +import applicationApi from '@/api/application/application' import { loadSharedApi } from '@/utils/dynamics-api/shared-api' import useStore from '@/stores' import { t } from '@/locales' @@ -237,6 +290,7 @@ const documentsType = computed(() => knowledge.documentsType) const FormRef = ref() const loading = ref(false) +const uploadLoading = ref(false) const form = ref({ fileType: 'txt', @@ -251,6 +305,21 @@ const rules = reactive({ const file_count_limit = ref(50) const file_size_limit = ref(100) +const successCount = computed( + () => form.value.fileList.filter((i: any) => i.status !== 'uploading').length, +) +const errorCount = computed( + () => form.value.fileList.filter((i: any) => i.status === 'error').length, +) +const uploadingCount = computed( + () => form.value.fileList.filter((i: any) => i.status === 'uploading').length, +) +const retryList = computed(() => + form.value.fileList.filter((i: any) => i.status === 'error' && i.canRetry), +) +const retryAll = () => { + retryList.value.forEach((i: any) => uploadFile(i)) +} watch(form.value, (value) => { knowledge.saveDocumentsType(value.fileType) @@ -272,10 +341,22 @@ function downloadTableTemplate(type: string) { } function radioChange() { + form.value.fileList.forEach((item: any) => { + if (item?.status === 'uploading' && typeof item.abort === 'function') { + item.aborted = true + item.abort() + } + }) form.value.fileList = [] } -function deleteFile(index: number | string) { +function deleteFile(index: number | string, item?: any) { + if (item?.status === 'uploading' && typeof item.abort === 'function') { + item.aborted = true + item.abort() + } else if (item?.status === 'success' && item?.file_id) { + applicationApi.deleteFile(item.file_id) + } form.value.fileList.splice(index, 1) } @@ -309,6 +390,55 @@ const fileHandleChange = (file: any, fileList: UploadFiles) => { removeCurrentFile() return false } + + const item = reactive({ + name: file.name, + size: file.size, + file_id: '', + source_file_id: '', + percentage: 0, + status: 'uploading' as 'uploading' | 'success' | 'error', + errMsg: '', + canRetry: false, + raw: file.raw, + abort: null as null | (() => void), + aborted: false, + }) + form.value.fileList.push(item) + uploadFile(item) +} + +const uploadFile = (item: any) => { + item.status = 'uploading' + item.percentage = 0 + item.errMsg = '' + item.canRetry = false + item.aborted = false + const res: any = applicationApi.postUploadFileProgress( + item.raw, + id as string, + 'KNOWLEDGE', + (percent: number) => { + item.percentage = percent + }, + uploadLoading, + ) + item.abort = typeof res?.abort === 'function' ? res.abort : null + const request: Promise = res?.then ? res : res?.request + request + .then((ok: any) => { + const split_path = ok.data.split('/') + item.file_id = split_path[split_path.length - 1] + item.source_file_id = item.file_id + item.percentage = 100 + item.status = 'success' + }) + .catch(() => { + if (item.aborted) return + item.status = 'error' + item.errMsg = t('dynamicsForm.UploadInput.errorTip.networkError') + item.canRetry = true + }) } const onExceed = () => { @@ -334,6 +464,14 @@ const handlePreview = (bool: boolean) => { */ function validate() { if (!FormRef.value) return + if (uploadingCount.value) { + MsgError(t('dynamicsForm.UploadInput.uploading')) + return false + } + if (errorCount.value) { + MsgError(t('dynamicsForm.UploadInput.failedStatus', { count: errorCount.value })) + return false + } return FormRef.value.validate((valid: any) => { return valid }) From 273275b2ad6ec1284c44961ff068660b70f9a3b8 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Tue, 30 Jun 2026 17:52:37 +0800 Subject: [PATCH 2/2] fix: file limit bug --- .../items/upload/LocalFileUpload.vue | 36 ++++++-- .../views/document/upload/UploadComponent.vue | 82 ++++++++++++------- .../component/action/DataSource.vue | 5 +- 3 files changed, 87 insertions(+), 36 deletions(-) diff --git a/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue b/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue index 0aae0579459..e8135597d14 100644 --- a/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue +++ b/ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue @@ -6,7 +6,7 @@ class="w-full" drag multiple - v-bind:file-list="modelValue" + v-bind:file-list="fileArray" action="#" :auto-upload="false" :show-file-list="false" @@ -62,7 +62,7 @@ -