Skip to content

Commit 29bbae8

Browse files
committed
feat: support downloading form-submitted files in chat logs
1 parent 8aae8b8 commit 29bbae8

1 file changed

Lines changed: 109 additions & 2 deletions

File tree

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

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
>
1313
<el-button type="primary">{{ $t('aiChat.uploadFile.label') }}</el-button>
1414
</el-upload>
15-
<el-space wrap class="w-full media-file-width upload_content mt-16">
15+
<el-space wrap class="w-full media-file-width upload_content mt-16" v-if="!inputDisabled">
1616
<template v-for="(file, index) in model_value" :key="index">
1717
<el-card style="--el-card-padding: 0" shadow="never">
1818
<div
@@ -37,12 +37,73 @@
3737
</el-card>
3838
</template>
3939
</el-space>
40+
<div class="mt-8 w-full" v-else>
41+
<div class="mb-8" v-if="download_list.length">
42+
<el-space wrap class="w-full media-file-width upload_content">
43+
<template v-for="(item, index) in download_list" :key="index">
44+
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
45+
<div class="download-button flex align-center" @click="downloadFile(item)">
46+
<el-icon class="mr-4">
47+
<Download />
48+
</el-icon>
49+
{{ $t('aiChat.download') }}
50+
</div>
51+
<div class="show flex align-center">
52+
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
53+
<div class="ml-4 ellipsis-1" :title="item && item?.name">
54+
{{ item && item?.name }}
55+
</div>
56+
</div>
57+
</el-card>
58+
</template>
59+
</el-space>
60+
</div>
61+
<div class="mb-8" v-if="image_list.length">
62+
<el-space wrap>
63+
<template v-for="(item, index) in image_list" :key="index">
64+
<div class="file cursor border-r-6" v-if="item.url">
65+
<el-image
66+
:src="item.url"
67+
:zoom-rate="1.2"
68+
:max-scale="7"
69+
:min-scale="0.2"
70+
:preview-src-list="getAttrsArray(image_list, 'url')"
71+
:initial-index="index"
72+
alt=""
73+
fit="cover"
74+
style="width: 170px; height: 170px; display: block"
75+
class="border-r-6"
76+
/>
77+
</div>
78+
</template>
79+
</el-space>
80+
</div>
81+
<div class="mb-8" v-if="audio_list.length">
82+
<el-space wrap>
83+
<template v-for="(item, index) in audio_list" :key="index">
84+
<div class="file cursor border-r-6" v-if="item.url">
85+
<audio :src="item.url" controls style="width: 350px; height: 43px" class="border-r-6" />
86+
</div>
87+
</template>
88+
</el-space>
89+
</div>
90+
<div class="mb-8" v-if="video_list.length">
91+
<el-space wrap>
92+
<template v-for="(item, index) in video_list" :key="index">
93+
<div class="file cursor border-r-6" v-if="item.url">
94+
<video :src="item.url" style="width: 170px; display: block" class="border-r-6" controls />
95+
</div>
96+
</template>
97+
</el-space>
98+
</div>
99+
</div>
40100
</template>
41101
<script setup lang="ts">
42102
import { computed, inject, ref, useAttrs } from 'vue'
43103
import { ElMessage } from 'element-plus'
44104
import type { FormField } from '@/components/dynamics-form/type'
45-
import { getImgUrl } from '@/utils/common'
105+
import { getImgUrl, downloadByURL, getFileUrl, fileType } from '@/utils/common'
106+
import { getAttrsArray } from '@/utils/array'
46107
import { t } from '@/locales'
47108
import { useFormDisabled } from 'element-plus'
48109
const inputDisabled = useFormDisabled()
@@ -86,6 +147,28 @@ const model_value = computed({
86147
})
87148
const fileArray = ref<any>([])
88149
150+
const imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF', 'BMP']
151+
const videoExtensions = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV']
152+
const audioExtensions = ['MP3', 'WAV', 'OGG', 'AAC', 'M4A']
153+
const ofType = (exts: string[]) => (f: any) => exts.includes(fileType(f?.name || '').toUpperCase())
154+
155+
const files_with_url = computed(() =>
156+
(model_value.value || []).map((f: any) => ({ ...f, url: f.url || getFileUrl(f.file_id) })),
157+
)
158+
const image_list = computed(() => files_with_url.value.filter(ofType(imageExtensions)))
159+
const audio_list = computed(() => files_with_url.value.filter(ofType(audioExtensions)))
160+
const video_list = computed(() => files_with_url.value.filter(ofType(videoExtensions)))
161+
// 非图片/音频/视频的(文档、压缩包等)统一走下载卡片
162+
const download_list = computed(() =>
163+
files_with_url.value.filter(
164+
(f: any) => !ofType([...imageExtensions, ...audioExtensions, ...videoExtensions])(f),
165+
),
166+
)
167+
168+
function downloadFile(item: any) {
169+
downloadByURL(item.url, item.name)
170+
}
171+
89172
const loading = ref<boolean>(false)
90173
91174
const uploadFile = async (file: any, fileList: Array<any>) => {
@@ -116,6 +199,30 @@ const uploadFile = async (file: any, fileList: Array<any>) => {
116199
}
117200
</script>
118201
<style lang="scss" scoped>
202+
/* hover 显示下载按钮,样式照抄 question-content/index.vue */
203+
.download-file {
204+
height: 43px;
205+
206+
&:hover {
207+
color: var(--el-color-primary);
208+
border: 1px solid var(--el-color-primary);
209+
210+
.download-button {
211+
display: block;
212+
text-align: center;
213+
line-height: 26px;
214+
}
215+
216+
.show {
217+
display: none;
218+
}
219+
}
220+
221+
.download-button {
222+
display: none;
223+
}
224+
}
225+
119226
.upload_content {
120227
.is-disabled {
121228
background-color: var(--el-fill-color-light);

0 commit comments

Comments
 (0)