This commit is contained in:
dengqichen 2025-10-20 22:45:43 +08:00
parent 3315114522
commit 1cd09a9501
5 changed files with 165 additions and 116 deletions

View File

@ -3,8 +3,7 @@ import type { WorkflowDefinition, WorkflowDefinitionQuery, WorkflowCategory } fr
import type { Page } from '@/types/base'; import type { Page } from '@/types/base';
// API 基础路径 // API 基础路径
const DEFINITION_URL = '/api/v1/workflow-definitions'; const DEFINITION_URL = '/api/v1/workflow/definition';
const CATEGORY_URL = '/api/v1/workflow-categories';
/** /**
* *
@ -19,9 +18,15 @@ export const getDefinitionDetail = (id: number) =>
request.get<WorkflowDefinition>(`${DEFINITION_URL}/${id}`); request.get<WorkflowDefinition>(`${DEFINITION_URL}/${id}`);
/** /**
* *
*/ */
export const createDefinition = (data: Partial<WorkflowDefinition>) => export const getPublishedDefinitions = () =>
request.get<WorkflowDefinition[]>(`${DEFINITION_URL}/published`);
/**
*
*/
export const saveDefinition = (data: WorkflowDefinition) =>
request.post<WorkflowDefinition>(DEFINITION_URL, data); request.post<WorkflowDefinition>(DEFINITION_URL, data);
/** /**
@ -40,23 +45,28 @@ export const deleteDefinition = (id: number) =>
* *
*/ */
export const publishDefinition = (id: number) => export const publishDefinition = (id: number) =>
request.post<void>(`${DEFINITION_URL}/${id}/publish`); request.post<void>(`${DEFINITION_URL}/${id}/published`);
/**
*
*/
export const deployDefinition = (id: number) =>
request.post<void>(`${DEFINITION_URL}/${id}/deploy`);
/** /**
* *
*/ */
export const startWorkflowInstance = (key: string, category: string, variables?: Record<string, any>) => export const startWorkflowInstance = (processKey: string, categoryCode: string) =>
request.post<void>(`/api/v1/workflow-instances/start`, { request.post<void>(`/api/v1/workflow/instance/start`, {
definitionKey: key, processKey,
category, businessKey: `${categoryCode}_${Date.now()}`,
variables
}); });
/** /**
* *
*/ */
export const getWorkflowCategories = () => export const getWorkflowCategories = () =>
request.get<WorkflowCategory[]>(CATEGORY_URL); request.get<WorkflowCategory[]>(`${DEFINITION_URL}/categories`);
/** /**
* *

View File

@ -1,21 +1,26 @@
import { BaseResponse, BaseQuery } from '@/types/base'; import { BaseResponse, BaseQuery } from '@/types/base';
// 复用现有的工作流定义类型保持API兼容性 // 复用现有的工作流定义类型保持API兼容性
export interface WorkflowDefinition extends BaseResponse { export interface WorkflowDefinition {
id: number; id: number;
createTime: string | null;
createBy: string | null;
updateTime: string | null;
updateBy: string | null;
version: number;
deleted: boolean;
extraData: any;
name: string; name: string;
key: string; key: string;
description?: string; description: string;
flowVersion?: number;
status?: string;
category: string; category: string;
triggers: string[]; triggers: string[];
flowVersion: number;
bpmnXml: string | null;
status: string;
graph: { graph: {
nodes: WorkflowDefinitionNode[]; nodes: WorkflowDefinitionNode[];
edges: any[]; edges: WorkflowDefinitionEdge[];
};
formConfig: {
formItems: any[];
}; };
formVariablesSchema?: { formVariablesSchema?: {
type: string; type: string;
@ -36,17 +41,31 @@ export interface WorkflowDefinition extends BaseResponse {
}; };
}; };
}; };
bpmnXml?: string;
} }
export interface WorkflowDefinitionNode { export interface WorkflowDefinitionNode {
id: number; id: string;
nodeCode: string; nodeCode: string;
nodeType: string; nodeType: string;
nodeName: string; nodeName: string;
uiVariables: JSON; position: {
panelVariables: JSON; x: number;
localVariables: JSON; y: number;
};
configs: Record<string, any>;
inputMapping: Record<string, any>;
outputMapping: Record<string, any>;
}
export interface WorkflowDefinitionEdge {
id: string;
from: string;
to: string;
name: string;
config: {
type: string;
};
vertices: any[];
} }
export interface WorkflowDefinitionQuery extends BaseQuery { export interface WorkflowDefinitionQuery extends BaseQuery {

View File

@ -3,6 +3,7 @@ import { message } from 'antd';
import * as definitionService from '../../Definition/service'; import * as definitionService from '../../Definition/service';
import type { FlowNode, FlowEdge } from '../types'; import type { FlowNode, FlowEdge } from '../types';
import type { WorkflowDefinition } from '../../Definition/types'; import type { WorkflowDefinition } from '../../Definition/types';
import { NODE_DEFINITIONS } from '../nodes/definitions';
interface LoadedWorkflowData { interface LoadedWorkflowData {
nodes: FlowNode[]; nodes: FlowNode[];
@ -16,40 +17,39 @@ export const useWorkflowLoad = () => {
// 从后端数据转换为React Flow格式 // 从后端数据转换为React Flow格式
const convertToFlowFormat = useCallback((definition: WorkflowDefinition): LoadedWorkflowData => { const convertToFlowFormat = useCallback((definition: WorkflowDefinition): LoadedWorkflowData => {
const nodes: FlowNode[] = (definition.graph?.nodes || []).map(node => ({ const nodes: FlowNode[] = (definition.graph?.nodes || []).map(node => {
// 根据nodeType查找对应的节点定义
const nodeDefinition = NODE_DEFINITIONS.find(def => def.nodeType === node.nodeType);
return {
id: node.id.toString(), id: node.id.toString(),
type: node.nodeType, type: node.nodeType,
position: node.uiVariables?.position || { x: 100, y: 100 }, position: node.position || { x: 100, y: 100 },
data: { data: {
label: node.nodeName, label: node.nodeName,
nodeType: node.nodeType as any, nodeType: node.nodeType as any,
category: 'TASK' as any, // 默认分类可以根据nodeType推导 category: getNodeCategory(node.nodeType) as any,
icon: getNodeIcon(node.nodeType), icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType),
color: getNodeColor(node.nodeType), color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType),
configs: node.panelVariables || {}, configs: node.configs || {},
inputMapping: node.localVariables?.inputMapping || {}, inputMapping: node.inputMapping || {},
outputMapping: node.localVariables?.outputMapping || {}, outputMapping: node.outputMapping || {},
nodeDefinition: { // 添加节点定义引用,用于配置弹窗
nodeCode: node.nodeCode, nodeDefinition
nodeName: node.nodeName,
nodeType: node.nodeType as any,
category: 'TASK' as any,
icon: getNodeIcon(node.nodeType),
color: getNodeColor(node.nodeType)
}
}, },
selected: node.uiVariables?.selected || false, selected: false,
dragging: node.uiVariables?.dragging || false dragging: false
})); };
});
const edges: FlowEdge[] = (definition.graph?.edges || []).map(edge => ({ const edges: FlowEdge[] = (definition.graph?.edges || []).map(edge => ({
id: edge.id, id: edge.id,
source: edge.source, source: edge.from, // 后端使用from字段作为source
target: edge.target, target: edge.to, // 后端使用to字段作为target
type: edge.type || 'default', type: 'default',
animated: edge.animated || true, animated: true,
data: edge.data || { data: {
label: '连接', label: edge.name || '连接',
condition: { condition: {
type: 'DEFAULT', type: 'DEFAULT',
priority: 0 priority: 0
@ -64,32 +64,49 @@ export const useWorkflowLoad = () => {
}; };
}, []); }, []);
// 根据节点类型获取图标 // 根据节点类型获取分类
const getNodeCategory = (nodeType: string): string => {
const categoryMap: Record<string, string> = {
'START_EVENT': 'EVENT',
'END_EVENT': 'EVENT',
'USER_TASK': 'TASK',
'SERVICE_TASK': 'TASK',
'SCRIPT_TASK': 'TASK',
'DEPLOY_NODE': 'TASK',
'JENKINS_BUILD': 'TASK',
'GATEWAY_NODE': 'GATEWAY',
'SUB_PROCESS': 'CONTAINER',
'CALL_ACTIVITY': 'CONTAINER'
};
return categoryMap[nodeType] || 'TASK';
};
// 根据节点类型获取图标(图标名称格式)
const getNodeIcon = (nodeType: string): string => { const getNodeIcon = (nodeType: string): string => {
const iconMap: Record<string, string> = { const iconMap: Record<string, string> = {
'START_EVENT': '▶️', 'START_EVENT': 'play-circle',
'END_EVENT': '⏹️', 'END_EVENT': 'stop-circle',
'USER_TASK': '👤', 'USER_TASK': 'user',
'SERVICE_TASK': '⚙️', 'SERVICE_TASK': 'api',
'SCRIPT_TASK': '📜', 'SCRIPT_TASK': 'code',
'DEPLOY_NODE': '🚀', 'DEPLOY_NODE': 'build',
'JENKINS_BUILD': '🔨', 'JENKINS_BUILD': 'jenkins',
'GATEWAY_NODE': '◇', 'GATEWAY_NODE': 'gateway',
'SUB_PROCESS': '📦', 'SUB_PROCESS': 'container',
'CALL_ACTIVITY': '📞' 'CALL_ACTIVITY': 'phone'
}; };
return iconMap[nodeType] || '📋'; return iconMap[nodeType] || 'api';
}; };
// 根据节点类型获取颜色 // 根据节点类型获取颜色
const getNodeColor = (nodeType: string): string => { const getNodeColor = (nodeType: string): string => {
const colorMap: Record<string, string> = { const colorMap: Record<string, string> = {
'START_EVENT': '#10b981', 'START_EVENT': '#52c41a',
'END_EVENT': '#ef4444', 'END_EVENT': '#ff4d4f',
'USER_TASK': '#6366f1', 'USER_TASK': '#722ed1',
'SERVICE_TASK': '#f59e0b', 'SERVICE_TASK': '#fa8c16',
'SCRIPT_TASK': '#8b5cf6', 'SCRIPT_TASK': '#8b5cf6',
'DEPLOY_NODE': '#06b6d4', 'DEPLOY_NODE': '#1890ff',
'JENKINS_BUILD': '#f97316', 'JENKINS_BUILD': '#f97316',
'GATEWAY_NODE': '#84cc16', 'GATEWAY_NODE': '#84cc16',
'SUB_PROCESS': '#ec4899', 'SUB_PROCESS': '#ec4899',
@ -123,18 +140,24 @@ export const useWorkflowLoad = () => {
const createNewWorkflow = useCallback((): LoadedWorkflowData => { const createNewWorkflow = useCallback((): LoadedWorkflowData => {
const emptyDefinition: WorkflowDefinition = { const emptyDefinition: WorkflowDefinition = {
id: 0, id: 0,
createTime: null,
createBy: null,
updateTime: null,
updateBy: null,
version: 1,
deleted: false,
extraData: null,
name: '新建工作流', name: '新建工作流',
key: `workflow_${Date.now()}`, key: `workflow_${Date.now()}`,
description: '', description: '',
category: 'GENERAL', category: 'GENERAL',
triggers: ['MANUAL'], triggers: [],
flowVersion: 1,
bpmnXml: null,
status: 'DRAFT', status: 'DRAFT',
graph: { graph: {
nodes: [], nodes: [],
edges: [] edges: []
},
formConfig: {
formItems: []
} }
}; };

View File

@ -2,7 +2,6 @@ import { useCallback, useState } from 'react';
import { message } from 'antd'; import { message } from 'antd';
import * as definitionService from '../../Definition/service'; import * as definitionService from '../../Definition/service';
import type { FlowNode, FlowEdge } from '../types'; import type { FlowNode, FlowEdge } from '../types';
import type { WorkflowDefinition } from '../../Definition/types';
interface WorkflowSaveData { interface WorkflowSaveData {
nodes: FlowNode[]; nodes: FlowNode[];
@ -10,6 +9,7 @@ interface WorkflowSaveData {
workflowId?: number; workflowId?: number;
name?: string; name?: string;
description?: string; description?: string;
definitionData?: any; // 原始工作流定义数据
} }
export const useWorkflowSave = () => { export const useWorkflowSave = () => {
@ -25,57 +25,53 @@ export const useWorkflowSave = () => {
// 转换节点和边数据为后端格式 // 转换节点和边数据为后端格式
const graph = { const graph = {
nodes: data.nodes.map(node => ({ nodes: data.nodes.map(node => ({
id: parseInt(node.id) || 0, id: node.id, // 使用React Flow的节点ID
nodeCode: `node_${node.id}`, nodeCode: node.id, // nodeCode与ID相同
nodeType: node.data.nodeType, nodeType: node.data.nodeType,
nodeName: node.data.label, nodeName: node.data.label,
uiVariables: { position: {
position: node.position, x: Math.round(node.position.x),
selected: node.selected || false, y: Math.round(node.position.y)
dragging: node.dragging || false
}, },
panelVariables: node.data.configs || {}, configs: node.data.configs || {},
localVariables: {
inputMapping: node.data.inputMapping || {}, inputMapping: node.data.inputMapping || {},
outputMapping: node.data.outputMapping || {} outputMapping: node.data.outputMapping || {}
}
})), })),
edges: data.edges.map(edge => ({ edges: data.edges.map(edge => ({
id: edge.id, id: edge.id,
source: edge.source, from: edge.source, // 后端使用from字段
target: edge.target, to: edge.target, // 后端使用to字段
type: edge.type || 'default', name: edge.data?.label || "", // 边的名称
animated: edge.animated || false, config: {
data: edge.data || {} type: "sequence" // 固定为sequence类型
},
vertices: [] // 暂时为空数组
})) }))
}; };
const workflowData: Partial<WorkflowDefinition> = { // 构建保存数据 - 完全模仿原始系统的逻辑
name: data.name || '未命名工作流', const workflowData = data.definitionData
description: data.description || '', ? {
graph, // 如果有原始定义数据,展开它并覆盖 graph
// 生成简单的表单配置 ...data.definitionData,
formConfig: { graph
formItems: []
} }
}; : {
// 如果没有原始定义数据,创建新的工作流
let result: WorkflowDefinition; name: data.name || '新建工作流',
description: data.description || '',
if (data.workflowId) {
// 更新现有工作流
result = await definitionService.updateDefinition(data.workflowId, workflowData);
message.success('工作流保存成功');
} else {
// 创建新工作流
result = await definitionService.createDefinition({
...workflowData,
key: `workflow_${Date.now()}`, key: `workflow_${Date.now()}`,
category: 'GENERAL', category: 'GENERAL',
triggers: ['MANUAL'] triggers: [],
} as any); flowVersion: 1,
message.success('工作流创建成功'); bpmnXml: null,
} status: 'DRAFT',
graph
};
// 无论新建还是更新,都调用 saveDefinition (POST 请求)
await definitionService.saveDefinition(workflowData as any);
message.success('工作流保存成功');
setLastSaved(new Date()); setLastSaved(new Date());
setHasUnsavedChanges(false); setHasUnsavedChanges(false);

View File

@ -158,7 +158,8 @@ const WorkflowDesignInner: React.FC = () => {
edges, edges,
workflowId: currentWorkflowId, workflowId: currentWorkflowId,
name: workflowTitle, name: workflowTitle,
description: workflowDefinition?.description || '' description: workflowDefinition?.description || '',
definitionData: workflowDefinition // 传递原始定义数据
}); });
if (success) { if (success) {