From 51fb4b479e1df7636a2c9204099f60ce216cd09e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Nov 2025 09:26:36 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90ele=E3=80=91=E3=80=90ai?= =?UTF-8?q?=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=E7=9A=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflow/form/modules/workflow-design.vue | 110 ++++--- apps/web-ele/src/api/ai/workflow/index.ts | 50 ++- apps/web-ele/src/router/routes/modules/ai.ts | 48 +-- apps/web-ele/src/views/ai/workflow/data.ts | 97 ++++++ .../src/views/ai/workflow/form/index.vue | 288 ++++++++++++++++++ .../ai/workflow/form/modules/basic-info.vue | 70 +++++ .../workflow/form/modules/workflow-design.vue | 286 +++++++++++++++++ apps/web-ele/src/views/ai/workflow/index.vue | 124 ++++++++ 8 files changed, 985 insertions(+), 88 deletions(-) create mode 100644 apps/web-ele/src/views/ai/workflow/data.ts create mode 100644 apps/web-ele/src/views/ai/workflow/form/index.vue create mode 100644 apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue create mode 100644 apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue create mode 100644 apps/web-ele/src/views/ai/workflow/index.vue diff --git a/apps/web-antd/src/views/ai/workflow/form/modules/workflow-design.vue b/apps/web-antd/src/views/ai/workflow/form/modules/workflow-design.vue index becc5505f..2875dbf9a 100644 --- a/apps/web-antd/src/views/ai/workflow/form/modules/workflow-design.vue +++ b/apps/web-antd/src/views/ai/workflow/form/modules/workflow-design.vue @@ -28,43 +28,52 @@ const [Drawer, drawerApi] = useVbenDrawer({ footer: false, closeOnClickModal: false, modal: false, + onOpenChange(isOpen: boolean) { + if (!isOpen) { + return; + } + try { + // 查找 start 节点 + const startNode = getStartNode(); + // 获取参数定义 + const parameters: any[] = (startNode.data?.parameters as any[]) || []; + const paramDefinitions: Record = {}; + // 加入参数选项方便用户添加非必须参数 + parameters.forEach((param: any) => { + paramDefinitions[param.name] = param; + }); + // 自动装载需必填的参数 + function mergeIfRequiredButNotSet(target: any[]) { + const needPushList = []; + for (const key in paramDefinitions) { + const param = paramDefinitions[key]; + + if (param.required) { + const item = target.find((item: any) => item.key === key); + + if (!item) { + needPushList.push({ + key: param.name, + value: param.defaultValue || '', + }); + } + } + } + target.push(...needPushList); + } + mergeIfRequiredButNotSet(params4Test.value); + + // 设置参数 + paramsOfStartNode.value = paramDefinitions; + } catch (error) { + console.error('加载参数失败:', error); + } + }, }); + /** 展示工作流测试抽屉 */ function testWorkflowModel() { drawerApi.open(); - const startNode = getStartNode(); - - // 获取参数定义 - const parameters: any[] = (startNode.data?.parameters as any[]) || []; - const paramDefinitions: Record = {}; - - // 加入参数选项方便用户添加非必须参数 - parameters.forEach((param: any) => { - paramDefinitions[param.name] = param; - }); - - function mergeIfRequiredButNotSet(target: any) { - const needPushList = []; - for (const key in paramDefinitions) { - const param = paramDefinitions[key]; - - if (param.required) { - const item = target.find((item: any) => item.key === key); - - if (!item) { - needPushList.push({ - key: param.name, - value: param.defaultValue || '', - }); - } - } - } - target.push(...needPushList); - } - // 自动装载需必填的参数 - mergeIfRequiredButNotSet(params4Test.value); - - paramsOfStartNode.value = paramDefinitions; } /** 运行流程 */ @@ -74,27 +83,26 @@ async function goRun() { loading.value = true; error.value = null; testResult.value = null; - // / 查找start节点 - const startNode = getStartNode(); + // 查找start节点 + const startNode = getStartNode(); // 获取参数定义 const parameters: any[] = (startNode.data?.parameters as any[]) || []; const paramDefinitions: Record = {}; parameters.forEach((param: any) => { paramDefinitions[param.name] = param.dataType; }); - // 参数类型转换 const convertedParams: Record = {}; for (const { key, value } of params4Test.value) { const paramKey = key.trim(); - if (!paramKey) continue; - + if (!paramKey) { + continue; + } let dataType = paramDefinitions[paramKey]; if (!dataType) { dataType = 'String'; } - try { convertedParams[paramKey] = convertParamValue(value, dataType); } catch (error: any) { @@ -102,13 +110,11 @@ async function goRun() { } } - const data = { + // 执行测试请求 + testResult.value = await testWorkflow({ graph: JSON.stringify(val), params: convertedParams, - }; - - const response = await testWorkflow(data); - testResult.value = response; + }); } catch (error: any) { error.value = error.response?.data?.message || '运行失败,请检查参数和网络连接'; @@ -120,6 +126,7 @@ async function goRun() { /** 获取开始节点 */ function getStartNode() { if (tinyflowRef.value) { + // TODO @xingyu:不确定是不是这里封装了 Tinyflow,现在 .getData() 会报错; const val = tinyflowRef.value.getData(); const startNode = val!.nodes.find((node: any) => node.type === 'startNode'); if (!startNode) { @@ -142,8 +149,9 @@ function removeParam(index: number) { /** 类型转换函数 */ function convertParamValue(value: string, dataType: string) { - if (value === '') return null; // 空值处理 - + if (value === '') { + return null; + } switch (dataType) { case 'Number': { const num = Number(value); @@ -154,8 +162,12 @@ function convertParamValue(value: string, dataType: string) { return String(value); } case 'Boolean': { - if (value.toLowerCase() === 'true') return true; - if (value.toLowerCase() === 'false') return false; + if (value.toLowerCase() === 'true') { + return true; + } + if (value.toLowerCase() === 'false') { + return false; + } throw new Error('必须为 true/false'); } case 'Array': @@ -171,9 +183,9 @@ function convertParamValue(value: string, dataType: string) { } } } + /** 表单校验 */ async function validate() { - // 获取最新的流程数据 if (!workflowData.value || !tinyflowRef.value) { throw new Error('请设计流程'); } diff --git a/apps/web-ele/src/api/ai/workflow/index.ts b/apps/web-ele/src/api/ai/workflow/index.ts index a383d0cd0..676d6337c 100644 --- a/apps/web-ele/src/api/ai/workflow/index.ts +++ b/apps/web-ele/src/api/ai/workflow/index.ts @@ -2,28 +2,48 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; -export function getWorkflowPage(params: PageParam) { - return requestClient.get>('/ai/workflow/page', { - params, - }); +export namespace AiWorkflowApi { + /** 工作流 */ + export interface Workflow { + id?: number; // 编号 + name: string; // 工作流名称 + code: string; // 工作流标识 + graph: string; // 工作流模型 JSON 数据 + remark?: string; // 备注 + status: number; // 状态 + createTime?: Date; // 创建时间 + } } -export const getWorkflow = (id: number | string) => { - return requestClient.get(`/ai/workflow/get?id=${id}`); -}; +/** 查询工作流管理列表 */ +export function getWorkflowPage(params: PageParam) { + return requestClient.get>( + '/ai/workflow/page', + { params }, + ); +} -export const createWorkflow = (data: any) => { +/** 查询工作流详情 */ +export function getWorkflow(id: number) { + return requestClient.get(`/ai/workflow/get?id=${id}`); +} + +/** 新增工作流 */ +export function createWorkflow(data: AiWorkflowApi.Workflow) { return requestClient.post('/ai/workflow/create', data); -}; +} -export const updateWorkflow = (data: any) => { +/** 修改工作流 */ +export function updateWorkflow(data: AiWorkflowApi.Workflow) { return requestClient.put('/ai/workflow/update', data); -}; +} -export const deleteWorkflow = (id: number | string) => { +/** 删除工作流 */ +export function deleteWorkflow(id: number) { return requestClient.delete(`/ai/workflow/delete?id=${id}`); -}; +} -export const testWorkflow = (data: any) => { +/** 测试工作流 */ +export function testWorkflow(data: any) { return requestClient.post('/ai/workflow/test', data); -}; +} diff --git a/apps/web-ele/src/router/routes/modules/ai.ts b/apps/web-ele/src/router/routes/modules/ai.ts index 639906e81..ccd7b2d8a 100644 --- a/apps/web-ele/src/router/routes/modules/ai.ts +++ b/apps/web-ele/src/router/routes/modules/ai.ts @@ -82,30 +82,30 @@ const routes: RouteRecordRaw[] = [ activePath: '/ai/knowledge', }, }, - // { - // path: String.raw`workflow/create/:id(\d+)/:type(update|create)`, - // component: () => import('#/views/ai/workflow/form/index.vue'), - // name: 'AiWorkflowCreate', - // meta: { - // noCache: true, - // hidden: true, - // canTo: true, - // title: '设计 AI 工作流', - // activePath: '/ai/workflow', - // }, - // }, - // { - // path: 'console/workflow/:type/:id', - // component: () => import('#/views/ai/workflow/form/index.vue'), - // name: 'AiWorkflowUpdate', - // meta: { - // noCache: true, - // hidden: true, - // canTo: true, - // title: '设计 AI 工作流', - // activePath: '/ai/workflow', - // }, - // }, + { + path: String.raw`workflow/create/:id(\d+)/:type(update|create)`, + component: () => import('#/views/ai/workflow/form/index.vue'), + name: 'AiWorkflowCreate', + meta: { + noCache: true, + hidden: true, + canTo: true, + title: '设计 AI 工作流', + activePath: '/ai/workflow', + }, + }, + { + path: 'console/workflow/:type/:id', + component: () => import('#/views/ai/workflow/form/index.vue'), + name: 'AiWorkflowUpdate', + meta: { + noCache: true, + hidden: true, + canTo: true, + title: '设计 AI 工作流', + activePath: '/ai/workflow', + }, + }, ], }, ]; diff --git a/apps/web-ele/src/views/ai/workflow/data.ts b/apps/web-ele/src/views/ai/workflow/data.ts new file mode 100644 index 000000000..5decf9f3f --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/data.ts @@ -0,0 +1,97 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '流程标识', + component: 'Input', + componentProps: { + placeholder: '请输入流程标识', + allowClear: true, + }, + }, + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'code', + title: '流程标识', + minWidth: 150, + }, + { + field: 'name', + title: '流程名称', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/workflow/form/index.vue b/apps/web-ele/src/views/ai/workflow/form/index.vue new file mode 100644 index 000000000..00e5a08d9 --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/index.vue @@ -0,0 +1,288 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue b/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue new file mode 100644 index 000000000..09f87253e --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue b/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue new file mode 100644 index 000000000..8786e31cd --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue @@ -0,0 +1,286 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/index.vue b/apps/web-ele/src/views/ai/workflow/index.vue new file mode 100644 index 000000000..ab31d4756 --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/index.vue @@ -0,0 +1,124 @@ + + +