Skip to content

Commit 9548fd7

Browse files
feat: Add a progress bar for uploading documents to the general knowledge base
1 parent e27533a commit 9548fd7

2 files changed

Lines changed: 161 additions & 13 deletions

File tree

ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ const retryAll = () => {
157157
}
158158
// 上传on-change事件
159159
const fileHandleChange = (file: any, fileList: UploadFiles) => {
160+
// 按文件唯一标识精确定位并移除当前文件
161+
// 注意:不能使用 splice(-1, 1) 盲删末尾元素,文件夹上传时会误删正常文件而放走超限文件
162+
const removeCurrentFile = () => {
163+
const index = fileList.findIndex((item: any) => item.uid === file.uid)
164+
if (index !== -1) {
165+
fileList.splice(index, 1)
166+
}
167+
}
160168
const item = reactive({
161169
name: file.name,
162170
size: file.size,
@@ -184,11 +192,13 @@ const fileHandleChange = (file: any, fileList: UploadFiles) => {
184192
if (file?.name !== '.DS_Store') {
185193
MsgError(t('views.document.upload.errorMessage2'))
186194
}
195+
removeCurrentFile()
187196
return false
188197
}
189198
190199
if (file?.size === 0) {
191200
MsgError(t('views.document.upload.errorMessage3'))
201+
removeCurrentFile()
192202
return false
193203
}
194204
@@ -233,7 +243,7 @@ function deleteFile(index: any, item?: any) {
233243
if (item?.status === 'uploading' && typeof item.abort === 'function') {
234244
item.aborted = true
235245
item.abort()
236-
}else if (item?.status === 'success' && item?.file_id) {
246+
} else if (item?.status === 'success' && item?.file_id) {
237247
applicationApi.deleteFile(item.file_id)
238248
}
239249
fileArray.value.splice(index, 1)

ui/src/views/document/upload/UploadComponent.vue

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
class="w-full mb-4"
4949
drag
5050
multiple
51-
v-model:file-list="form.fileList"
51+
:file-list="form.fileList"
5252
action="#"
5353
:auto-upload="false"
5454
:show-file-list="false"
@@ -106,7 +106,7 @@
106106
class="w-full mb-4"
107107
drag
108108
multiple
109-
v-model:file-list="form.fileList"
109+
:file-list="form.fileList"
110110
action="#"
111111
:auto-upload="false"
112112
:show-file-list="false"
@@ -152,7 +152,7 @@
152152
class="w-full"
153153
drag
154154
multiple
155-
v-model:file-list="form.fileList"
155+
:file-list="form.fileList"
156156
action="#"
157157
:auto-upload="false"
158158
:show-file-list="false"
@@ -184,24 +184,76 @@
184184
</el-upload>
185185
</el-form-item>
186186
</el-form>
187-
<el-row :gutter="8" v-if="form.fileList?.length">
187+
<div v-if="form.fileList?.length" class="flex-between w-full mt-16">
188+
<span>
189+
{{
190+
$t('dynamicsForm.UploadInput.uploadStatus', {
191+
success: successCount,
192+
total: form.fileList.length,
193+
})
194+
}}
195+
</span>
196+
<span v-if="uploadingCount" class="flex align-center">
197+
<el-icon class="is-loading color-primary" size="18"><Loading /></el-icon>
198+
<span class="ml-4">{{ $t('dynamicsForm.UploadInput.uploading') }}</span>
199+
</span>
200+
<span v-else-if="errorCount" class="flex align-center">
201+
<el-icon class="color-danger ml-4" size="18"><WarningFilled /></el-icon>
202+
<span class="ml-4">
203+
{{ $t('dynamicsForm.UploadInput.failedStatus', { count: errorCount }) }}
204+
</span>
205+
<el-button v-if="retryList.length" text @click="retryAll">
206+
<AppIcon iconName="app-refresh"></AppIcon>
207+
{{ $t('dynamicsForm.UploadInput.reUpload') }}
208+
</el-button>
209+
</span>
210+
</div>
211+
<el-row :gutter="8" v-if="form.fileList?.length" class="mt-8">
188212
<template v-for="(item, index) in form.fileList" :key="index">
189213
<el-col :span="12" class="mb-8">
190-
<el-card shadow="never" style="--el-card-padding: 8px 12px; line-height: normal">
214+
<el-card
215+
shadow="never"
216+
style="
217+
--el-card-padding: 8px 12px;
218+
line-height: normal;
219+
position: relative;
220+
overflow: hidden;
221+
"
222+
:class="item.status === 'error' ? 'border-danger' : ''"
223+
>
191224
<div class="flex-between">
192225
<div class="flex">
193226
<img :src="getImgUrl(item && item?.name)" alt="" width="40" />
194227
<div class="ml-8">
195228
<p class="ellipsis-1" :title="item && item?.name">{{ item && item?.name }}</p>
196-
<el-text type="info" size="small">{{
197-
filesize(item && item?.size) || '0K'
198-
}}</el-text>
229+
<el-text type="info" size="small">
230+
<template v-if="item.status === 'uploading'">
231+
{{ filesize((item.size * item.percentage) / 100) }} /
232+
{{ filesize(item.size) || '0K' }}
233+
</template>
234+
<template v-else>{{ filesize(item && item?.size) || '0K' }}</template>
235+
</el-text>
236+
<el-text class="ml-8" v-if="item.status === 'error'" type="danger" size="small">
237+
{{ item.errMsg }}
238+
</el-text>
199239
</div>
200240
</div>
201-
<el-button text @click="deleteFile(index)">
202-
<AppIcon iconName="app-delete"></AppIcon>
203-
</el-button>
241+
<div class="flex align-center">
242+
<el-button v-if="item.canRetry" text @click="uploadFile(item)">
243+
<AppIcon iconName="app-refresh"></AppIcon>
244+
</el-button>
245+
<el-button text @click="deleteFile(index, item)">
246+
<AppIcon iconName="app-delete"></AppIcon>
247+
</el-button>
248+
</div>
204249
</div>
250+
<el-progress
251+
v-if="item.status === 'uploading'"
252+
class="card-progress"
253+
:percentage="item.percentage"
254+
:stroke-width="4"
255+
:show-text="false"
256+
/>
205257
</el-card>
206258
</el-col>
207259
</template>
@@ -213,6 +265,7 @@ import { useRoute } from 'vue-router'
213265
import type { UploadFiles } from 'element-plus'
214266
import { filesize, getImgUrl, isRightType } from '@/utils/common'
215267
import { MsgError } from '@/utils/message'
268+
import applicationApi from '@/api/application/application'
216269
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
217270
import useStore from '@/stores'
218271
import { t } from '@/locales'
@@ -237,6 +290,7 @@ const documentsType = computed(() => knowledge.documentsType)
237290
238291
const FormRef = ref()
239292
const loading = ref(false)
293+
const uploadLoading = ref(false)
240294
241295
const form = ref({
242296
fileType: 'txt',
@@ -251,6 +305,21 @@ const rules = reactive({
251305
252306
const file_count_limit = ref(50)
253307
const file_size_limit = ref(100)
308+
const successCount = computed(
309+
() => form.value.fileList.filter((i: any) => i.status !== 'uploading').length,
310+
)
311+
const errorCount = computed(
312+
() => form.value.fileList.filter((i: any) => i.status === 'error').length,
313+
)
314+
const uploadingCount = computed(
315+
() => form.value.fileList.filter((i: any) => i.status === 'uploading').length,
316+
)
317+
const retryList = computed(() =>
318+
form.value.fileList.filter((i: any) => i.status === 'error' && i.canRetry),
319+
)
320+
const retryAll = () => {
321+
retryList.value.forEach((i: any) => uploadFile(i))
322+
}
254323
255324
watch(form.value, (value) => {
256325
knowledge.saveDocumentsType(value.fileType)
@@ -272,10 +341,22 @@ function downloadTableTemplate(type: string) {
272341
}
273342
274343
function radioChange() {
344+
form.value.fileList.forEach((item: any) => {
345+
if (item?.status === 'uploading' && typeof item.abort === 'function') {
346+
item.aborted = true
347+
item.abort()
348+
}
349+
})
275350
form.value.fileList = []
276351
}
277352
278-
function deleteFile(index: number | string) {
353+
function deleteFile(index: number | string, item?: any) {
354+
if (item?.status === 'uploading' && typeof item.abort === 'function') {
355+
item.aborted = true
356+
item.abort()
357+
} else if (item?.status === 'success' && item?.file_id) {
358+
applicationApi.deleteFile(item.file_id)
359+
}
279360
form.value.fileList.splice(index, 1)
280361
}
281362
@@ -309,6 +390,55 @@ const fileHandleChange = (file: any, fileList: UploadFiles) => {
309390
removeCurrentFile()
310391
return false
311392
}
393+
394+
const item = reactive({
395+
name: file.name,
396+
size: file.size,
397+
file_id: '',
398+
source_file_id: '',
399+
percentage: 0,
400+
status: 'uploading' as 'uploading' | 'success' | 'error',
401+
errMsg: '',
402+
canRetry: false,
403+
raw: file.raw,
404+
abort: null as null | (() => void),
405+
aborted: false,
406+
})
407+
form.value.fileList.push(item)
408+
uploadFile(item)
409+
}
410+
411+
const uploadFile = (item: any) => {
412+
item.status = 'uploading'
413+
item.percentage = 0
414+
item.errMsg = ''
415+
item.canRetry = false
416+
item.aborted = false
417+
const res: any = applicationApi.postUploadFileProgress(
418+
item.raw,
419+
id as string,
420+
'KNOWLEDGE',
421+
(percent: number) => {
422+
item.percentage = percent
423+
},
424+
uploadLoading,
425+
)
426+
item.abort = typeof res?.abort === 'function' ? res.abort : null
427+
const request: Promise<any> = res?.then ? res : res?.request
428+
request
429+
.then((ok: any) => {
430+
const split_path = ok.data.split('/')
431+
item.file_id = split_path[split_path.length - 1]
432+
item.source_file_id = item.file_id
433+
item.percentage = 100
434+
item.status = 'success'
435+
})
436+
.catch(() => {
437+
if (item.aborted) return
438+
item.status = 'error'
439+
item.errMsg = t('dynamicsForm.UploadInput.errorTip.networkError')
440+
item.canRetry = true
441+
})
312442
}
313443
314444
const onExceed = () => {
@@ -334,6 +464,14 @@ const handlePreview = (bool: boolean) => {
334464
*/
335465
function validate() {
336466
if (!FormRef.value) return
467+
if (uploadingCount.value) {
468+
MsgError(t('dynamicsForm.UploadInput.uploading'))
469+
return false
470+
}
471+
if (errorCount.value) {
472+
MsgError(t('dynamicsForm.UploadInput.failedStatus', { count: errorCount.value }))
473+
return false
474+
}
337475
return FormRef.value.validate((valid: any) => {
338476
return valid
339477
})

0 commit comments

Comments
 (0)