From d675283dfaf5bffbdfb24ec4417798826e258830 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Thu, 24 Sep 2026 20:58:01 +0800 Subject: [PATCH] feat: tool icon --- ui/src/api/API_README.md | 1 + ui/src/api/admin/file.ts | 4 +- ui/src/assets/iconfont.js | 2 +- ui/src/components/COMPONENT_README.md | 14 +- ui/src/components/mk-edit-avatar/index.vue | 80 ++++----- ui/src/styles/STYLE_README.md | 17 +- ui/src/styles/element-plus.scss | 2 +- ui/src/styles/tailwind.css | 1 + ui/src/styles/variables.scss | 3 + ui/src/views/VIEW_README.md | 5 + .../components/ButtonEditPortal.vue | 6 +- .../tool-form/tool-custom/ToolFormDrawer.vue | 67 ++++--- ui/src/workflow-canvas/WORKFLOW_README.md | 7 +- .../workflow-canvas/component/NodeControl.vue | 170 ++++++++++++++++-- .../workflow-canvas/component/NodeSearch.vue | 8 +- ui/src/workflow-canvas/index.vue | 4 +- .../nodes/loop-body-node/index.vue | 2 +- ui/src/workflow-canvas/style.scss | 3 +- 18 files changed, 293 insertions(+), 103 deletions(-) diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index df58e6b0921..f52d1237deb 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -453,6 +453,7 @@ API 对象和工作空间上下文,作为该抽屉的范围选择例外;用 `admin/file.ts` 与 `chat/file.ts` 分别提供 `postUploadFile(file, sourceId, sourceType, onProgress?)`, 通过各自请求客户端向 `/oss/file` 提交 FormData 的 `file`、`source_id` 和 `source_type`。 +Admin 上传尚未创建的资源文件时,`sourceId` 可传 `undefined`,省略 `source_id` 并使用后端默认值。 返回值统一为 `{ request, abort }`,`request` 解包得到文件地址,`abort()` 中断客户端请求。 普通上传直接等待 `request`,需要进度时传入 `(percent, event)`;只有能获取上传总量时才回调 0–100 的百分比,100 表示请求体已上传,不代表服务端处理成功,完成状态以 `request` 为准。 diff --git a/ui/src/api/admin/file.ts b/ui/src/api/admin/file.ts index 060a999559a..4276834a5fe 100644 --- a/ui/src/api/admin/file.ts +++ b/ui/src/api/admin/file.ts @@ -5,13 +5,13 @@ import type { FileSourceType } from '@/api/types' /** 上传资源文件,支持可选的进度回调与取消操作。 */ const postUploadFile = ( file: File, - sourceId: string, + sourceId: string | undefined, sourceType: FileSourceType, onProgress?: (percent: number, event: AxiosProgressEvent) => void, ) => { const formData = new FormData() formData.append('file', file) - formData.append('source_id', sourceId) + if (sourceId !== undefined) formData.append('source_id', sourceId) formData.append('source_type', sourceType) return postUpload('/oss/file', formData, onProgress) } diff --git a/ui/src/assets/iconfont.js b/ui/src/assets/iconfont.js index 220a24f8fd2..e86a56969e2 100644 --- a/ui/src/assets/iconfont.js +++ b/ui/src/assets/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file +window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index e078fb26b10..2b3e42bfcd7 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -365,7 +365,7 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启 手动导入 `@/components/mk-edit-avatar/index.vue`,不参与全局注册。字符串 `v-model` 为当前 自定义头像 URL,空字符串表示使用默认图标;不再接收 `defaultIcon`。 `size` 默认为 `32`(px),只设置 `referenceRef` 触发容器的宽高;弹层预览和上传区域固定为 80px。 -`editable` 默认为 `true`,设为 `false` 时仅展示头像。 +`disabled` 默认为 `false`,直接传给 `el-popover` 控制禁用,不额外维护禁用状态监听。 必填默认插槽同时渲染触发区和默认 Logo 预览,只透出 `{ icon }`。触发区传入当前头像 (空值转为 `undefined`);默认预览始终传入 `icon: undefined`。两处容器通过样式让插槽的 @@ -376,10 +376,14 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启 门户编辑通过此插槽渲染 `PortalIcon`,并将头像绑定到 `portalForm.logo`。 悬停头像打开 Logo 设置,打开后保持显示以便选择本地文件;取消、点击外部或弹层内按 Escape -关闭并丢弃草稿,确定后才更新 `v-model` 并触发 `change(icon, file)`。 -自定义图片支持 JPG、PNG、GIF,大小不超过 10MB;确认时 `icon` 为本地 Data URL,`file` -为所选 `File`,未重新选文件或使用默认 Logo 时为 `null`。组件不请求上传接口;调用方负责上传 -和持久化,并可将返回的图片 URL 写回 `v-model`。 +关闭并丢弃草稿。选择新图片后确定,先将预览地址写入 `v-model`,再触发 `change(file)`;保留已有自定义 +头像直接确定时不触发事件。恢复默认时先将 `v-model` 设为空,再触发 `change(null)`。 +自定义图片支持 JPG、PNG、GIF,大小不超过 10MB;组件通过 `URL.createObjectURL` 创建预览, +统一释放不再被 `previewIcon` 或 `v-model` 使用的临时地址,卸载时释放剩余地址。 +调用方不重复创建或释放预览地址;确认后即可通过插槽展示新头像,关闭弹层不会释放仍在显示的地址。 +`blob:` 地址仅供当前组件存续期间预览,不能提交给后端作为持久图片地址。 +组件只通过 `change(file)` 通知调用方,不请求接口,也不规定文件何时上传。 +调用方按业务流程处理文件,通过 `v-model` 传入用于展示的头像地址。 ```vue