141 lines
4.5 KiB
TypeScript
141 lines
4.5 KiB
TypeScript
import React, { memo } from 'react';
|
||
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||
import { BaseNodeDefinition, NodeType, NodeCategory } from './types';
|
||
import type { FlowNodeData } from '../types';
|
||
|
||
/**
|
||
* 结束事件节点定义(元数据)
|
||
*/
|
||
export const EndEventNodeDefinition: BaseNodeDefinition = {
|
||
nodeCode: "END_EVENT",
|
||
nodeName: "结束",
|
||
nodeType: NodeType.END_EVENT,
|
||
category: NodeCategory.EVENT,
|
||
description: "工作流的结束节点",
|
||
|
||
uiConfig: {
|
||
size: { width: 80, height: 50 },
|
||
style: {
|
||
fill: '#ff4d4f',
|
||
stroke: '#cf1322',
|
||
strokeWidth: 2,
|
||
icon: 'stop-circle',
|
||
iconColor: '#fff'
|
||
}
|
||
},
|
||
|
||
configSchema: {
|
||
type: "object",
|
||
title: "基本配置",
|
||
description: "节点的基本信息",
|
||
properties: {
|
||
nodeName: {
|
||
type: "string",
|
||
title: "节点名称",
|
||
description: "节点在流程图中显示的名称",
|
||
default: "结束"
|
||
},
|
||
nodeCode: {
|
||
type: "string",
|
||
title: "节点编码",
|
||
description: "节点的唯一标识符",
|
||
default: "END_EVENT"
|
||
},
|
||
description: {
|
||
type: "string",
|
||
title: "节点描述",
|
||
description: "节点的详细说明",
|
||
default: "工作流的结束节点"
|
||
}
|
||
},
|
||
required: ["nodeName", "nodeCode"]
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 结束事件节点渲染组件
|
||
*/
|
||
const EndEventNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||
const nodeData = data as FlowNodeData;
|
||
const [isHovered, setIsHovered] = React.useState(false);
|
||
|
||
return (
|
||
<div
|
||
onMouseEnter={() => setIsHovered(true)}
|
||
onMouseLeave={() => setIsHovered(false)}
|
||
style={{
|
||
padding: '12px',
|
||
borderRadius: '50%',
|
||
border: `3px solid ${selected ? '#3b82f6' : '#ef4444'}`,
|
||
background: selected
|
||
? 'linear-gradient(135deg, #fee2e2 0%, #fecaca 100%)'
|
||
: 'linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%)',
|
||
minWidth: '70px',
|
||
minHeight: '70px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
position: 'relative',
|
||
boxShadow: selected
|
||
? '0 8px 16px rgba(59, 130, 246, 0.25), 0 2px 4px rgba(59, 130, 246, 0.15)'
|
||
: isHovered
|
||
? '0 6px 12px rgba(239, 68, 68, 0.2), 0 2px 4px rgba(239, 68, 68, 0.1)'
|
||
: '0 2px 8px rgba(0,0,0,0.08)',
|
||
cursor: 'pointer',
|
||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
|
||
}}
|
||
>
|
||
{/* 图标 */}
|
||
<div style={{
|
||
fontSize: '26px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
filter: selected ? 'brightness(1.1)' : 'none',
|
||
transition: 'filter 0.2s ease'
|
||
}}>
|
||
⏹️
|
||
</div>
|
||
|
||
{/* 输入连接点 */}
|
||
<Handle
|
||
type="target"
|
||
position={Position.Left}
|
||
style={{
|
||
background: '#ef4444',
|
||
border: '3px solid white',
|
||
width: '12px',
|
||
height: '12px',
|
||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||
transition: 'all 0.2s ease',
|
||
}}
|
||
/>
|
||
|
||
{/* 节点标签 */}
|
||
<div
|
||
style={{
|
||
position: 'absolute',
|
||
bottom: '-30px',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
fontSize: '13px',
|
||
color: '#1f2937',
|
||
fontWeight: '600',
|
||
whiteSpace: 'nowrap',
|
||
background: 'white',
|
||
padding: '4px 10px',
|
||
borderRadius: '6px',
|
||
boxShadow: '0 2px 6px rgba(0,0,0,0.1)',
|
||
border: '1px solid #e5e7eb',
|
||
}}
|
||
>
|
||
{nodeData.label || '结束'}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default memo(EndEventNode);
|
||
|