From 2e6af6e06c0b5542451a98cf1fec132f2abe534b Mon Sep 17 00:00:00 2001 From: 2910410219 <2910410219@qq.com> Date: Sat, 9 May 2026 11:01:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=8A=9F=E8=83=BD=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E6=89=A7=E8=A1=8C=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增执行列表相关接口和数据结构,支持获取执行流和执行项 - 更新创作页面以展示执行流和预览功能,提升用户交互体验 - 优化树形结构展示,确保根据执行流动态生成节点 - 引入文件上传功能,支持用户上传文件并获取文件URL --- src/api/digitalHuman/creation/index.ts | 39 ++++- src/views/digitalHuman/creation/index.vue | 177 +++++++++++++--------- 2 files changed, 145 insertions(+), 71 deletions(-) diff --git a/src/api/digitalHuman/creation/index.ts b/src/api/digitalHuman/creation/index.ts index c6bfb94..4da2ed1 100644 --- a/src/api/digitalHuman/creation/index.ts +++ b/src/api/digitalHuman/creation/index.ts @@ -67,6 +67,23 @@ export interface CreationTreeItem { contentTypes: CreationContentTypeItem[]; } +// 新的执行列表数据结构 +export interface ExecutionItem { + timestamp: string; + content: string; + label: string; +} + +export interface ExecutionFlowItem { + flowName: string; + items: ExecutionItem[]; +} + +export interface ExecutionTreeItem { + createDate: string; + flows: ExecutionFlowItem[]; +} + export interface CreationListData { list: unknown[] | null; total: number; @@ -74,12 +91,23 @@ export interface CreationListData { imgAddressPrefix: string; } +export interface ExecutionListData { + tree: ExecutionTreeItem[]; + imgAddressPrefix: string; +} + export interface CreationListResponse { code: number; message: string; data: CreationListData; } +export interface ExecutionListResponse { + code: number; + message: string; + data: ExecutionListData; +} + export interface CreationSubmitParams { mode: string; content_type: string; @@ -105,6 +133,14 @@ export function getCreationList(params: CreationListParams, requestOptions?: Req }) as Promise; } +export function getExecutionList(requestOptions?: RequestOptions) { + return request({ + url: '/ai-agent/flow/execution/list', + method: 'get', + requestOptions, + }) as Promise; +} + export function getNodeLibraryList(requestOptions?: RequestOptions) { return request({ url: '/ai-agent/node/library/list', @@ -260,7 +296,8 @@ export interface ExecuteFlowParams { desc?: string; fileUrl?: string[]; flowContent?: FlowInfo; - flowId?: number; + flowId?: string; + flowName?: string; nodeInputParams?: FlowNode[]; sessionId?: string; skillName?: string; diff --git a/src/views/digitalHuman/creation/index.vue b/src/views/digitalHuman/creation/index.vue index bb118d3..587d2e0 100644 --- a/src/views/digitalHuman/creation/index.vue +++ b/src/views/digitalHuman/creation/index.vue @@ -15,19 +15,28 @@ default-expand-all :highlight-current="true" :expand-on-click-node="false" - @node-click="handleNodeClick" > @@ -249,7 +258,7 @@ - + @@ -497,7 +514,7 @@ import SkillSelector from '/@/components/skill/NodeSkillSelector.vue'; import type { SkillItem } from '/@/api/digitalHuman/skill'; import { downloadToFile, - getCreationList, + getExecutionList, getNodeLibraryList, getWorkflowList, getWorkflowDetail, @@ -505,13 +522,13 @@ import { deleteWorkflow, saveWorkflow, executeFlow, - type CreationListParams, - type CreationTreeItem, + type ExecutionTreeItem, type NodeLibraryFormItem, type NodeLibraryGroup, type WorkflowItem, type ExecuteFlowParams, } from '/@/api/digitalHuman/creation'; +import { uploadFile } from '/@/api/common/upload'; type NodeType = 'date' | 'contentType' | 'theme' | 'title' | 'html' | 'image'; type Item = Record; @@ -573,6 +590,9 @@ const selectedFiles = ref([]); const selectedCreationSkill = ref(null); const showCreationSkillSelector = ref(false); const isCreating = ref(false); +// 预览相关状态 +const previewDialogVisible = ref(false); +const previewUrl = ref(''); // 会话ID管理(存储在 sessionStorage 中) const getSessionId = () => { let sessionId = sessionStorage.getItem('ai_creation_session_id'); @@ -718,7 +738,6 @@ const availableParentParams = computed(() => { return params; }); const treeProps = { children: 'children', label: 'label' }; -const queryParams: CreationListParams = { keyword: '', pageNum: 1, pageSize: 10 }; const apiBaseUrl = (import.meta.env.VITE_API_URL || '').replace(/\/$/, ''); const nodeLibraryGroups = ref([]); const workflowDsl = computed(() => ({ @@ -761,44 +780,36 @@ const buildAssetUrl = (p?: string) => : imgAddressPrefix.value ? joinUrl(joinUrl(apiBaseUrl, imgAddressPrefix.value), p) : joinUrl(apiBaseUrl, p); -const buildTreeNodes = (tree: CreationTreeItem[]): TreeNode[] => +const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] => tree.map((d, di) => ({ id: `date-${di}`, - label: d.createdDate, + label: d.createDate, nodeType: 'date', - children: (d.contentTypes || []).map((c, ci) => ({ - id: `content-${di}-${ci}`, - label: c.contentType, + children: (d.flows || []).map((f, fi) => ({ + id: `flow-${di}-${fi}`, + label: f.flowName || '未命名工作流', nodeType: 'contentType', - children: (c.themes || []).map((t, ti) => ({ - id: `theme-${di}-${ci}-${ti}`, - label: t.theme, - nodeType: 'theme', - children: (t.titles || []).map((title, i) => ({ - id: `title-${di}-${ci}-${ti}-${i}`, - label: title.title || `作品${i + 1}`, - nodeType: 'title', - children: [ - ...(title.htmlFileUrl - ? [{ id: `html-${di}-${ci}-${ti}-${i}`, label: 'HTML', nodeType: 'html' as const, fileUrl: title.htmlFileUrl }] - : []), - ...(title.imageUrls || []).map((img, ii) => ({ - id: `img-${di}-${ci}-${ti}-${i}-${ii}`, - label: img.name || `图片 ${ii + 1}`, - nodeType: 'image' as const, - fileUrl: img.url, - })), - ], - })), + children: (f.items || []).map((item, ii) => ({ + id: `item-${di}-${fi}-${ii}`, + label: item.label || `作品${ii + 1}`, + nodeType: 'title', + children: [ + { + id: `html-${di}-${fi}-${ii}`, + label: 'HTML', + nodeType: 'html' as const, + fileUrl: item.content, + }, + ], })), })), })); const getList = async () => { treeLoading.value = true; try { - const res = await getCreationList({ ...queryParams, keyword: queryParams.keyword || undefined }, { errorMode: 'page' }); + const res = await getExecutionList({ errorMode: 'page' }); imgAddressPrefix.value = res.data?.imgAddressPrefix || ''; - treeNodes.value = buildTreeNodes(res.data?.Tree || []); + treeNodes.value = buildTreeNodes(res.data?.tree || []); } catch { treeNodes.value = []; imgAddressPrefix.value = ''; @@ -1048,7 +1059,25 @@ const sendMessage = async () => { isCreating.value = true; try { - // 1. 构建节点输入参数 + // 1. 先上传文件到 OSS,获取文件 URL + const fileUrls: string[] = []; + if (selectedFiles.value.length > 0) { + for (const file of selectedFiles.value) { + try { + const uploadRes = await uploadFile(file, { errorMode: 'page' }); + // 拼接完整的文件地址 + const fullUrl = uploadRes.data.fileAddressPrefix + ? `${uploadRes.data.fileAddressPrefix}${uploadRes.data.fileURL}` + : uploadRes.data.fileURL; + fileUrls.push(fullUrl); + } catch (error) { + ElMessage.error(`文件 ${file.name} 上传失败`); + throw error; + } + } + } + + // 2. 构建节点输入参数 const nodeInputParams = currentWorkflowForCreation.value.nodeInputParams?.map((node: any) => { const nodeParam: any = { id: node.id, @@ -1087,44 +1116,26 @@ const sendMessage = async () => { return nodeParam; }) || []; - // 2. 同步更新 flowContent.nodes,使其与 nodeInputParams 一致 + // 3. 同步更新 flowContent.nodes,使其与 nodeInputParams 一致 const updatedFlowContent = { ...currentWorkflowForCreation.value.flowContent, nodes: nodeInputParams, // 使用更新后的节点参数 }; - // 3. 构建请求参数 + // 4. 构建请求参数 const params: ExecuteFlowParams = { - flowId: parseInt(currentWorkflowForCreation.value.id), + flowId: currentWorkflowForCreation.value.id, // ID 是字符串 flowContent: updatedFlowContent, nodeInputParams: nodeInputParams, sessionId: getSessionId(), desc: userInput.value, skillName: selectedCreationSkill.value?.name, + flowName: currentWorkflowForCreation.value.flowName || currentWorkflowForCreation.value.flowTemplateName, // 工作流名称 + fileUrl: fileUrls, // 添加文件 URL 数组 }; - // 4. 使用 FormData 传递文件流 - const formData = new FormData(); - - // 添加文件 - if (selectedFiles.value.length > 0) { - selectedFiles.value.forEach((file) => { - formData.append('files', file); - }); - } - - // 添加其他参数(转为 JSON 字符串) - formData.append('flowId', params.flowId!.toString()); - formData.append('flowContent', JSON.stringify(params.flowContent)); - formData.append('nodeInputParams', JSON.stringify(params.nodeInputParams)); - formData.append('sessionId', params.sessionId!); - formData.append('desc', params.desc!); - if (params.skillName) { - formData.append('skillName', params.skillName); - } - - // 5. 调用执行接口 - await executeFlow(formData, { errorMode: 'page' }); + // 5. 调用执行接口(不再使用 FormData,直接传 JSON) + await executeFlow(params, { errorMode: 'page' }); ElMessage.success('创作完成!'); @@ -1151,12 +1162,15 @@ const getFieldClass = (type: string) => { if (type === 'number' || type === 'switch') return 'form-item-small'; return 'form-item-medium'; }; -const handleNodeClick = (d: TreeNode) => { +// 预览节点 +const previewNode = (d: TreeNode) => { if (d.nodeType !== 'html' && d.nodeType !== 'image') return; const url = buildAssetUrl(d.fileUrl); if (!url) return ElMessage.warning('当前节点没有可用预览地址'); - window.open(url, '_blank'); + previewUrl.value = url; + previewDialogVisible.value = true; }; +// 下载节点 const downloadNode = async (d: TreeNode) => { if (d.nodeType !== 'html' && d.nodeType !== 'image') return; if (!d.fileUrl) return ElMessage.warning('当前节点没有可下载地址'); @@ -3101,4 +3115,27 @@ onBeforeUnmount(() => { display: flex; gap: 8px; } + +/* 预览弹窗样式 */ +.preview-container { + width: 100%; + height: 70vh; + display: flex; + align-items: center; + justify-content: center; +} +.preview-iframe { + width: 100%; + height: 100%; + border: none; + border-radius: 8px; + background: #fff; +} + +/* 树节点操作按钮样式 */ +.tree-node-actions { + display: flex; + gap: 4px; + margin-left: 8px; +}