From cf4c88982386035cb3486979dd6cd388c4c14101 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Thu, 29 Oct 2026 18:57:36 +0800 Subject: [PATCH] fix: upload --- ui/src/api/API_README.md | 2 + ui/src/components/COMPONENT_README.md | 5 + .../items/upload/UploadInput.vue | 225 +++++------------- .../components/content/items/form.vue | 4 +- ui/src/conversation-panel/core/types.ts | 4 +- .../left-sidebar/account-menu/index.vue | 4 +- ui/src/conversation-panel/view/chat/index.vue | 17 ++ .../conversation-panel/view/debug/index.vue | 17 ++ .../conversation-panel/view/portal/index.vue | 17 ++ .../tool-store/component/ToolStoreCard.vue | 2 +- .../ai-chat-node/details/application.vue | 4 +- .../document-extract-node/details/index.vue | 70 ++++-- .../knowledge-write-node/details/index.vue | 59 +++-- .../details/index.vue | 26 +- .../details/application.vue | 5 +- 15 files changed, 234 insertions(+), 227 deletions(-) diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 1092e0d0531..93bc09ca4f7 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -500,6 +500,8 @@ Admin 上传尚未创建的资源文件时,`sourceId` 可传 `undefined`,省 资源类型使用 `@/api/enums` 的 `FILE_SOURCE_TYPE` 与 `@/api/types` 的 `FileSourceType`, 包括知识库、智能体、工具、文档、对话及三种临时文件有效期。对话 Store 使用 `FILE_SOURCE_TYPE.CHAT` 调用对应 File API,对话 API 不再维护上传接口。 +chat、debug、portal 视图还通过 `upload` 注入为动态表单提供上传能力,使用当前会话 ID 与 +`FILE_SOURCE_TYPE.CHAT`,返回带 `abort` 的 Promise,响应适配为 `{ data: url }`。 知识库工作流调试上传复用 Admin `FileApi.postUploadFile`,传知识库 ID 与 `FILE_SOURCE_TYPE.KNOWLEDGE`, 通过返回的 `request` 获取文件地址,再适配动态表单的 `{ data: url }` 返回协议;工作流 API 不重复封装上传。 diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 5374f9296d8..dfc1b209199 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -671,6 +671,11 @@ JSON 专用输入框,通过 `v-model` 接收并回传解析后的 JSON 值, 已导入节点自动选中且不可取消,`disabled` 节点不可选择。懒加载先查询根目录,展开时通过 原有工具数据源接口提交 `current_node` 查询子节点;非懒加载使用 `option_list`。 +动态表单 `UploadInput` 按附件原顺序使用 Flex 换行展示,不按类型分组。 +文件及音视频使用图标和文件名紧凑卡片,文件链接在新标签页打开或下载。 +图片使用 `ImageUploadCard`,音视频通过 `MkAudio`、`MkVideo` 打开预览。`inputDisabled` 为真时隐藏 +上传和删除入口,保留下载、图片预览和音视频播放;可编辑时允许上传和删除附件。 + 动态表单 `LocalFileUpload` 字段复用 `MkDragUpload` 的多文件模式,将 `file_type_list`、`file_size_limit`、`file_count_limit` 映射为格式、大小和数量限制。 上传适配兼容上层返回的 Promise 或 `{ request, abort }`,响应支持地址字符串或 `{ data: 地址 }`; diff --git a/ui/src/components/mk-dynamics-form/items/upload/UploadInput.vue b/ui/src/components/mk-dynamics-form/items/upload/UploadInput.vue index 31aa252074c..568927e0b74 100644 --- a/ui/src/components/mk-dynamics-form/items/upload/UploadInput.vue +++ b/ui/src/components/mk-dynamics-form/items/upload/UploadInput.vue @@ -3,11 +3,13 @@ import type { DynamicFormValue } from '../../type' import { computed, inject, ref, useAttrs } from 'vue' import type { FormField } from '@/components/mk-dynamics-form/type' import { MsgWarning } from '@/utils/message' -import { downloadByURL, getAttrsArray, getFileUrl } from '@/utils/common' -import { getFileExtension, getFileIconUrl } from '@/utils/icon' -import { formatFileSize } from '@/utils/number' - +import { getFileUrl } from '@/utils/common' +import { getFileIconUrl, isImage, isAudio, isVideo } from '@/utils/icon' import { useFormDisabled } from 'element-plus' + +import ImageUploadCard from '@/components/mk-drag-upload/ImageUploadCard.vue' +import MkAudio from '@/components/global/mk-media-preview/MkAudio.vue' +import MkVideo from '@/components/global/mk-media-preview/MkVideo.vue' const inputDisabled = useFormDisabled() const attrs = useAttrs() as DynamicFormValue const upload = inject('upload') as DynamicFormValue @@ -15,10 +17,8 @@ const props = withDefaults(defineProps<{ modelValue?: DynamicFormValue; formFiel const emit = defineEmits(['update:modelValue']) const deleteFile = (file: DynamicFormValue) => { - if (inputDisabled.value) { - return - } - fileArray.value = fileArray.value.filter((f: DynamicFormValue) => f.uid !== file.uid) + if (inputDisabled.value) return + fileArray.value = modelValueProxy.value.filter((attachment: DynamicFormValue) => attachment.file_id !== file.file_id) emit('update:modelValue', fileArray.value) } @@ -35,23 +35,18 @@ const modelValueProxy = computed({ }) const fileArray = ref([]) -const imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF', 'BMP'] -const videoExtensions = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV'] -const audioExtensions = ['MP3', 'WAV', 'OGG', 'AAC', 'M4A'] -const ofType = (exts: string[]) => (f: DynamicFormValue) => exts.includes(getFileExtension(f?.name || '').toUpperCase()) - -const filesWithUrl = computed(() => (modelValueProxy.value || []).map((f: DynamicFormValue) => ({ ...f, url: f.url || getFileUrl(f.file_id) }))) -const images = computed(() => filesWithUrl.value.filter(ofType(imageExtensions))) -const audioFiles = computed(() => filesWithUrl.value.filter(ofType(audioExtensions))) -const videoFiles = computed(() => filesWithUrl.value.filter(ofType(videoExtensions))) -// 非图片/音频/视频的(文档、压缩包等)统一走下载卡片 -const downloadFiles = computed(() => - filesWithUrl.value.filter((f: DynamicFormValue) => !ofType([...imageExtensions, ...audioExtensions, ...videoExtensions])(f)), +// 按原顺序展示附件,类型仅决定预览方式。 +const attachments = computed(() => + (modelValueProxy.value || []).map((file: DynamicFormValue) => ({ + ...file, + status: 'success' as const, + url: file.url || getFileUrl(file.file_id), + kind: isImage(file.name) ? 'image' : isAudio(file.name) ? 'audio' : isVideo(file.name) ? 'video' : 'file', + })), +) +const imageSources = computed(() => + attachments.value.filter((file: DynamicFormValue) => file.kind === 'image').map((file: DynamicFormValue) => file.url), ) - -function downloadFile(item: DynamicFormValue) { - downloadByURL(item.url, item.name) -} const loading = ref(false) @@ -82,8 +77,9 @@ const uploadFile = async (file: DynamicFormValue, fileList: DynamicFormValue[]) - diff --git a/ui/src/conversation-panel/components/content/items/form.vue b/ui/src/conversation-panel/components/content/items/form.vue index 246b44cd1e0..3876bd7d0b2 100644 --- a/ui/src/conversation-panel/components/content/items/form.vue +++ b/ui/src/conversation-panel/components/content/items/form.vue @@ -59,8 +59,8 @@ const submit = async () => { diff --git a/ui/src/workflow-canvas/nodes/knowledge-write-node/details/index.vue b/ui/src/workflow-canvas/nodes/knowledge-write-node/details/index.vue index c6746fb7918..91e461742af 100644 --- a/ui/src/workflow-canvas/nodes/knowledge-write-node/details/index.vue +++ b/ui/src/workflow-canvas/nodes/knowledge-write-node/details/index.vue @@ -1,8 +1,9 @@ diff --git a/ui/src/workflow-canvas/nodes/video-understand-node/details/application.vue b/ui/src/workflow-canvas/nodes/video-understand-node/details/application.vue index 9a43b72a28e..2fc268cfb1d 100644 --- a/ui/src/workflow-canvas/nodes/video-understand-node/details/application.vue +++ b/ui/src/workflow-canvas/nodes/video-understand-node/details/application.vue @@ -30,7 +30,10 @@ defineProps<{ {{ history.content }}