This commit is contained in:
dengqichen 2025-10-21 12:16:37 +08:00
parent 7bead2a989
commit 33c70b7894
5 changed files with 413 additions and 197 deletions

View File

@ -1,12 +1,26 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { Drawer, Tabs, Button, Space, message } from 'antd'; import { Drawer, Tabs, Button, Space, message } from 'antd';
import { SaveOutlined, ReloadOutlined, CloseOutlined } from '@ant-design/icons'; import { SaveOutlined, ReloadOutlined, CloseOutlined } from '@ant-design/icons';
import { BetaSchemaForm } from '@ant-design/pro-components'; import { FormItem, Input, NumberPicker, Select, FormLayout, Switch } from '@formily/antd-v5';
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils'; import { createForm } from '@formily/core';
import { createSchemaField, FormProvider, ISchema } from '@formily/react';
import type { FlowNode, FlowNodeData } from '../types'; import type { FlowNode, FlowNodeData } from '../types';
import type { WorkflowNodeDefinition } from '../nodes/types'; import type { WorkflowNodeDefinition } from '../nodes/types';
import { isConfigurableNode } from '../nodes/types'; import { isConfigurableNode } from '../nodes/types';
// 创建Schema组件
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
NumberPicker,
Select,
FormLayout,
Switch,
'Input.TextArea': Input.TextArea,
},
});
interface NodeConfigModalProps { interface NodeConfigModalProps {
visible: boolean; visible: boolean;
node: FlowNode | null; node: FlowNode | null;
@ -14,12 +28,6 @@ interface NodeConfigModalProps {
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void; onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
} }
interface FormData {
configs?: Record<string, any>;
inputMapping?: Record<string, any>;
outputMapping?: Record<string, any>;
}
const NodeConfigModal: React.FC<NodeConfigModalProps> = ({ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
visible, visible,
node, node,
@ -28,46 +36,85 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
}) => { }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [activeTab, setActiveTab] = useState('config'); const [activeTab, setActiveTab] = useState('config');
const [formData, setFormData] = useState<FormData>({});
// 获取节点定义 // 获取节点定义
const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null; const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null;
// 创建Formily表单实例
const configForm = useMemo(() => createForm(), []);
const inputForm = useMemo(() => createForm(), []);
const outputForm = useMemo(() => createForm(), []);
// 初始化表单数据
useEffect(() => { useEffect(() => {
if (visible && node && nodeDefinition) { if (visible && node && nodeDefinition) {
// 从节点数据中获取现有配置
const nodeData = node.data || {}; const nodeData = node.data || {};
// 准备默认的基本信息配置 // 准备默认配置
const defaultConfig = { const defaultConfig = {
nodeName: nodeDefinition.nodeName, // 默认节点名称 nodeName: nodeDefinition.nodeName,
nodeCode: nodeDefinition.nodeCode, // 默认节点编码 nodeCode: nodeDefinition.nodeCode,
description: nodeDefinition.description // 默认节点描述 description: nodeDefinition.description
}; };
// 合并默认值和已保存的配置 // 设置表单初始值
setFormData({ configForm.setInitialValues({ ...defaultConfig, ...(nodeData.configs || {}) });
configs: { ...defaultConfig, ...(nodeData.configs || {}) }, configForm.reset();
inputMapping: nodeData.inputMapping || {},
outputMapping: nodeData.outputMapping || {}, if (isConfigurableNode(nodeDefinition)) {
}); inputForm.setInitialValues(nodeData.inputMapping || {});
} else { inputForm.reset();
setFormData({});
outputForm.setInitialValues(nodeData.outputMapping || {});
outputForm.reset();
}
} }
}, [visible, node, nodeDefinition]); }, [visible, node, nodeDefinition, configForm, inputForm, outputForm]);
const handleSubmit = () => { // 递归处理表单值将JSON字符串转换为对象
if (!node) return; const processFormValues = (values: Record<string, any>, schema: ISchema | undefined): Record<string, any> => {
const result: Record<string, any> = {};
if (!schema?.properties || typeof schema.properties !== 'object') return values;
Object.entries(values).forEach(([key, value]) => {
const propSchema = (schema.properties as Record<string, any>)?.[key];
// 如果是object类型且值是字符串尝试解析
if (propSchema?.type === 'object' && typeof value === 'string') {
try {
result[key] = JSON.parse(value);
} catch {
result[key] = value; // 解析失败保持原值
}
} else {
result[key] = value;
}
});
return result;
};
const handleSubmit = async () => {
if (!node || !nodeDefinition) return;
try { try {
setLoading(true); setLoading(true);
// 获取表单值并转换
const configs = processFormValues(configForm.values, nodeDefinition.configSchema);
const inputMapping = isConfigurableNode(nodeDefinition)
? processFormValues(inputForm.values, nodeDefinition.inputMappingSchema)
: {};
const outputMapping = isConfigurableNode(nodeDefinition)
? processFormValues(outputForm.values, nodeDefinition.outputMappingSchema)
: {};
const updatedData: Partial<FlowNodeData> = { const updatedData: Partial<FlowNodeData> = {
// 更新节点标签为配置中的节点名称 label: configs.nodeName || node.data.label,
label: formData.configs?.nodeName || node.data.label, configs,
configs: formData.configs, inputMapping,
inputMapping: formData.inputMapping, outputMapping,
outputMapping: formData.outputMapping,
}; };
onOk(node.id, updatedData); onOk(node.id, updatedData);
@ -83,40 +130,118 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
}; };
const handleReset = () => { const handleReset = () => {
if (nodeDefinition) { configForm.reset();
const defaultConfig = { inputForm.reset();
nodeName: nodeDefinition.nodeName, outputForm.reset();
nodeCode: nodeDefinition.nodeCode, message.info('已重置为初始值');
description: nodeDefinition.description };
// 将JSON Schema转换为Formily Schema扩展配置
const convertToFormilySchema = (jsonSchema: ISchema): ISchema => {
const schema: ISchema = {
type: 'object',
properties: {}
};
if (!jsonSchema.properties || typeof jsonSchema.properties !== 'object') return schema;
Object.entries(jsonSchema.properties as Record<string, any>).forEach(([key, prop]: [string, any]) => {
const field: any = {
title: prop.title || key,
description: prop.description,
'x-decorator': 'FormItem',
'x-decorator-props': {
tooltip: prop.description,
labelCol: 6, // 标签占6列
wrapperCol: 18, // 内容占18列剩余空间
},
}; };
setFormData({
configs: defaultConfig,
inputMapping: {},
outputMapping: {},
});
}
};
const handleConfigChange = (values: Record<string, any>) => { // 根据类型设置组件
setFormData(prev => ({ switch (prop.type) {
...prev, case 'string':
configs: values if (prop.enum) {
})); field['x-component'] = 'Select';
}; field['x-component-props'] = {
style: { width: '100%' }, // 统一宽度
options: prop.enum.map((v: any, i: number) => ({
label: prop.enumNames?.[i] || v,
value: v
}))
};
} else if (prop.format === 'password') {
field['x-component'] = 'Input';
field['x-component-props'] = {
type: 'password',
style: { width: '100%' } // 统一宽度
};
} else {
field['x-component'] = 'Input';
field['x-component-props'] = {
style: { width: '100%' } // 统一宽度
};
}
break;
case 'number':
case 'integer':
field['x-component'] = 'NumberPicker';
field['x-component-props'] = {
min: prop.minimum,
max: prop.maximum,
style: { width: '100%' } // 统一宽度
};
break;
case 'boolean':
field['x-component'] = 'Switch';
break;
case 'object':
field['x-component'] = 'Input.TextArea';
field['x-component-props'] = {
rows: 4,
style: { width: '100%' }, // 统一宽度
placeholder: '请输入JSON格式例如{"key": "value"}'
};
// ✅ 关键修复将object类型的default值转换为JSON字符串
if (prop.default !== undefined && typeof prop.default === 'object') {
field.default = JSON.stringify(prop.default, null, 2);
} else {
field.default = prop.default;
}
// Formily会自动处理object的序列化
field['x-validator'] = (value: any) => {
if (!value) return true;
if (typeof value === 'string') {
try {
JSON.parse(value);
return true;
} catch {
return '请输入有效的JSON格式';
}
}
return true;
};
break;
default:
field['x-component'] = 'Input';
field['x-component-props'] = {
style: { width: '100%' } // 统一宽度
};
}
const handleInputMappingChange = (values: Record<string, any>) => { // 设置默认值非object类型
setFormData(prev => ({ if (prop.type !== 'object' && prop.default !== undefined) {
...prev, field.default = prop.default;
inputMapping: values }
}));
};
const handleOutputMappingChange = (values: Record<string, any>) => { // 设置必填
setFormData(prev => ({ if (Array.isArray(jsonSchema.required) && jsonSchema.required.includes(key)) {
...prev, field.required = true;
outputMapping: values }
}));
(schema.properties as Record<string, any>)[key] = field;
});
return schema;
}; };
if (!nodeDefinition) { if (!nodeDefinition) {
@ -128,18 +253,15 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
{ {
key: 'config', key: 'config',
label: '基本配置', label: '基本配置',
children: ( children: activeTab === 'config' ? (
<div style={{ padding: '16px 0' }}> <div style={{ padding: '16px 0' }}>
<BetaSchemaForm <FormProvider form={configForm}>
key={`configs-${node?.id}-${JSON.stringify(formData.configs)}`} <FormLayout labelCol={6} wrapperCol={18} labelAlign="right" colon={false}>
layoutType="Form" <SchemaField schema={convertToFormilySchema(nodeDefinition.configSchema)} />
columns={convertJsonSchemaToColumns(nodeDefinition.configSchema as any)} </FormLayout>
initialValues={formData.configs} </FormProvider>
onValuesChange={(_, allValues) => handleConfigChange(allValues)}
submitter={false}
/>
</div> </div>
), ) : null,
}, },
]; ];
@ -149,18 +271,15 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
tabItems.push({ tabItems.push({
key: 'input', key: 'input',
label: '输入映射', label: '输入映射',
children: ( children: activeTab === 'input' ? (
<div style={{ padding: '16px 0' }}> <div style={{ padding: '16px 0' }}>
<BetaSchemaForm <FormProvider form={inputForm}>
key={`input-${node?.id}-${JSON.stringify(formData.inputMapping)}`} <FormLayout labelCol={6} wrapperCol={18} labelAlign="right" colon={false}>
layoutType="Form" <SchemaField schema={convertToFormilySchema(nodeDefinition.inputMappingSchema)} />
columns={convertJsonSchemaToColumns(nodeDefinition.inputMappingSchema as any)} </FormLayout>
initialValues={formData.inputMapping} </FormProvider>
onValuesChange={(_, allValues) => handleInputMappingChange(allValues)}
submitter={false}
/>
</div> </div>
), ) : null,
}); });
} }
@ -168,18 +287,15 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
tabItems.push({ tabItems.push({
key: 'output', key: 'output',
label: '输出映射', label: '输出映射',
children: ( children: activeTab === 'output' ? (
<div style={{ padding: '16px 0' }}> <div style={{ padding: '16px 0' }}>
<BetaSchemaForm <FormProvider form={outputForm}>
key={`output-${node?.id}-${JSON.stringify(formData.outputMapping)}`} <FormLayout labelCol={6} wrapperCol={18} labelAlign="right" colon={false}>
layoutType="Form" <SchemaField schema={convertToFormilySchema(nodeDefinition.outputMappingSchema)} />
columns={convertJsonSchemaToColumns(nodeDefinition.outputMappingSchema as any)} </FormLayout>
initialValues={formData.outputMapping} </FormProvider>
onValuesChange={(_, allValues) => handleOutputMappingChange(allValues)}
submitter={false}
/>
</div> </div>
), ) : null,
}); });
} }
} }

View File

@ -3,7 +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'; import { NODE_DEFINITIONS, NodeType, getNodeCategory } from '../nodes';
interface LoadedWorkflowData { interface LoadedWorkflowData {
nodes: FlowNode[]; nodes: FlowNode[];
@ -15,47 +15,88 @@ export const useWorkflowLoad = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [workflowDefinition, setWorkflowDefinition] = useState<WorkflowDefinition | null>(null); const [workflowDefinition, setWorkflowDefinition] = useState<WorkflowDefinition | null>(null);
// 获取已注册的节点类型
const getRegisteredNodeTypes = useCallback((): NodeType[] => {
return NODE_DEFINITIONS.map(def => def.nodeType);
}, []);
// 从后端数据转换为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 registeredTypes = getRegisteredNodeTypes();
// 根据nodeType查找对应的节点定义
const nodeDefinition = NODE_DEFINITIONS.find(def => def.nodeType === node.nodeType); const nodes: FlowNode[] = (definition.graph?.nodes || [])
.filter(node => {
return { // 过滤掉不支持的节点类型
id: node.id.toString(), const isSupported = registeredTypes.includes(node.nodeType as any);
type: node.nodeType, if (!isSupported) {
position: node.position || { x: 100, y: 100 }, console.warn(`节点类型 "${node.nodeType}" 不支持,已跳过`, node);
data: {
label: node.nodeName,
nodeType: node.nodeType as any,
category: getNodeCategory(node.nodeType) as any,
icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType),
color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType),
configs: node.configs || {},
inputMapping: node.inputMapping || {},
outputMapping: node.outputMapping || {},
// 添加节点定义引用,用于配置弹窗
nodeDefinition
},
selected: false,
dragging: false
};
});
const edges: FlowEdge[] = (definition.graph?.edges || []).map(edge => ({
id: edge.id,
source: edge.from, // 后端使用from字段作为source
target: edge.to, // 后端使用to字段作为target
type: 'default',
animated: true,
data: {
label: edge.name || '连接',
condition: {
type: 'DEFAULT',
priority: 0
} }
} return isSupported;
})); })
.map(node => {
// 根据nodeType查找对应的节点定义
const nodeDefinition = NODE_DEFINITIONS.find(def => def.nodeType === node.nodeType);
return {
id: node.id.toString(),
type: node.nodeType,
position: node.position || { x: 100, y: 100 },
data: {
label: node.nodeName,
nodeType: node.nodeType as any,
category: getNodeCategory(node.nodeType) as any,
icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType),
color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType),
configs: node.configs || {},
inputMapping: node.inputMapping || {},
outputMapping: node.outputMapping || {},
// 添加节点定义引用,用于配置弹窗
nodeDefinition
},
selected: false,
dragging: false
};
});
// 创建节点ID集合用于过滤边
const nodeIds = new Set(nodes.map(n => n.id));
const edges: FlowEdge[] = (definition.graph?.edges || [])
.filter(edge => {
// 只保留两端节点都存在的边
const sourceExists = nodeIds.has(edge.from);
const targetExists = nodeIds.has(edge.to);
if (!sourceExists || !targetExists) {
console.warn(`边的节点不存在,已跳过:`, edge);
}
return sourceExists && targetExists;
})
.map(edge => {
const edgeConfig: any = edge.config || {};
const condition = edgeConfig.condition || { type: 'DEFAULT', priority: 10 };
const label = condition.expression || edge.name || '';
return {
id: edge.id,
source: edge.from, // 后端使用from字段作为source
target: edge.to, // 后端使用to字段作为target
type: 'smoothstep',
animated: true,
style: {
stroke: '#94a3b8',
strokeWidth: 2,
},
markerEnd: {
type: 'arrowclosed' as const,
color: '#94a3b8',
},
label,
data: {
label,
condition
}
};
});
return { return {
nodes, nodes,
@ -64,36 +105,19 @@ 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': 'play-circle', [NodeType.START_EVENT]: 'play-circle',
'END_EVENT': 'stop-circle', [NodeType.END_EVENT]: 'stop-circle',
'USER_TASK': 'user', [NodeType.USER_TASK]: 'user',
'SERVICE_TASK': 'api', [NodeType.SERVICE_TASK]: 'api',
'SCRIPT_TASK': 'code', [NodeType.SCRIPT_TASK]: 'code',
'DEPLOY_NODE': 'build', [NodeType.DEPLOY_NODE]: 'build',
'JENKINS_BUILD': 'jenkins', [NodeType.JENKINS_BUILD]: 'jenkins',
'GATEWAY_NODE': 'gateway', [NodeType.GATEWAY_NODE]: 'gateway',
'SUB_PROCESS': 'container', [NodeType.SUB_PROCESS]: 'container',
'CALL_ACTIVITY': 'phone' [NodeType.CALL_ACTIVITY]: 'phone'
}; };
return iconMap[nodeType] || 'api'; return iconMap[nodeType] || 'api';
}; };
@ -101,16 +125,16 @@ export const useWorkflowLoad = () => {
// 根据节点类型获取颜色 // 根据节点类型获取颜色
const getNodeColor = (nodeType: string): string => { const getNodeColor = (nodeType: string): string => {
const colorMap: Record<string, string> = { const colorMap: Record<string, string> = {
'START_EVENT': '#52c41a', [NodeType.START_EVENT]: '#52c41a',
'END_EVENT': '#ff4d4f', [NodeType.END_EVENT]: '#ff4d4f',
'USER_TASK': '#722ed1', [NodeType.USER_TASK]: '#722ed1',
'SERVICE_TASK': '#fa8c16', [NodeType.SERVICE_TASK]: '#fa8c16',
'SCRIPT_TASK': '#8b5cf6', [NodeType.SCRIPT_TASK]: '#8b5cf6',
'DEPLOY_NODE': '#1890ff', [NodeType.DEPLOY_NODE]: '#1890ff',
'JENKINS_BUILD': '#f97316', [NodeType.JENKINS_BUILD]: '#52c41a',
'GATEWAY_NODE': '#84cc16', [NodeType.GATEWAY_NODE]: '#84cc16',
'SUB_PROCESS': '#ec4899', [NodeType.SUB_PROCESS]: '#ec4899',
'CALL_ACTIVITY': '#14b8a6' [NodeType.CALL_ACTIVITY]: '#14b8a6'
}; };
return colorMap[nodeType] || '#6b7280'; return colorMap[nodeType] || '#6b7280';
}; };
@ -125,7 +149,15 @@ export const useWorkflowLoad = () => {
const flowData = convertToFlowFormat(definition); const flowData = convertToFlowFormat(definition);
message.success(`工作流 "${definition.name}" 加载成功`); // 提示不支持的节点
const totalNodes = definition.graph?.nodes?.length || 0;
const loadedNodes = flowData.nodes.length;
if (totalNodes > loadedNodes) {
message.warning(`工作流加载成功,但有 ${totalNodes - loadedNodes} 个节点类型不支持,已跳过`);
} else {
message.success(`工作流 "${definition.name}" 加载成功`);
}
return flowData; return flowData;
} catch (error) { } catch (error) {
console.error('加载工作流失败:', error); console.error('加载工作流失败:', error);

View File

@ -478,38 +478,63 @@ const WorkflowDesignInner: React.FC = () => {
// 键盘快捷键支持 // 键盘快捷键支持
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
// 检查焦点是否在输入框、文本域或可编辑元素内
const target = e.target as HTMLElement;
const tagName = target.tagName?.toUpperCase();
const isInputElement = tagName === 'INPUT' ||
tagName === 'TEXTAREA' ||
target.isContentEditable ||
target.getAttribute('contenteditable') === 'true';
const isInDrawer = target.closest('.ant-drawer-body') !== null;
const isInModal = target.closest('.ant-modal') !== null;
// 在抽屉或模态框内,且在输入元素中时,允许原生行为
const shouldSkipShortcut = isInputElement || isInDrawer || isInModal;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const ctrlKey = isMac ? e.metaKey : e.ctrlKey; const ctrlKey = isMac ? e.metaKey : e.ctrlKey;
// Ctrl+Z / Cmd+Z - 撤销 // Ctrl+Z / Cmd+Z - 撤销(仅在画布区域)
if (ctrlKey && e.key === 'z' && !e.shiftKey) { if (ctrlKey && e.key === 'z' && !e.shiftKey) {
e.preventDefault(); if (!shouldSkipShortcut) {
handleUndo(); e.preventDefault();
handleUndo();
}
} }
// Ctrl+Shift+Z / Cmd+Shift+Z - 重做 // Ctrl+Shift+Z / Cmd+Shift+Z - 重做(仅在画布区域)
else if (ctrlKey && e.key === 'z' && e.shiftKey) { else if (ctrlKey && e.key === 'z' && e.shiftKey) {
e.preventDefault(); if (!shouldSkipShortcut) {
handleRedo(); e.preventDefault();
handleRedo();
}
} }
// Ctrl+C / Cmd+C - 复制 // Ctrl+C / Cmd+C - 复制节点(仅在画布区域)
else if (ctrlKey && e.key === 'c') { else if (ctrlKey && e.key === 'c') {
e.preventDefault(); if (!shouldSkipShortcut) {
handleCopy(); e.preventDefault();
handleCopy();
}
} }
// Ctrl+V / Cmd+V - 粘贴 // Ctrl+V / Cmd+V - 粘贴节点(仅在画布区域)
else if (ctrlKey && e.key === 'v') { else if (ctrlKey && e.key === 'v') {
e.preventDefault(); if (!shouldSkipShortcut) {
handlePaste(); e.preventDefault();
handlePaste();
}
} }
// Ctrl+A / Cmd+A - 全选 // Ctrl+A / Cmd+A - 全选节点(仅在画布区域)
else if (ctrlKey && e.key === 'a') { else if (ctrlKey && e.key === 'a') {
e.preventDefault(); if (!shouldSkipShortcut) {
handleSelectAll(); e.preventDefault();
handleSelectAll();
}
} }
// Delete / Backspace - 删除 // Delete / Backspace - 删除节点(仅在画布区域)
else if (e.key === 'Delete' || e.key === 'Backspace') { else if (e.key === 'Delete' || e.key === 'Backspace') {
e.preventDefault(); if (!shouldSkipShortcut) {
handleDelete(); e.preventDefault();
handleDelete();
}
} }
}; };

View File

@ -6,23 +6,46 @@ export enum NodeCategory {
CONTAINER = 'CONTAINER' CONTAINER = 'CONTAINER'
} }
// 节点类型 // 节点类型(完整定义,包含所有可能的类型)
export enum NodeType { export enum NodeType {
START_EVENT = 'START_EVENT', START_EVENT = 'START_EVENT',
END_EVENT = 'END_EVENT', END_EVENT = 'END_EVENT',
USER_TASK = 'USER_TASK',
SERVICE_TASK = 'SERVICE_TASK',
SCRIPT_TASK = 'SCRIPT_TASK', SCRIPT_TASK = 'SCRIPT_TASK',
DEPLOY_NODE = 'DEPLOY_NODE',
JENKINS_BUILD = 'JENKINS_BUILD', JENKINS_BUILD = 'JENKINS_BUILD',
GATEWAY_NODE = 'GATEWAY_NODE', GATEWAY_NODE = 'GATEWAY_NODE',
SUB_PROCESS = 'SUB_PROCESS',
CALL_ACTIVITY = 'CALL_ACTIVITY'
} }
// 节点类型到分类的映射
export const NODE_CATEGORY_MAP: Record<NodeType, NodeCategory> = {
[NodeType.START_EVENT]: NodeCategory.EVENT,
[NodeType.END_EVENT]: NodeCategory.EVENT,
[NodeType.USER_TASK]: NodeCategory.TASK,
[NodeType.SERVICE_TASK]: NodeCategory.TASK,
[NodeType.SCRIPT_TASK]: NodeCategory.TASK,
[NodeType.DEPLOY_NODE]: NodeCategory.TASK,
[NodeType.JENKINS_BUILD]: NodeCategory.TASK,
[NodeType.GATEWAY_NODE]: NodeCategory.GATEWAY,
[NodeType.SUB_PROCESS]: NodeCategory.CONTAINER,
[NodeType.CALL_ACTIVITY]: NodeCategory.CONTAINER,
};
// 获取节点分类的工具函数
export const getNodeCategory = (nodeType: NodeType | string): NodeCategory => {
return NODE_CATEGORY_MAP[nodeType as NodeType] || NodeCategory.TASK;
};
// JSON Schema 定义 // JSON Schema 定义
export interface JSONSchema { /**
type: string; * JSON Schema - 使 Formily ISchema
properties?: Record<string, any>; * @formily/react @formily/json-schema
required?: string[]; */
title?: string; import type { ISchema } from '@formily/react';
description?: string; export type JSONSchema = ISchema;
}
// UI 配置 // UI 配置
export interface NodeSize { export interface NodeSize {

View File

@ -117,7 +117,7 @@ export const convertJsonSchemaToColumns = (schema: JsonSchema): ProFormColumnsTy
formItemProps: { formItemProps: {
required: schema.required?.includes(key), required: schema.required?.includes(key),
}, },
initialValue: value.default, // 不在这里设置initialValue在表单层面设置
readonly: value.readOnly, readonly: value.readOnly,
}; };
@ -289,10 +289,30 @@ export const convertJsonSchemaToColumns = (schema: JsonSchema): ProFormColumnsTy
case 'object': case 'object':
return { return {
...baseConfig, ...baseConfig,
valueType: 'jsonCode', valueType: 'textarea',
fieldProps: { fieldProps: {
placeholder: `请输入${value.title || key}`, placeholder: `请输入JSON格式数据例如{"key": "value"}`,
disabled: value.readOnly, disabled: value.readOnly,
rows: 4,
autoSize: { minRows: 4, maxRows: 8 },
},
// 转换值object → JSON string显示
transform: (value: any) => {
if (typeof value === 'object' && value !== null) {
return JSON.stringify(value, null, 2);
}
return value;
},
// 转换值JSON string → object保存
convertValue: (val: any) => {
if (typeof val === 'string') {
try {
return JSON.parse(val);
} catch {
return value.default || {};
}
}
return val;
}, },
}; };