This commit is contained in:
dengqichen 2025-10-20 17:34:14 +08:00
parent 978a24790a
commit fa506b4d96
19 changed files with 334 additions and 642 deletions

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import React from 'react';
import { Modal, Form, Input, InputNumber, Radio } from 'antd';
import { Edge } from '@antv/x6';
import { ConditionType, EdgeCondition } from '../types';
import { EdgeCondition } from '../nodes/types';
interface ExpressionModalProps {
visible: boolean;

View File

@ -45,8 +45,17 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
if (nodeDefinition && node) {
// 从节点数据中获取现有配置
const nodeData = node.getData() || {};
// 准备默认的基本信息配置
const defaultConfig = {
nodeName: nodeDefinition.nodeName, // 默认节点名称
nodeCode: nodeDefinition.nodeCode, // 默认节点编码
description: nodeDefinition.description // 默认节点描述
};
// 合并默认值和已保存的配置
setFormData({
config: nodeData.config || {},
config: { ...defaultConfig, ...(nodeData.config || {}) },
inputMapping: nodeData.inputMapping || {},
outputMapping: nodeData.outputMapping || {},
});
@ -92,6 +101,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
children: (
<div style={{ padding: '16px 0' }}>
<BetaSchemaForm
key={`config-${node?.id}-${JSON.stringify(formData.config)}`}
layoutType="Form"
columns={convertJsonSchemaToColumns(nodeDefinition.configSchema as any)}
initialValues={formData.config}
@ -112,6 +122,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
children: (
<div style={{ padding: '16px 0' }}>
<BetaSchemaForm
key={`input-${node?.id}-${JSON.stringify(formData.inputMapping)}`}
layoutType="Form"
columns={convertJsonSchemaToColumns(nodeDefinition.inputMappingSchema as any)}
initialValues={formData.inputMapping}
@ -130,6 +141,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
children: (
<div style={{ padding: '16px 0' }}>
<BetaSchemaForm
key={`output-${node?.id}-${JSON.stringify(formData.outputMapping)}`}
layoutType="Form"
columns={convertJsonSchemaToColumns(nodeDefinition.outputMappingSchema as any)}
initialValues={formData.outputMapping}

View File

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react';
import {Card, Collapse, Tooltip, message} from 'antd';
import type {NodeCategory} from '../types';
import type {NodeCategory} from '../nodes/types';
import {
PlayCircleOutlined,
StopOutlined,

View File

@ -1,6 +1,6 @@
import React from 'react';
import NodePanel from './NodePanel';
import type { NodeDefinitionResponse } from "../nodes/nodeService";
import type { WorkflowNodeDefinition } from "../nodes/nodeService";
interface WorkflowCanvasProps {
graphContainerRef: React.RefObject<HTMLDivElement>;

View File

@ -1,150 +0,0 @@
// 节点端口配置
export const PORT_GROUPS = ['top', 'right', 'bottom', 'left'] as const;
// 转换后端端口配置为X6格式
export const convertPortConfig = (ports: any = {}) => {
const { groups = {} } = ports;
// 转换groups配置
const processedGroups: any = {};
Object.entries(groups).forEach(([key, group]: [string, any]) => {
processedGroups[key] = {
...group,
attrs: {
circle: {
...group.attrs?.circle,
magnet: true,
}
},
position: group.position
};
});
// 如果没有types就用groups的key作为默认的items
const items = Object.keys(groups).map(group => ({
group: group
}));
return {
groups: processedGroups,
items
};
};
// 默认样式配置
export const DEFAULT_STYLES = {
node: {
stroke: '#5F95FF',
strokeWidth: 2,
fill: '#FFF',
},
label: {
fontSize: 12,
fill: '#000000',
},
edge: {
stroke: '#1890ff',
strokeWidth: 2,
targetMarker: {
name: 'classic',
size: 8,
},
},
} as const;
// 默认节点大小
export const NODE_SIZES = {
circle: {
width: 60,
height: 60,
},
rectangle: {
width: 100,
height: 50,
},
diamond: {
width: 60,
height: 60,
},
} as const;
// 网格配置
export const GRID_CONFIG = {
size: 10,
visible: true,
type: 'dot',
args: {
color: '#a0a0a0',
thickness: 1,
},
} as const;
// 连接配置
export const CONNECTING_CONFIG = {
snap: true,
allowBlank: false,
allowLoop: false,
highlight: true,
connector: 'rounded',
connectionPoint: 'boundary',
router: {
name: 'manhattan'
},
validateConnection({
sourceMagnet,
targetMagnet
}: any) {
if (!sourceMagnet || !targetMagnet) {
return false;
}
return true;
}
} as const;
// 高亮配置
export const HIGHLIGHTING_CONFIG = {
magnetAvailable: {
name: 'stroke',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: '#52c41a',
},
},
},
} as const;
// 节点注册配置
export const NODE_REGISTRY_CONFIG = {
circle: {
inherit: 'circle',
width: NODE_SIZES.circle.width,
height: NODE_SIZES.circle.height,
attrs: {
body: DEFAULT_STYLES.node,
label: DEFAULT_STYLES.label,
},
},
rectangle: {
inherit: 'rect',
width: NODE_SIZES.rectangle.width,
height: NODE_SIZES.rectangle.height,
attrs: {
body: DEFAULT_STYLES.node,
label: DEFAULT_STYLES.label,
},
},
diamond: {
inherit: 'polygon',
width: NODE_SIZES.diamond.width,
height: NODE_SIZES.diamond.height,
attrs: {
body: {
...DEFAULT_STYLES.node,
refPoints: '0,10 10,0 20,10 10,20',
},
label: DEFAULT_STYLES.label,
},
},
} as const;

View File

@ -34,7 +34,9 @@ export const useWorkflowData = () => {
// 加载工作流定义详情
const loadDefinitionDetail = useCallback(async (graphInstance: Graph, definitionId: string) => {
try {
console.log('正在加载工作流定义详情, ID:', definitionId);
const response = await getDefinitionDetail(Number(definitionId));
console.log('工作流定义详情加载成功:', response);
setTitle(`工作流设计 - ${response.name}`);
setDefinitionData(response);
@ -142,52 +144,18 @@ export const useWorkflowData = () => {
}
}, [nodeDefinitions]);
// 合并 LocalVariables Schemas
const mergeLocalVariablesSchemas = (schemas: any[]) => {
const mergedSchema = {
type: 'object',
properties: {},
required: []
};
schemas.forEach(schema => {
if (!schema) return;
// 合并 properties
if (schema.properties) {
Object.entries(schema.properties).forEach(([key, property]) => {
if ((mergedSchema.properties as any)[key]) {
if (JSON.stringify((mergedSchema.properties as any)[key]) !== JSON.stringify(property)) {
console.warn(`属性 ${key} 在不同节点中定义不一致,使用第一个定义`);
}
} else {
(mergedSchema.properties as any)[key] = property;
}
});
}
// 合并 required 字段
if (schema.required) {
schema.required.forEach((field: string) => {
if (!(mergedSchema.required as any).includes(field)) {
(mergedSchema.required as any).push(field);
}
});
}
});
return mergedSchema;
};
// 保存工作流
const saveWorkflow = useCallback(async (graph: Graph) => {
if (!graph) {
console.error('Graph 实例为空');
message.error('图形实例不存在,无法保存');
return;
}
if (!definitionData) {
console.error('definitionData 为空');
console.error('definitionData 为空 - 工作流定义数据未加载');
message.error('工作流定义数据未加载,请刷新页面重试');
return;
}
@ -199,10 +167,9 @@ export const useWorkflowData = () => {
return;
}
// 获取所有节点和边的数据
// 获取所有节点和边的数据 - 只保存业务数据不保存UI配置
const nodes = graph.getNodes().map(node => {
const nodeData = node.getData();
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeData.nodeType);
const position = node.getPosition();
return {
@ -210,14 +177,10 @@ export const useWorkflowData = () => {
nodeCode: nodeData.nodeCode,
nodeType: nodeData.nodeType,
nodeName: nodeData.nodeName,
position,
uiConfig: {
...(nodeDefinition?.uiConfig || {}),
position
},
config: nodeData.config || {},
inputMapping: nodeData.inputMapping || {},
outputMapping: nodeData.outputMapping || {}
position, // 只保存位置信息
config: nodeData.config || {}, // 节点配置数据
inputMapping: nodeData.inputMapping || {}, // 输入映射数据
outputMapping: nodeData.outputMapping || {} // 输出映射数据
};
});
@ -238,24 +201,13 @@ export const useWorkflowData = () => {
};
});
// 收集并合并所有节点的输入映射Schema (用于全局变量)
const allInputSchemas = nodes
.map(node => {
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === node.nodeType);
// 检查是否为可配置节点并有输入映射Schema
return 'inputMappingSchema' in nodeDefinition! ? nodeDefinition.inputMappingSchema : null;
})
.filter(schema => schema); // 过滤掉空值
const mergedLocalSchema = mergeLocalVariablesSchemas(allInputSchemas);
// 构建保存数据
// 构建保存数据 - 只保存图形结构不保存Schema配置
const saveData = {
...definitionData,
graph: {
nodes,
edges
},
localVariablesSchema: mergedLocalSchema
}
};
// 调用保存接口

View File

@ -2,7 +2,7 @@ import { useState, useCallback } from 'react';
import { Cell, Edge } from '@antv/x6';
import { message } from 'antd';
import type { WorkflowNodeDefinition } from '../nodes/types';
import type { EdgeCondition } from '../types';
import type { EdgeCondition } from '../nodes/types';
/**
* Hook

View File

@ -1,17 +1,20 @@
.workflow-design {
position: relative;
height: calc(100vh - 184px); // 120px + 24px * 2 + 16px
height: calc(100vh - 184px);
display: flex;
flex-direction: column;
background: #fff;
background: #f8fafc;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
.header {
padding: 16px;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
background: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
.back-button {
margin-right: 16px;
@ -35,11 +38,13 @@
.sidebar {
width: 280px;
flex-shrink: 0;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #f0f0f0;
display: flex;
flex-direction: column;
overflow: hidden; // 防止sidebar本身出现滚动条
overflow: hidden;
:global {
.ant-collapse {
@ -69,6 +74,7 @@
border-radius: 4px;
cursor: move;
transition: all 0.3s;
background: #ffffff;
&:hover {
background: #f5f5f5;
@ -88,7 +94,7 @@
position: relative;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: #f5f5f5;
background: #ffffff;
overflow: hidden;
.workflow-canvas {

View File

@ -64,7 +64,20 @@ const WorkflowDesign: React.FC = () => {
// 加载工作流数据
useEffect(() => {
console.log('工作流数据加载检查:', {
hasGraph: !!graph,
hasId: !!id,
id,
isNodeDefinitionsLoaded
});
if (!id) {
console.error('工作流ID缺失无法加载定义数据');
return;
}
if (graph && id && isNodeDefinitionsLoaded) {
console.log('开始加载工作流定义详情:', id);
loadDefinitionDetail(graph, id);
}
}, [graph, id, isNodeDefinitionsLoaded, loadDefinitionDetail]);

View File

@ -1,76 +0,0 @@
/**
*
* Schema格式和运行时key/value格式之间转换
*/
import {
ConfigurableNodeDefinition,
NodeInstanceData,
NodeFormData,
WorkflowNodeDefinition,
UIConfig
} from './types';
export class NodeDataConverter {
/**
*
*/
static isConfigurableNode(node: WorkflowNodeDefinition): node is ConfigurableNodeDefinition {
return 'inputMappingSchema' in node || 'outputMappingSchema' in node;
}
/**
*
*/
static convertToSaveFormat(
nodeDefinition: WorkflowNodeDefinition,
formData: NodeFormData,
position: { x: number; y: number },
uiConfig: UIConfig
): NodeInstanceData {
const baseData = {
nodeCode: nodeDefinition.nodeCode,
nodeName: nodeDefinition.nodeName,
nodeType: nodeDefinition.nodeType,
category: nodeDefinition.category,
description: nodeDefinition.description,
position,
uiConfig,
config: formData.config || {},
};
// 如果是可配置节点,添加输入输出映射
if (this.isConfigurableNode(nodeDefinition)) {
return {
...baseData,
inputMapping: formData.inputMapping || {},
outputMapping: formData.outputMapping || {}
};
}
return baseData;
}
/**
*
*/
static convertToFormFormat(
nodeDefinition: WorkflowNodeDefinition,
savedData: NodeInstanceData
): NodeFormData {
const formData: NodeFormData = {
config: savedData.config || {}
};
// 如果是可配置节点,添加输入输出映射
if (this.isConfigurableNode(nodeDefinition)) {
return {
...formData,
inputMapping: savedData.inputMapping || {},
outputMapping: savedData.outputMapping || {}
};
}
return formData;
}
}

View File

@ -11,12 +11,12 @@ export const DeployNode: ConfigurableNodeDefinition = {
category: NodeCategory.TASK,
description: "执行应用构建和部署任务",
// UI 配置 - 节点在画布上的显示样式
// UI 配置 - 节点在画布上的显示样式(现代化设计)
uiConfig: {
shape: 'rect',
size: {
width: 120,
height: 60
size: {
width: 160,
height: 80
},
style: {
fill: '#1890ff',
@ -27,24 +27,44 @@ export const DeployNode: ConfigurableNodeDefinition = {
},
ports: {
groups: {
// 输入端口 - 现代化样式
in: {
position: 'left',
attrs: {
circle: {
r: 4,
fill: '#fff',
stroke: '#1890ff'
}
attrs: {
circle: {
r: 7,
fill: '#ffffff',
stroke: '#3b82f6',
strokeWidth: 2.5,
// 现代化端口样式
filter: 'drop-shadow(0 2px 4px rgba(59, 130, 246, 0.3))',
// 端口悬浮效果
':hover': {
r: 8,
fill: '#dbeafe',
stroke: '#2563eb'
}
}
}
},
// 输出端口 - 现代化样式
out: {
position: 'right',
attrs: {
circle: {
r: 4,
fill: '#fff',
stroke: '#1890ff'
}
attrs: {
circle: {
r: 7,
fill: '#ffffff',
stroke: '#3b82f6',
strokeWidth: 2.5,
// 现代化端口样式
filter: 'drop-shadow(0 2px 4px rgba(59, 130, 246, 0.3))',
// 端口悬浮效果
':hover': {
r: 8,
fill: '#dbeafe',
stroke: '#2563eb'
}
}
}
}
}
@ -61,17 +81,20 @@ export const DeployNode: ConfigurableNodeDefinition = {
nodeName: {
type: "string",
title: "节点名称",
description: "节点在流程图中显示的名称"
description: "节点在流程图中显示的名称",
default: "构建任务"
},
nodeCode: {
type: "string",
title: "节点编码",
description: "节点的唯一标识符"
description: "节点的唯一标识符",
default: "DEPLOY_NODE"
},
description: {
type: "string",
title: "节点描述",
description: "节点的详细说明"
description: "节点的详细说明",
default: "执行应用构建和部署任务"
},
// 节点配置
buildCommand: {

View File

@ -11,30 +11,30 @@ export const EndEventNode: BaseNodeDefinition = {
category: NodeCategory.EVENT,
description: "工作流的结束节点",
// UI 配置 - 节点在画布上的显示样式
// UI 配置 - 节点在画布上的显示样式(现代化设计)
uiConfig: {
shape: 'circle',
size: {
width: 40,
height: 40
width: 60,
height: 60
},
style: {
fill: '#f5222d',
stroke: '#cf1322',
strokeWidth: 1,
strokeWidth: 2,
icon: 'stop',
iconColor: '#fff'
},
ports: {
groups: {
// 结束节点只有输入端口
// 结束节点只有输入端口 - 现代化样式
in: {
position: 'left',
attrs: {
circle: {
r: 4,
fill: '#fff',
stroke: '#f5222d'
stroke: '#f5222d'
}
}
}
@ -51,17 +51,20 @@ export const EndEventNode: BaseNodeDefinition = {
nodeName: {
type: "string",
title: "节点名称",
description: "节点在流程图中显示的名称"
description: "节点在流程图中显示的名称",
default: "结束"
},
nodeCode: {
type: "string",
title: "节点编码",
description: "节点的唯一标识符"
description: "节点的唯一标识符",
default: "END_EVENT"
},
description: {
type: "string",
title: "节点描述",
description: "节点的详细说明"
description: "节点的详细说明",
default: "工作流的结束节点"
}
},
required: ["nodeName", "nodeCode"]

View File

@ -1,69 +1,72 @@
import { BaseNodeDefinition, NodeType, NodeCategory } from '../types';
import {BaseNodeDefinition, NodeType, NodeCategory} from '../types';
/**
*
*
*/
export const StartEventNode: BaseNodeDefinition = {
nodeCode: "START_EVENT",
nodeName: "开始",
nodeType: NodeType.START_EVENT,
category: NodeCategory.EVENT,
description: "工作流的起始节点",
// UI 配置 - 节点在画布上的显示样式
nodeCode: "START_EVENT",
nodeName: "开始",
nodeType: NodeType.START_EVENT,
category: NodeCategory.EVENT,
description: "工作流的起始节点",
// UI 配置 - 节点在画布上的显示样式(现代化设计)
uiConfig: {
shape: 'circle',
size: {
width: 40,
height: 40
width: 60,
height: 60
},
style: {
fill: '#52c41a',
stroke: '#389e08',
strokeWidth: 1,
strokeWidth: 2,
icon: 'play-circle',
iconColor: '#fff'
},
ports: {
groups: {
// 开始节点只有输出端口
// 开始节点只有输出端口 - 现代化样式
out: {
position: 'right',
attrs: {
circle: {
r: 4,
fill: '#fff',
stroke: '#52c41a'
stroke: '#52c41a'
}
}
}
}
}
},
// 基本配置Schema - 用于生成"基本配置"TAB包含基本信息
configSchema: {
type: "object",
title: "基本配置",
description: "节点的基本配置信息",
properties: {
nodeName: {
type: "string",
title: "节点名称",
description: "节点在流程图中显示的名称"
},
nodeCode: {
type: "string",
title: "节点编码",
description: "节点的唯一标识符"
},
description: {
type: "string",
title: "节点描述",
description: "节点的详细说明"
}
},
required: ["nodeName", "nodeCode"]
}
// 基本配置Schema - 用于生成"基本配置"TAB包含基本信息
configSchema: {
type: "object",
title: "基本配置",
description: "节点的基本配置信息",
properties: {
nodeName: {
type: "string",
title: "节点名称",
description: "节点在流程图中显示的名称",
default: "开始"
},
nodeCode: {
type: "string",
title: "节点编码",
description: "节点的唯一标识符",
default: "START_EVENT"
},
description: {
type: "string",
title: "节点描述",
description: "节点的详细说明",
default: "工作流的起始节点"
}
},
required: ["nodeName", "nodeCode"]
}
};

View File

@ -1,76 +1,23 @@
import { WorkflowNodeDefinition, NodeCategory, NodeType } from '../types';
import { NodeDataConverter } from '../NodeDataConverter';
import { DeployNode } from './DeployNode';
import { StartEventNode } from './StartEventNode';
import { EndEventNode } from './EndEventNode';
import {WorkflowNodeDefinition, ConfigurableNodeDefinition, NodeCategory, NodeType} from '../types';
import {DeployNode} from './DeployNode';
import {StartEventNode} from './StartEventNode';
import {EndEventNode} from './EndEventNode';
/**
*
*/
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
StartEventNode,
EndEventNode,
DeployNode,
// 在这里添加更多节点定义
StartEventNode,
EndEventNode,
DeployNode,
// 在这里添加更多节点定义
];
/**
*
*/
export const getNodeDefinition = (nodeType: NodeType): WorkflowNodeDefinition | undefined => {
return NODE_DEFINITIONS.find(node => node.nodeType === nodeType);
};
/**
*
*/
export const getNodeDefinitionByCode = (nodeCode: string): WorkflowNodeDefinition | undefined => {
return NODE_DEFINITIONS.find(node => node.nodeCode === nodeCode);
};
/**
*
*/
export const getNodesByCategory = (category: NodeCategory): WorkflowNodeDefinition[] => {
return NODE_DEFINITIONS.filter(node => node.category === category);
};
/**
*
*/
export const getNodesByCategories = (): Record<NodeCategory, WorkflowNodeDefinition[]> => {
return NODE_DEFINITIONS.reduce((acc, node) => {
if (!acc[node.category]) {
acc[node.category] = [];
}
acc[node.category].push(node);
return acc;
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
};
/**
*
*/
export const getEnabledNodes = (): WorkflowNodeDefinition[] => {
// 目前所有节点都是启用的后续可以添加enabled字段
return NODE_DEFINITIONS;
};
/**
*
*/
export const isConfigurableNode = NodeDataConverter.isConfigurableNode;
/**
*
*/
export {
StartEventNode,
EndEventNode,
DeployNode
StartEventNode,
EndEventNode,
DeployNode
};
/**
*
*/
export { NodeDataConverter };

View File

@ -2,58 +2,16 @@
* - 访
*/
import {
NODE_DEFINITIONS,
getNodeDefinition,
getNodesByCategory,
getNodesByCategories
} from './definitions';
import {
WorkflowNodeDefinition,
NodeType,
NodeCategory
} from './types';
import {NODE_DEFINITIONS} from './definitions';
import {WorkflowNodeDefinition} from './types';
/**
*
*/
export const getNodeDefinitionList = async (): Promise<WorkflowNodeDefinition[]> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(NODE_DEFINITIONS);
}, 10);
});
};
/**
*
*/
export const getNodeDefinitionsGroupedByCategory = async (): Promise<Record<NodeCategory, WorkflowNodeDefinition[]>> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(getNodesByCategories());
}, 10);
});
};
/**
*
*/
export const getNodeDefinitionByType = async (nodeType: NodeType): Promise<WorkflowNodeDefinition | undefined> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(getNodeDefinition(nodeType));
}, 10);
});
};
/**
*
*/
export const getNodeDefinitionsByCategory = async (category: NodeCategory): Promise<WorkflowNodeDefinition[]> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(getNodesByCategory(category));
}, 10);
});
return new Promise((resolve) => {
setTimeout(() => {
resolve(NODE_DEFINITIONS);
}, 10);
});
};

View File

@ -1,4 +1,22 @@
import { BaseResponse } from "@/types/base";
// 节点分类 (从 ../types.ts 合并)
export enum NodeCategory {
EVENT = 'EVENT',
TASK = 'TASK',
GATEWAY = 'GATEWAY',
CONTAINER = 'CONTAINER'
}
// 条件类型
export type ConditionType = 'EXPRESSION' | 'SCRIPT' | 'DEFAULT';
// 边的条件配置
export interface EdgeCondition {
type: ConditionType;
expression?: string;
script?: string;
priority: number;
}
// JSON Schema 定义
export interface JSONSchema {
@ -19,6 +37,13 @@ export interface PortStyle {
r: number;
fill: string;
stroke: string;
strokeWidth?: number; // 新增:端口描边宽度
filter?: string; // 新增:端口滤镜效果
':hover'?: { // 新增:端口悬浮状态
r?: number;
fill?: string;
stroke?: string;
};
}
export interface PortAttributes {
@ -45,6 +70,24 @@ export interface NodeStyle {
stroke: string;
iconColor: string;
strokeWidth: number;
// 现代化样式属性
iconSize?: number; // 新增:图标大小
borderRadius?: string; // 新增:圆角
boxShadow?: string; // 新增:阴影
transition?: string; // 新增:过渡效果
fontSize?: string; // 新增:字体大小
fontWeight?: string; // 新增:字体粗细
fontFamily?: string; // 新增:字体族
':hover'?: { // 新增:悬浮状态
fill?: string;
stroke?: string;
boxShadow?: string;
transform?: string;
};
':active'?: { // 新增:激活状态
transform?: string;
boxShadow?: string;
};
}
export interface UIConfig {
@ -59,23 +102,17 @@ export interface UIConfig {
// 节点类型和分类(保持原有的枚举格式)
export enum NodeType {
START_EVENT = 'START_EVENT',
END_EVENT = 'END_EVENT',
USER_TASK = 'USER_TASK',
SERVICE_TASK = 'SERVICE_TASK',
SCRIPT_TASK = 'SCRIPT_TASK',
DEPLOY_NODE = 'DEPLOY_NODE',
GATEWAY_NODE = 'GATEWAY_NODE',
SUB_PROCESS = 'SUB_PROCESS',
CALL_ACTIVITY = 'CALL_ACTIVITY'
START_EVENT = 'START_EVENT',
END_EVENT = 'END_EVENT',
USER_TASK = 'USER_TASK',
SERVICE_TASK = 'SERVICE_TASK',
SCRIPT_TASK = 'SCRIPT_TASK',
DEPLOY_NODE = 'DEPLOY_NODE',
GATEWAY_NODE = 'GATEWAY_NODE',
SUB_PROCESS = 'SUB_PROCESS',
CALL_ACTIVITY = 'CALL_ACTIVITY'
}
export enum NodeCategory {
EVENT = 'EVENT',
TASK = 'TASK',
GATEWAY = 'GATEWAY',
CONTAINER = 'CONTAINER'
}
// 基础节点定义(只有基本配置)
export interface BaseNodeDefinition {
@ -104,37 +141,13 @@ export interface NodeInstanceData {
nodeType: NodeType;
category: NodeCategory;
description?: string;
// 运行时数据key/value格式
config?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
configs?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
inputMapping?: Record<string, any>;
outputMapping?: Record<string, any>;
// UI位置信息
position: { x: number; y: number };
uiConfig: UIConfig; // 包含运行时可能更新的UI配置如位置
}
// 兼容现有API的响应格式
export interface NodeDefinitionResponse extends BaseResponse {
nodeCode: string;
nodeName: string;
nodeType: NodeType;
category: NodeCategory;
description: string;
uiConfig: UIConfig | null;
configSchema?: JSONSchema | null; // 基本配置Schema
// 兼容字段(保持现有组件正常工作)
panelVariablesSchema?: JSONSchema | null;
localVariablesSchema?: JSONSchema | null;
panelVariables?: Record<string, any>;
localVariables?: Record<string, any>;
}
// 数据转换器工具类型
export interface NodeFormData {
config?: Record<string, any>; // 基本配置表单数据(包含基本信息+节点配置)
inputMapping?: Record<string, any>;
outputMapping?: Record<string, any>;
}

View File

@ -1,13 +0,0 @@
// 节点分类
export type NodeCategory = 'EVENT' | 'TASK' | 'GATEWAY' | 'CONTAINER';
// 条件类型
export type ConditionType = 'EXPRESSION' | 'SCRIPT' | 'DEFAULT';
// 边的条件配置
export interface EdgeCondition {
type: ConditionType;
expression?: string;
script?: string;
priority: number;
}

View File

@ -44,7 +44,7 @@ export class EventRegistrar {
PortManager.showPorts(node.id);
});
this.graph.on('node:mouseleave', ({node}: any) => {
this.graph.on('node:mouseleave', ({node}) => {
NodeStyleManager.resetNodeStyle(node);
PortManager.hidePorts(node.id);
});

View File

@ -1,135 +1,136 @@
import { Graph } from '@antv/x6';
import { WorkflowNodeDefinition, NodeInstanceData } from '../nodes/types';
import {Graph} from '@antv/x6';
import {WorkflowNodeDefinition, NodeInstanceData} from '../nodes/types';
/**
*
*/
export const createNodeFromDefinition = (
graph: Graph,
nodeDefinition: WorkflowNodeDefinition,
position: { x: number; y: number }
graph: Graph,
nodeDefinition: WorkflowNodeDefinition,
position: { x: number; y: number }
) => {
const { uiConfig } = nodeDefinition;
// 根据形状类型设置正确的 shape
let shape = 'rect';
if (uiConfig.shape === 'circle') {
shape = 'circle';
} else if (uiConfig.shape === 'diamond') {
shape = 'polygon';
}
const {uiConfig} = nodeDefinition;
// 创建节点配置
const nodeConfig = {
shape,
x: position.x,
y: position.y,
width: uiConfig.size.width,
height: uiConfig.size.height,
attrs: {
body: {
...uiConfig.style,
...(uiConfig.shape === 'diamond' ? {
refPoints: '0,10 10,0 20,10 10,20',
} : {})
},
label: {
text: nodeDefinition.nodeName,
fontSize: 12,
fill: '#000'
},
},
ports: convertPortConfig(uiConfig.ports),
// 同时设置为props和data方便访问
nodeType: nodeDefinition.nodeType,
nodeCode: nodeDefinition.nodeCode,
data: {
nodeType: nodeDefinition.nodeType,
nodeCode: nodeDefinition.nodeCode,
nodeName: nodeDefinition.nodeName
// 根据形状类型设置正确的 shape
let shape = 'rect';
if (uiConfig.shape === 'circle') {
shape = 'circle';
} else if (uiConfig.shape === 'diamond') {
shape = 'polygon';
}
};
return graph.addNode(nodeConfig);
// 创建节点配置
const nodeConfig = {
shape,
x: position.x,
y: position.y,
width: uiConfig.size.width,
height: uiConfig.size.height,
attrs: {
body: {
...uiConfig.style,
...(uiConfig.shape === 'diamond' ? {
refPoints: '0,10 10,0 20,10 10,20',
} : {})
},
label: {
text: nodeDefinition.nodeName,
fontSize: 12,
fill: '#000'
},
},
ports: convertPortConfig(uiConfig.ports),
// 同时设置为props和data方便访问
nodeType: nodeDefinition.nodeType,
nodeCode: nodeDefinition.nodeCode,
data: {
nodeType: nodeDefinition.nodeType,
nodeCode: nodeDefinition.nodeCode,
nodeName: nodeDefinition.nodeName
}
};
return graph.addNode(nodeConfig);
};
/**
*
*/
export const restoreNodeFromData = (
graph: Graph,
nodeData: NodeInstanceData,
_nodeDefinition: WorkflowNodeDefinition
graph: Graph,
nodeData: NodeInstanceData,
nodeDefinition: WorkflowNodeDefinition
) => {
const { uiConfig } = nodeData;
// 根据形状类型设置正确的 shape
let shape = 'rect';
if (uiConfig.shape === 'circle') {
shape = 'circle';
} else if (uiConfig.shape === 'diamond') {
shape = 'polygon';
}
// 从节点定义中获取UI配置兼容旧数据中的uiConfig
const uiConfig = nodeData.uiConfig || nodeDefinition.uiConfig;
// 创建节点配置
const nodeConfig = {
id: nodeData.nodeCode, // 使用保存的ID
shape,
x: nodeData.position.x,
y: nodeData.position.y,
width: uiConfig.size.width,
height: uiConfig.size.height,
attrs: {
body: {
...uiConfig.style,
...(uiConfig.shape === 'diamond' ? {
refPoints: '0,10 10,0 20,10 10,20',
} : {})
},
label: {
text: nodeData.nodeName,
fontSize: 12,
fill: '#000'
},
},
ports: convertPortConfig(uiConfig.ports),
// 同时设置为props和data方便访问
nodeType: nodeData.nodeType,
nodeCode: nodeData.nodeCode,
data: {
nodeType: nodeData.nodeType,
nodeCode: nodeData.nodeCode,
nodeName: nodeData.nodeName,
config: nodeData.config,
inputMapping: nodeData.inputMapping,
outputMapping: nodeData.outputMapping
// 根据形状类型设置正确的 shape
let shape = 'rect';
if (uiConfig.shape === 'circle') {
shape = 'circle';
} else if (uiConfig.shape === 'diamond') {
shape = 'polygon';
}
};
return graph.addNode(nodeConfig);
// 创建节点配置
const nodeConfig = {
id: nodeData.nodeCode, // 使用保存的ID
shape,
x: nodeData.position.x,
y: nodeData.position.y,
width: uiConfig.size.width,
height: uiConfig.size.height,
attrs: {
body: {
...uiConfig.style,
...(uiConfig.shape === 'diamond' ? {
refPoints: '0,10 10,0 20,10 10,20',
} : {})
},
label: {
text: nodeData.nodeName,
fontSize: 12,
fill: '#000'
},
},
ports: convertPortConfig(uiConfig.ports),
// 同时设置为props和data方便访问
nodeType: nodeData.nodeType,
nodeCode: nodeData.nodeCode,
data: {
nodeType: nodeData.nodeType,
nodeCode: nodeData.nodeCode,
nodeName: nodeData.nodeName,
configs: nodeData.configs,
inputMapping: nodeData.inputMapping,
outputMapping: nodeData.outputMapping
}
};
return graph.addNode(nodeConfig);
};
/**
* X6格式
*/
const convertPortConfig = (ports: any) => {
if (!ports?.groups) return { items: [] };
const groups: any = {};
const items: any[] = [];
if (!ports?.groups) return {items: []};
Object.entries(ports.groups).forEach(([key, group]: [string, any]) => {
groups[key] = {
position: group.position,
attrs: {
circle: {
...group.attrs.circle,
magnet: true,
}
}
};
items.push({ group: key });
});
const groups: any = {};
const items: any[] = [];
return { groups, items };
Object.entries(ports.groups).forEach(([key, group]: [string, any]) => {
groups[key] = {
position: group.position,
attrs: {
circle: {
...group.attrs.circle,
magnet: true,
}
}
};
items.push({group: key});
});
return {groups, items};
};