feat:【ele】【ai】工作流的代码迁移
This commit is contained in:
@@ -28,43 +28,52 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
footer: false,
|
footer: false,
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
modal: 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<string, any> = {};
|
||||||
|
// 加入参数选项方便用户添加非必须参数
|
||||||
|
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() {
|
function testWorkflowModel() {
|
||||||
drawerApi.open();
|
drawerApi.open();
|
||||||
const startNode = getStartNode();
|
|
||||||
|
|
||||||
// 获取参数定义
|
|
||||||
const parameters: any[] = (startNode.data?.parameters as any[]) || [];
|
|
||||||
const paramDefinitions: Record<string, any> = {};
|
|
||||||
|
|
||||||
// 加入参数选项方便用户添加非必须参数
|
|
||||||
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;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
testResult.value = null;
|
testResult.value = null;
|
||||||
// / 查找start节点
|
|
||||||
const startNode = getStartNode();
|
|
||||||
|
|
||||||
|
// 查找start节点
|
||||||
|
const startNode = getStartNode();
|
||||||
// 获取参数定义
|
// 获取参数定义
|
||||||
const parameters: any[] = (startNode.data?.parameters as any[]) || [];
|
const parameters: any[] = (startNode.data?.parameters as any[]) || [];
|
||||||
const paramDefinitions: Record<string, any> = {};
|
const paramDefinitions: Record<string, any> = {};
|
||||||
parameters.forEach((param: any) => {
|
parameters.forEach((param: any) => {
|
||||||
paramDefinitions[param.name] = param.dataType;
|
paramDefinitions[param.name] = param.dataType;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 参数类型转换
|
// 参数类型转换
|
||||||
const convertedParams: Record<string, any> = {};
|
const convertedParams: Record<string, any> = {};
|
||||||
for (const { key, value } of params4Test.value) {
|
for (const { key, value } of params4Test.value) {
|
||||||
const paramKey = key.trim();
|
const paramKey = key.trim();
|
||||||
if (!paramKey) continue;
|
if (!paramKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let dataType = paramDefinitions[paramKey];
|
let dataType = paramDefinitions[paramKey];
|
||||||
if (!dataType) {
|
if (!dataType) {
|
||||||
dataType = 'String';
|
dataType = 'String';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
convertedParams[paramKey] = convertParamValue(value, dataType);
|
convertedParams[paramKey] = convertParamValue(value, dataType);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -102,13 +110,11 @@ async function goRun() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
// 执行测试请求
|
||||||
|
testResult.value = await testWorkflow({
|
||||||
graph: JSON.stringify(val),
|
graph: JSON.stringify(val),
|
||||||
params: convertedParams,
|
params: convertedParams,
|
||||||
};
|
});
|
||||||
|
|
||||||
const response = await testWorkflow(data);
|
|
||||||
testResult.value = response;
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
error.value =
|
error.value =
|
||||||
error.response?.data?.message || '运行失败,请检查参数和网络连接';
|
error.response?.data?.message || '运行失败,请检查参数和网络连接';
|
||||||
@@ -120,6 +126,7 @@ async function goRun() {
|
|||||||
/** 获取开始节点 */
|
/** 获取开始节点 */
|
||||||
function getStartNode() {
|
function getStartNode() {
|
||||||
if (tinyflowRef.value) {
|
if (tinyflowRef.value) {
|
||||||
|
// TODO @xingyu:不确定是不是这里封装了 Tinyflow,现在 .getData() 会报错;
|
||||||
const val = tinyflowRef.value.getData();
|
const val = tinyflowRef.value.getData();
|
||||||
const startNode = val!.nodes.find((node: any) => node.type === 'startNode');
|
const startNode = val!.nodes.find((node: any) => node.type === 'startNode');
|
||||||
if (!startNode) {
|
if (!startNode) {
|
||||||
@@ -142,8 +149,9 @@ function removeParam(index: number) {
|
|||||||
|
|
||||||
/** 类型转换函数 */
|
/** 类型转换函数 */
|
||||||
function convertParamValue(value: string, dataType: string) {
|
function convertParamValue(value: string, dataType: string) {
|
||||||
if (value === '') return null; // 空值处理
|
if (value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case 'Number': {
|
case 'Number': {
|
||||||
const num = Number(value);
|
const num = Number(value);
|
||||||
@@ -154,8 +162,12 @@ function convertParamValue(value: string, dataType: string) {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
case 'Boolean': {
|
case 'Boolean': {
|
||||||
if (value.toLowerCase() === 'true') return true;
|
if (value.toLowerCase() === 'true') {
|
||||||
if (value.toLowerCase() === 'false') return false;
|
return true;
|
||||||
|
}
|
||||||
|
if (value.toLowerCase() === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
throw new Error('必须为 true/false');
|
throw new Error('必须为 true/false');
|
||||||
}
|
}
|
||||||
case 'Array':
|
case 'Array':
|
||||||
@@ -171,9 +183,9 @@ function convertParamValue(value: string, dataType: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表单校验 */
|
/** 表单校验 */
|
||||||
async function validate() {
|
async function validate() {
|
||||||
// 获取最新的流程数据
|
|
||||||
if (!workflowData.value || !tinyflowRef.value) {
|
if (!workflowData.value || !tinyflowRef.value) {
|
||||||
throw new Error('请设计流程');
|
throw new Error('请设计流程');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,48 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export function getWorkflowPage(params: PageParam) {
|
export namespace AiWorkflowApi {
|
||||||
return requestClient.get<PageResult<any>>('/ai/workflow/page', {
|
/** 工作流 */
|
||||||
params,
|
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<PageResult<AiWorkflowApi.Workflow>>(
|
||||||
|
'/ai/workflow/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const createWorkflow = (data: any) => {
|
/** 查询工作流详情 */
|
||||||
|
export function getWorkflow(id: number) {
|
||||||
|
return requestClient.get<AiWorkflowApi.Workflow>(`/ai/workflow/get?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增工作流 */
|
||||||
|
export function createWorkflow(data: AiWorkflowApi.Workflow) {
|
||||||
return requestClient.post('/ai/workflow/create', data);
|
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);
|
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}`);
|
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);
|
return requestClient.post('/ai/workflow/test', data);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -82,30 +82,30 @@ const routes: RouteRecordRaw[] = [
|
|||||||
activePath: '/ai/knowledge',
|
activePath: '/ai/knowledge',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: String.raw`workflow/create/:id(\d+)/:type(update|create)`,
|
path: String.raw`workflow/create/:id(\d+)/:type(update|create)`,
|
||||||
// component: () => import('#/views/ai/workflow/form/index.vue'),
|
component: () => import('#/views/ai/workflow/form/index.vue'),
|
||||||
// name: 'AiWorkflowCreate',
|
name: 'AiWorkflowCreate',
|
||||||
// meta: {
|
meta: {
|
||||||
// noCache: true,
|
noCache: true,
|
||||||
// hidden: true,
|
hidden: true,
|
||||||
// canTo: true,
|
canTo: true,
|
||||||
// title: '设计 AI 工作流',
|
title: '设计 AI 工作流',
|
||||||
// activePath: '/ai/workflow',
|
activePath: '/ai/workflow',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'console/workflow/:type/:id',
|
path: 'console/workflow/:type/:id',
|
||||||
// component: () => import('#/views/ai/workflow/form/index.vue'),
|
component: () => import('#/views/ai/workflow/form/index.vue'),
|
||||||
// name: 'AiWorkflowUpdate',
|
name: 'AiWorkflowUpdate',
|
||||||
// meta: {
|
meta: {
|
||||||
// noCache: true,
|
noCache: true,
|
||||||
// hidden: true,
|
hidden: true,
|
||||||
// canTo: true,
|
canTo: true,
|
||||||
// title: '设计 AI 工作流',
|
title: '设计 AI 工作流',
|
||||||
// activePath: '/ai/workflow',
|
activePath: '/ai/workflow',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
97
apps/web-ele/src/views/ai/workflow/data.ts
Normal file
97
apps/web-ele/src/views/ai/workflow/data.ts
Normal file
@@ -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' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
288
apps/web-ele/src/views/ai/workflow/form/index.vue
Normal file
288
apps/web-ele/src/views/ai/workflow/form/index.vue
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, provide, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { confirm, Page } from '@vben/common-ui';
|
||||||
|
import { AiModelTypeEnum, CommonStatusEnum } from '@vben/constants';
|
||||||
|
import { useTabs } from '@vben/hooks';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { ElButton, ElCard, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { getModelSimpleList } from '#/api/ai/model/model';
|
||||||
|
import { createWorkflow, getWorkflow, updateWorkflow } from '#/api/ai/workflow';
|
||||||
|
import { createModel, deployModel, updateModel } from '#/api/bpm/model';
|
||||||
|
|
||||||
|
import BasicInfo from './modules/basic-info.vue';
|
||||||
|
import WorkflowDesign from './modules/workflow-design.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'AiWorkflowCreate' });
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const workflowId = ref<string>('');
|
||||||
|
const actionType = ref<string>('');
|
||||||
|
|
||||||
|
const basicInfoRef = ref<InstanceType<typeof BasicInfo>>(); // 基础信息组件引用
|
||||||
|
const workflowDesignRef = ref<InstanceType<typeof WorkflowDesign>>(); // 工作流设计组件引用
|
||||||
|
|
||||||
|
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
|
||||||
|
const steps = [
|
||||||
|
{ title: '基本信息', validator: validateBasic },
|
||||||
|
{ title: '工作流设计', validator: validateWorkflow },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData: any = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
remark: '',
|
||||||
|
graph: '',
|
||||||
|
status: CommonStatusEnum.ENABLE,
|
||||||
|
}); // 表单数据
|
||||||
|
|
||||||
|
const llmProvider = ref<any>([]);
|
||||||
|
const workflowData = ref<any>({});
|
||||||
|
provide('workflowData', workflowData);
|
||||||
|
|
||||||
|
/** 步骤校验函数 */
|
||||||
|
async function validateBasic() {
|
||||||
|
await basicInfoRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工作流设计校验 */
|
||||||
|
async function validateWorkflow() {
|
||||||
|
await workflowDesignRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initData() {
|
||||||
|
if (actionType.value === 'update' && workflowId.value) {
|
||||||
|
formData.value = await getWorkflow(workflowId.value as any);
|
||||||
|
workflowData.value = JSON.parse(formData.value.graph);
|
||||||
|
}
|
||||||
|
const models = await getModelSimpleList(AiModelTypeEnum.CHAT);
|
||||||
|
llmProvider.value = {
|
||||||
|
llm: () =>
|
||||||
|
models.map(({ id, name }) => ({
|
||||||
|
value: id,
|
||||||
|
label: name,
|
||||||
|
})),
|
||||||
|
knowledge: () => [],
|
||||||
|
internal: () => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置当前步骤
|
||||||
|
currentStep.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验所有步骤数据是否完整 */
|
||||||
|
async function validateAllSteps() {
|
||||||
|
// 基本信息校验
|
||||||
|
try {
|
||||||
|
await validateBasic();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 0;
|
||||||
|
throw new Error('请完善基本信息');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单设计校验
|
||||||
|
try {
|
||||||
|
await validateWorkflow();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 1;
|
||||||
|
throw new Error('请完善工作流信息');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存操作 */
|
||||||
|
async function handleSave() {
|
||||||
|
try {
|
||||||
|
// 保存前校验所有步骤的数据
|
||||||
|
await validateAllSteps();
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
const data = {
|
||||||
|
...formData.value,
|
||||||
|
graph: JSON.stringify(workflowData.value),
|
||||||
|
};
|
||||||
|
await (actionType.value === 'update'
|
||||||
|
? updateWorkflow(data)
|
||||||
|
: createWorkflow(data));
|
||||||
|
|
||||||
|
// 保存成功,提示并跳转到列表页
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
await tabs.closeCurrentTab();
|
||||||
|
await router.push({ name: 'AiWorkflow' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
ElMessage.warning(error.message || '请完善所有步骤的必填信息');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发布操作 */
|
||||||
|
async function handleDeploy() {
|
||||||
|
try {
|
||||||
|
// 修改场景下直接发布,新增场景下需要先确认
|
||||||
|
if (!formData.value.id) {
|
||||||
|
await confirm('是否确认发布该流程?');
|
||||||
|
}
|
||||||
|
// 校验所有步骤
|
||||||
|
await validateAllSteps();
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
const modelData = {
|
||||||
|
...formData.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 先保存所有数据
|
||||||
|
if (formData.value.id) {
|
||||||
|
await updateModel(modelData);
|
||||||
|
} else {
|
||||||
|
const result = await createModel(modelData);
|
||||||
|
formData.value.id = result.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布
|
||||||
|
await deployModel(formData.value.id);
|
||||||
|
ElMessage.success('发布成功');
|
||||||
|
// 返回列表页
|
||||||
|
await router.push({ name: 'AiWorkflow' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('发布失败:', error);
|
||||||
|
ElMessage.warning(error.message || '发布失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 步骤切换处理 */
|
||||||
|
async function handleStepClick(index: number) {
|
||||||
|
try {
|
||||||
|
if (index !== 0) {
|
||||||
|
await validateBasic();
|
||||||
|
}
|
||||||
|
if (index !== 1) {
|
||||||
|
await validateWorkflow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换步骤
|
||||||
|
currentStep.value = index;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('步骤切换失败:', error);
|
||||||
|
ElMessage.warning('请先完善当前步骤必填信息');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = useTabs();
|
||||||
|
|
||||||
|
/** 返回列表页 */
|
||||||
|
function handleBack() {
|
||||||
|
// 关闭当前页签
|
||||||
|
tabs.closeCurrentTab();
|
||||||
|
// 跳转到列表页,使用路径, 目前后端的路由 name: 'name'+ menuId
|
||||||
|
router.push({ path: '/ai/workflow' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
workflowId.value = route.params.id as string;
|
||||||
|
actionType.value = route.params.type as string;
|
||||||
|
await initData();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 添加组件卸载前的清理 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 清理所有的引用
|
||||||
|
basicInfoRef.value = undefined;
|
||||||
|
workflowDesignRef.value = undefined;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<!-- 头部导航栏 -->
|
||||||
|
<div
|
||||||
|
class="bg-card absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b px-5"
|
||||||
|
>
|
||||||
|
<!-- 左侧标题 -->
|
||||||
|
<div class="flex w-48 items-center overflow-hidden">
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:arrow-left"
|
||||||
|
class="size-5 flex-shrink-0 cursor-pointer"
|
||||||
|
@click="handleBack"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ml-2.5 truncate text-base"
|
||||||
|
:title="formData.name || '创建AI 工作流'"
|
||||||
|
>
|
||||||
|
{{ formData.name || '创建AI 工作流' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤条 -->
|
||||||
|
<div class="flex h-full flex-1 items-center justify-center">
|
||||||
|
<div class="flex h-full w-96 items-center justify-between">
|
||||||
|
<div
|
||||||
|
v-for="(step, index) in steps"
|
||||||
|
:key="index"
|
||||||
|
class="relative mx-4 flex h-full cursor-pointer items-center"
|
||||||
|
:class="[
|
||||||
|
currentStep === index
|
||||||
|
? 'border-b-2 border-solid border-blue-500 text-blue-500'
|
||||||
|
: 'text-gray-500',
|
||||||
|
]"
|
||||||
|
@click="handleStepClick(index)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-base"
|
||||||
|
:class="[
|
||||||
|
currentStep === index
|
||||||
|
? 'border-blue-500 bg-blue-500 text-white'
|
||||||
|
: 'border-gray-300 bg-white text-gray-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ index + 1 }}
|
||||||
|
</div>
|
||||||
|
<span class="whitespace-nowrap text-base font-bold">
|
||||||
|
{{ step.title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧按钮 -->
|
||||||
|
<div class="flex w-48 items-center justify-end gap-2">
|
||||||
|
<ElButton
|
||||||
|
v-if="actionType === 'update'"
|
||||||
|
type="primary"
|
||||||
|
@click="handleDeploy"
|
||||||
|
>
|
||||||
|
发 布
|
||||||
|
</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleSave">
|
||||||
|
<span v-if="actionType === 'definition'">恢 复</span>
|
||||||
|
<span v-else>保 存</span>
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<ElCard class="mb-4 p-4">
|
||||||
|
<div class="mt-12">
|
||||||
|
<!-- 第一步:基本信息 -->
|
||||||
|
<div v-if="currentStep === 0" class="mx-auto w-4/6">
|
||||||
|
<BasicInfo v-model="formData" ref="basicInfoRef" />
|
||||||
|
</div>
|
||||||
|
<!-- 第二步:表单设计 -->
|
||||||
|
<WorkflowDesign
|
||||||
|
v-if="currentStep === 1"
|
||||||
|
v-model="formData"
|
||||||
|
:provider="llmProvider"
|
||||||
|
ref="workflowDesignRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormRules } from 'element-plus';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
||||||
|
import { ElForm, ElFormItem, ElInput, ElOption, ElSelect } from 'element-plus';
|
||||||
|
|
||||||
|
const modelData = defineModel<any>(); // 创建本地数据副本
|
||||||
|
const formRef = ref(); // 表单引用
|
||||||
|
const rules: FormRules = {
|
||||||
|
code: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
label-position="right"
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
|
<ElFormItem label="流程标识" prop="code" class="mb-5">
|
||||||
|
<ElInput
|
||||||
|
class="w-full"
|
||||||
|
v-model="modelData.code"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入流程标识"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程名称" prop="name" class="mb-5">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.name"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入流程名称"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="状态" prop="status" class="mb-5">
|
||||||
|
<ElSelect
|
||||||
|
class="w-full"
|
||||||
|
v-model="modelData.status"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择状态"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')"
|
||||||
|
:key="dict.value"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程描述" prop="description" class="mb-5">
|
||||||
|
<ElInput v-model="modelData.description" type="textarea" clearable />
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import { inject, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { Tinyflow } from '@vben/plugins/tinyflow';
|
||||||
|
import { isNumber } from '@vben/utils';
|
||||||
|
|
||||||
|
import { ElButton, ElInput, ElOption, ElSelect } from 'element-plus';
|
||||||
|
|
||||||
|
import { testWorkflow } from '#/api/ai/workflow';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
provider: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tinyflowRef = ref<InstanceType<typeof Tinyflow> | null>(null);
|
||||||
|
const workflowData = inject('workflowData') as Ref;
|
||||||
|
const params4Test = ref<any[]>([]);
|
||||||
|
const paramsOfStartNode = ref<any>({});
|
||||||
|
const testResult = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
|
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<string, any> = {};
|
||||||
|
// 加入参数选项方便用户添加非必须参数
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 运行流程 */
|
||||||
|
async function goRun() {
|
||||||
|
try {
|
||||||
|
const val = tinyflowRef.value?.getData();
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
testResult.value = null;
|
||||||
|
|
||||||
|
// 查找start节点
|
||||||
|
const startNode = getStartNode();
|
||||||
|
// 获取参数定义
|
||||||
|
const parameters: any[] = (startNode.data?.parameters as any[]) || [];
|
||||||
|
const paramDefinitions: Record<string, any> = {};
|
||||||
|
parameters.forEach((param: any) => {
|
||||||
|
paramDefinitions[param.name] = param.dataType;
|
||||||
|
});
|
||||||
|
// 参数类型转换
|
||||||
|
const convertedParams: Record<string, any> = {};
|
||||||
|
for (const { key, value } of params4Test.value) {
|
||||||
|
const paramKey = key.trim();
|
||||||
|
if (!paramKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let dataType = paramDefinitions[paramKey];
|
||||||
|
if (!dataType) {
|
||||||
|
dataType = 'String';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
convertedParams[paramKey] = convertParamValue(value, dataType);
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行测试请求
|
||||||
|
testResult.value = await testWorkflow({
|
||||||
|
graph: JSON.stringify(val),
|
||||||
|
params: convertedParams,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
error.value =
|
||||||
|
error.response?.data?.message || '运行失败,请检查参数和网络连接';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取开始节点 */
|
||||||
|
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) {
|
||||||
|
throw new Error('流程缺少开始节点');
|
||||||
|
}
|
||||||
|
return startNode;
|
||||||
|
}
|
||||||
|
throw new Error('请设计流程');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加参数项 */
|
||||||
|
function addParam() {
|
||||||
|
params4Test.value.push({ key: '', value: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除参数项 */
|
||||||
|
function removeParam(index: number) {
|
||||||
|
params4Test.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 类型转换函数 */
|
||||||
|
function convertParamValue(value: string, dataType: string) {
|
||||||
|
if (value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (dataType) {
|
||||||
|
case 'Number': {
|
||||||
|
const num = Number(value);
|
||||||
|
if (!isNumber(num)) throw new Error('非数字格式');
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
case 'String': {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
case 'Boolean': {
|
||||||
|
if (value.toLowerCase() === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value.toLowerCase() === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new Error('必须为 true/false');
|
||||||
|
}
|
||||||
|
case 'Array':
|
||||||
|
case 'Object': {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(`JSON格式错误: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`不支持的类型: ${dataType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
if (!workflowData.value || !tinyflowRef.value) {
|
||||||
|
throw new Error('请设计流程');
|
||||||
|
}
|
||||||
|
workflowData.value = tinyflowRef.value.getData();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative h-[800px] w-full">
|
||||||
|
<Tinyflow
|
||||||
|
v-if="workflowData"
|
||||||
|
ref="tinyflowRef"
|
||||||
|
class-name="custom-class"
|
||||||
|
class="h-full w-full"
|
||||||
|
:data="workflowData"
|
||||||
|
:provider="provider"
|
||||||
|
/>
|
||||||
|
<div class="absolute right-8 top-8">
|
||||||
|
<ElButton
|
||||||
|
@click="testWorkflowModel"
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['ai:workflow:test']"
|
||||||
|
>
|
||||||
|
测试
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Drawer title="工作流测试">
|
||||||
|
<fieldset
|
||||||
|
class="min-inline-size-auto m-0 rounded-lg border border-gray-200 px-3 py-4"
|
||||||
|
>
|
||||||
|
<legend class="ml-2 px-2.5 text-base font-semibold text-gray-600">
|
||||||
|
<h3>运行参数配置</h3>
|
||||||
|
</legend>
|
||||||
|
<div class="p-2">
|
||||||
|
<div
|
||||||
|
class="mb-1 flex items-center justify-around"
|
||||||
|
v-for="(param, index) in params4Test"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<ElSelect class="w-48" v-model="param.key" placeholder="参数名">
|
||||||
|
<ElOption
|
||||||
|
v-for="(value, key) in paramsOfStartNode"
|
||||||
|
:key="key"
|
||||||
|
:value="key"
|
||||||
|
:disabled="!!value?.disabled"
|
||||||
|
:label="value?.description || key"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
<ElInput
|
||||||
|
class="mx-2 w-48"
|
||||||
|
v-model="param.value"
|
||||||
|
placeholder="参数值"
|
||||||
|
/>
|
||||||
|
<ElButton type="danger" circle @click="removeParam(index)">
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:trash" />
|
||||||
|
</template>
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
<ElButton type="primary" plain class="mt-2" @click="addParam">
|
||||||
|
添加参数
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset
|
||||||
|
class="bg-card m-0 mt-10 rounded-lg border border-gray-200 px-3 py-4"
|
||||||
|
>
|
||||||
|
<legend class="ml-2 px-2.5 text-base font-semibold text-gray-600">
|
||||||
|
<h3>运行结果</h3>
|
||||||
|
</legend>
|
||||||
|
<div class="p-2">
|
||||||
|
<div v-if="loading" class="text-primary">执行中...</div>
|
||||||
|
<div v-else-if="error" class="text-danger">{{ error }}</div>
|
||||||
|
<pre
|
||||||
|
v-else-if="testResult"
|
||||||
|
class="max-h-80 overflow-auto whitespace-pre-wrap rounded-lg bg-white p-3 font-mono text-sm leading-5"
|
||||||
|
>
|
||||||
|
{{ JSON.stringify(testResult, null, 2) }}
|
||||||
|
</pre>
|
||||||
|
<div v-else class="text-gray-400">点击运行查看结果</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<ElButton
|
||||||
|
size="large"
|
||||||
|
class="mt-2 w-full bg-green-500 text-white"
|
||||||
|
@click="goRun"
|
||||||
|
>
|
||||||
|
运行流程
|
||||||
|
</ElButton>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
124
apps/web-ele/src/views/ai/workflow/index.vue
Normal file
124
apps/web-ele/src/views/ai/workflow/index.vue
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { AiWorkflowApi } from '#/api/ai/workflow';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ElLoading, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteWorkflow, getWorkflowPage } from '#/api/ai/workflow';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { router } from '#/router';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
/** 刷新表格 */
|
||||||
|
function handleRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建工作流 */
|
||||||
|
function handleCreate() {
|
||||||
|
router.push({
|
||||||
|
name: 'AiWorkflowCreate',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑工作流 */
|
||||||
|
function handleEdit(row: any) {
|
||||||
|
router.push({
|
||||||
|
name: 'AiWorkflowCreate',
|
||||||
|
params: { id: row.id, type: 'update' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除工作流 */
|
||||||
|
async function handleDelete(row: any) {
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteWorkflow(row.id!);
|
||||||
|
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getWorkflowPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<AiWorkflowApi.Workflow>,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="AI 工作流列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('ui.actionTitle.create', ['AI 工作流']),
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['ai:workflow:create'],
|
||||||
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'primary',
|
||||||
|
link: true,
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['ai:workflow:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'danger',
|
||||||
|
link: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['ai:workflow:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user