• 深入React Flow Renderer(二):构建拖动操作栏


    在上一篇博客中,我们介绍了如何启动React Flow Renderer并创建一个基本的工作流界面。本文将进一步深入,着重讨论如何构建一个可拖动的操作栏,它是用户与工作流交互的入口之一。

    引言

    操作栏是工作流界面的一部分,通常位于界面的一侧或顶部。它包含了用户可以从中拖拽节点到画布上的组件列表。在我们的示例中,操作栏将位于界面的左侧。

    创建操作栏组件

    首先,让我们看一下如何创建操作栏组件。在我们的示例中,我们使用了React组件,名为Slider。这个组件接收一个名为components的属性,该属性包含了可用的组件列表。

    // Slider/index.jsx
    
    import React from 'react';
    //项目中自定义的手风琴组件,请你使用自己项目中的组件
    import { CustomAccordion } from '@/components/CustomeAccordion';
    
    // 模拟节点
    const mockComponent = [
      {
        'name': 'clear alarm',
        'type': 'ACTION',
        'clazz': 'action.TbClearAlarmNode'
      },
      {
        'name': 'create alarm',
        'type': 'ACTION',
        'clazz': 'action.TbCreateAlarmNode'
      },
      {
        'name': 'device profile',
        'type': 'ACTION',
        'clazz': 'profile.TbDeviceProfileNode'
      },
      {
        'name': 'log',
        'type': 'ACTION',
        'clazz': 'action.TbLogNode'
      },
      {
        'name': 'message type switch',
        'type': 'FILTER',
        'clazz': 'filter.TbMsgTypeSwitchNode'
      },
      {
        'name': 'rpc call request',
        'type': 'ACTION',
        'clazz': 'rpc.TbSendRPCRequestNode'
      },
      {
        'name': 'rule chain',
        'type': 'FLOW',
        'clazz': 'flow.TbRuleChainInputNode'
      },
      {
        'name': 'save attributes',
        'type': 'ACTION',
        'clazz': 'telemetry.TbMsgAttributesNode'
      },
      {
        'name': 'save timeseries',
        'type': 'ACTION',
        'clazz': 'telemetry.TbMsgTimeseriesNode'
      },
      {
        'name': 'script',
        'type': 'TRANSFORMATION',
        'clazz': 'transform.TbTransformMsgNode'
      }
    ];
    
    export enum RuleNodeType {
      FILTER = 'FILTER',
      ENRICHMENT = 'ENRICHMENT',
      TRANSFORMATION = 'TRANSFORMATION',
      ACTION = 'ACTION',
      EXTERNAL = 'EXTERNAL',
      FLOW = 'FLOW',
      UNKNOWN = 'UNKNOWN',
      INPUT = 'INPUT',
    }
    
    export const ruleNodeTypeDescriptors = new Map<RuleNodeType, any>(
      [
        [
          RuleNodeType.FILTER,
          {
            value: RuleNodeType.FILTER,
            name: 'rulenode.type-filter',
            details: 'rulenode.type-filter-details',
            nodeClass: 'tb-filter-type',
            icon: 'filter_list'
          }
        ],
        [
          RuleNodeType.ENRICHMENT,
          {
            value: RuleNodeType.ENRICHMENT,
            name: 'rulenode.type-enrichment',
            details: 'rulenode.type-enrichment-details',
            nodeClass: 'tb-enrichment-type',
            icon: 'playlist_add'
          }
        ],
        [
          RuleNodeType.TRANSFORMATION,
          {
            value: RuleNodeType.TRANSFORMATION,
            name: 'rulenode.type-transformation',
            details: 'rulenode.type-transformation-details',
            nodeClass: 'tb-transformation-type',
            icon: 'transform'
          }
        ],
        [
          RuleNodeType.ACTION,
          {
            value: RuleNodeType.ACTION,
            name: 'rulenode.type-action',
            details: 'rulenode.type-action-details',
            nodeClass: 'tb-action-type',
            icon: 'flash_on'
          }
        ],
        [
          RuleNodeType.EXTERNAL,
          {
            value: RuleNodeType.EXTERNAL,
            name: 'rulenode.type-external',
            details: 'rulenode.type-external-details',
            nodeClass: 'tb-external-type',
            icon: 'cloud_upload'
          }
        ],
        [
          RuleNodeType.FLOW,
          {
            value: RuleNodeType.FLOW,
            name: 'rulenode.type-flow',
            details: 'rulenode.type-flow-details',
            nodeClass: 'tb-flow-type',
            icon: 'settings_ethernet'
          }
        ],
        [
          RuleNodeType.INPUT,
          {
            value: RuleNodeType.INPUT,
            name: 'rulenode.type-input',
            details: 'rulenode.type-input-details',
            nodeClass: 'tb-input-type',
            icon: 'input',
            special: true
          }
        ],
        [
          RuleNodeType.UNKNOWN,
          {
            value: RuleNodeType.UNKNOWN,
            name: 'rulenode.type-unknown',
            details: 'rulenode.type-unknown-details',
            nodeClass: 'tb-unknown-type',
            icon: 'help_outline'
          }
        ]
      ]
    );
    
    const classMap = new Map([
      ['ACTION', 'relation-node'],
      ['input', 'input-node'],
      ['FILTER', 'filter-node'],
      ['ENRICHMENT', 'enrichment-node'],
      ['TRANSFORMATION', 'transformation-node'],
      ['EXTERNAL', 'external-node'],
      ['FLOW', 'flow-node']
    ]);
    
    // const allowType = ruleNodeTypeComponentTypes;
    const allowNodesClazz = [
      'telemetry.TbMsgAttributesNode',
      'filter.TbMsgTypeSwitchNode',
      'action.TbLogNode',
      'rpc.TbSendRPCRequestNode',
      'profile.TbDeviceProfileNode',
      'telemetry.TbMsgTimeseriesNode',
      'action.TbCreateAlarmNode',
      'action.TbClearAlarmNode',
      'flow.TbRuleChainInputNode',
      'transform.TbTransformMsgNode'
    ];
    
    export default function Slider() {
      const [allowType, setAllowType] = React.useState<any>(['input']);
      const [allowedNodes, setAllowedNodes] = React.useState<any>([]);
    
      React.useEffect(() => {
        // 将组件按名称进行排序
        const sortedComponents = mockComponent?.sort((a: any, b: any) =>
          a.name?.localeCompare(b.name)
        );
    
        // 过滤出符合条件的组件并拼接到allowedNodes数组中
        const filteredComponents =
          sortedComponents?.filter((component: any) =>
            allowNodesClazz.includes(component.clazz)
          ) || [];
        const updatedAllowedNodes = [...filteredComponents];
    
        // 获取所有组件的类型,并和allowType数组进行合并
        const updatedTypes = updatedAllowedNodes.map((component) => component.type);
    
        // 去除重复的节点并更新allowedNodes状态
        setAllowedNodes(Array.from(new Set(updatedAllowedNodes)));
    
        // 去除重复的类型并更新allowType状态(如果为空数组,则设置为默认值)
        setAllowType(Array.from(new Set(updatedTypes)) || []);
      }, []);
    
      return (
        <div className="sider">
          {allowType.map((type: any) =>
          //自定义手风琴,项目中使用的是mui,你可以使用其他组件库,这里就不贴出手风琴的代码了,请你根据你的项目,使用对应的组件。如果不需要手风琴组件。可以拥
    来代替 <CustomAccordion title={ ruleNodeTypeDescriptors.get(type as any)?.name as string} key={type} > <div className="nodes"> {allowedNodes .filter((node: any) => node.type === type) .map((x: any, i: number) => <div key={`${x.type}-${i}`} className={`sider-node ${ classMap.get(x.type) || 'default-node' }`} onDragStart={(e) => onDragStart(e, x)} draggable > <div>{x.name}</div> {/* 黑色遮罩层 */} <div className="overlay"></div> </div> )} </div> </CustomAccordion> )} </div> ); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241

    在上述代码中,我们定义了一个Slider组件,它将组件列表映射到可展开的自定义组件中,并为每个组件添加了拖拽支持。

    拖拽事件处理

    拖拽操作栏的核心功能在于如何处理拖拽事件。在我们的示例中,我们使用了onDragStart函数来处理节点拖拽开始事件。该函数会设置被拖拽的节点的类型和名称,并记录被拖拽节点的完整信息。

    /**
     * 处理节点拖拽开始事件的回调函数
      * @param {Event} evt - 拖拽事件对象
      * @param {Object} node - 被拖拽的节点对象
      */
    const onDragStart = (evt: any, node: any) => {
      // 记录被拖拽的节点类型和名称
      evt.dataTransfer.setData(
        'application/reactflow',
        node.type + ',' + node.name
      );
      // 记录被拖拽的节点的完整信息
      evt.dataTransfer.setData('application/reactflownode', JSON.stringify(node));
      // 设置拖拽效果为移动
      evt.dataTransfer.effectAllowed = 'move';
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个函数会在用户拖拽节点时被触发,并且会设置相关的数据以便后续在画布上放置节点时使用。

    总结

    通过创建一个可拖动的操作栏,用户可以方便地将节点拖放到工作流画布上。在本文中,我们了解了如何创建操作栏组件,处理拖拽事件,并将组件列表展示给用户。下一篇博客中,我们将继续深入研究工作流界面的其他方面,包括画布的交互性和节点的定制。敬请期待!

  • 相关阅读:
    快速入手SpringMVC 之 JSR303与拦截器
    STM32G4系列之DAC
    基于微信小程序的电影院订票选座系统的设计与实现(2)
    Linux进程控制
    linux下编译安装和使用cURL库(含有openssl)
    计算机毕业设计选什么题目好?springboot智慧养老中心管理系统
    30行Python极简代码,10分钟get常用技巧
    echarts的项目总结
    leetcode:6243. 到达首都的最少油耗【变种子树大小统计 + 从边的角度出发 + 思维转换】
    Tomcat
  • 原文地址:https://blog.csdn.net/m0_73117087/article/details/133353817