瀏覽代碼

添加流程编辑器

yangg 6 月之前
父節點
當前提交
67f33acce7

+ 10 - 1
ui/package.json

@@ -36,13 +36,19 @@
     "url": "https://gitee.com/y_project/RuoYi-Vue.git"
   },
   "dependencies": {
+    "@antv/x6": "^2.18.1",
+    "@antv/x6-plugin-dnd": "^2.1.1",
+    "@antv/x6-vue-shape": "^2.1.2",
     "@handsontable/vue": "^14.6.1",
+    "@logicflow/core": "^0.7.9",
+    "@logicflow/extension": "^0.7.9",
     "@riophae/vue-treeselect": "0.4.0",
+    "@vue/composition-api": "^1.7.2",
     "axios": "0.28.1",
+    "ckeditor4": "^4.23.0",
     "ckeditor4-vue": "^3.2.0",
     "clipboard": "2.0.8",
     "core-js": "3.37.1",
-    "ckeditor4": "^4.23.0",
     "echarts": "5.4.0",
     "element-ui": "2.15.14",
     "file-saver": "2.0.5",
@@ -58,6 +64,7 @@
     "js-cookie": "3.0.1",
     "jsencrypt": "3.0.0-rc.1",
     "jspdf": "^2.5.2",
+    "jsplumb": "^2.15.6",
     "luckyexcel": "^1.0.1",
     "nprogress": "0.2.0",
     "quill": "2.0.2",
@@ -66,8 +73,10 @@
     "vue": "2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
+    "vue-flow": "^0.3.0",
     "vue-meta": "2.4.0",
     "vue-router": "3.4.9",
+    "vue2-flowchart": "^0.1.12",
     "vuedraggable": "2.24.3",
     "vuex": "3.6.0"
   },

+ 3 - 0
ui/src/main.js

@@ -38,6 +38,9 @@ import VueMeta from 'vue-meta'
 // 字典数据组件
 import DictData from '@/components/DictData'
 import CKEditor from 'ckeditor4-vue';
+import VueCompositionAPI from '@vue/composition-api'
+
+Vue.use(VueCompositionAPI)
 
 Vue.use( CKEditor );
 // 全局方法挂载

+ 588 - 0
ui/src/views/flow/X61.vue

@@ -0,0 +1,588 @@
+<template>
+    <div>
+      <button @mousedown="startDrag('Start', $event)">开始节点</button>
+      <button @mousedown="startDrag('End', $event)">结束节点</button>
+      <button @mousedown="startDrag('Circle', $event)">循环节点</button>
+      <button @mousedown="startDrag('Rect', $event)">任务节点</button>
+      <button @mousedown="startDrag('polygon', $event)">条件节点</button>
+      <button @click="deleteNode()">删除</button>
+      <button @click="onUndo">撤销</button>
+      <button @click="onRedo">重做</button>
+      <button @click="savePic">生成图片</button>
+      <button @click="saveFlow">保存流程图并生成java代码</button>
+      <button @click="getJson">getJson</button>
+      <div class="graph-drawer-box">
+        <div ref="graphContainer"></div>
+        <RightDrawer ref="rightDrawer"
+          :drawerType="type" :selectCellNum="selectCellNum" :selectCell="selectCell" 
+          :graph="graph" :grid="grid" @deleteNode="deleteNode"></RightDrawer>
+      </div>
+    </div>
+  </template>
+  <script>
+  import { Graph, Shape, FunctionExt, DataUri } from '@antv/x6';
+  // import insertCss from 'insert-css';
+  // 添加 Dnd 导入
+import { Dnd } from '@antv/x6-plugin-dnd';
+  import data from './nodes/data.js'
+  import { startDragToGraph } from './nodes/methods.js';
+  import RightDrawer from './nodes/right-drawer';
+  export default {
+    name: 'index2Page',
+    components: {
+      RightDrawer
+    },
+    data() {
+      return {
+        selectCellNum: 0,
+        contentWidth: 0,
+        contentHeight: 0,
+        graph: null,
+        type: 'grid',
+        selectCell: '',
+        connectEdgeType: {  //连线方式
+          connector: 'normal',
+          router: {
+            // name: ''
+            name: 'normal'
+          }
+        },
+        grid: { // 网格设置
+          showGrid: true,
+          size: 10,      // 网格大小 10px
+          visible: true, // 渲染网格背景
+          type: 'mesh',
+          args: {
+            color: '#D0D0D0',
+            thickness: 1,     // 网格线宽度/网格点大小
+            factor: 10,
+          },
+        },
+        canUndo: false,
+        canRedo: false,
+        haveStart: false,  //已有开始或结束
+        haveEnd: false,
+      }
+    },
+    mounted() {
+      // 在初始化图形之前注册插件
+      Graph.use(['dnd']);
+      this.initX6()
+    },
+    methods: {
+      // initGraph() {
+      //   this.graph = new Graph({
+      //     container: document.getElementById('container'),
+      //     width: 800,
+      //     height: 600,
+      //     background: {
+      //       color: '#F2F7FA'
+      //     }
+      //   })
+      //   console.log(dataJSON)
+      //   this.graph.fromJSON(dataJSON) // 渲染元素    this.graph.centerContent() // 居中显示
+      // },
+      // 计算main主体高度
+      getMainHeight(e) {
+        /* this.contentHeight = Math.max(window.innerHeight - 60, 400);
+        this.contentWidth = window.innerWidth - 250;
+        if (e) {
+          //画布调整大小
+          this.graph.resize(this.contentWidth, this.contentHeight);
+        } */
+        this.contentWidth = this.$refs['graphContainer'].parentNode.offsetWidth;
+        this.contentHeight = 910;
+      },
+      initX6() {
+        this.type = 'grid';
+        this.selectCell = '';
+        let _that = this;
+        // window.addEventListener('resize', this.debounceHeight);
+        this.graph = new Graph({
+          container: this.$refs['graphContainer'],
+          width: 1200,
+          height: 600,
+          grid: this.grid,
+          panning: {
+            enabled: true,
+            modifiers: 'shift'
+          }, //平移
+          resizing: { //调整节点宽高
+            enabled: true,
+            orthogonal: false,
+          },
+          selecting: true, //可选
+          snapline: true,
+          interacting: {
+            edgeLabelMovable: true
+          },
+          connecting: { // 节点连接
+            anchor: 'center',
+            connectionPoint: 'anchor',
+            allowBlank: false,
+            snap: true,
+            createEdge() {
+              return new Shape.Edge({
+                attrs: {
+                  line: {
+                    stroke: '#1890ff',
+                    strokeWidth: 1,
+                    targetMarker: {
+                      name: 'classic',
+                      size: 8
+                    },
+                    strokeDasharray: 0, //虚线
+                    style: {
+                      animation: 'ant-line 30s infinite linear',
+                    },
+                  },
+                },
+                label: {
+                  text: ''
+                },
+                connector: _that.connectEdgeType.connector,
+                router: {
+                  name: _that.connectEdgeType.router.name || ''
+                },
+                zIndex: 0
+              })
+            },
+          },
+          highlighting: {
+            magnetAvailable: {
+              name: 'stroke',
+              args: {
+                padding: 4,
+                attrs: {
+                  strokeWidth: 4,
+                  stroke: '#6a6c8a'
+                }
+              }
+            }
+          },
+          //滚动
+          scroller: {
+            enabled: false
+          },
+          //小地图
+          minimap: {
+            enabled: !this.readonly,
+            container: !this.readonly ? this.$refs['rightDrawer'].$refs['miniMap'] : '',
+            width: 300,
+            height: 150,
+            padding: 5
+          },
+          //缩放
+          mousewheel: {
+            enabled: true,
+            modifiers: ['ctrl', 'meta'],
+          },
+          // 历史
+          history: {
+            enabled: true,
+          },
+          //框选
+          selecting: {
+            enabled: true,
+            rubberband: true
+          },
+        });
+        // insertCss(`
+        //         @keyframes ant-line {
+        //           to {
+        //               stroke-dashoffset: -1000
+        //           }
+        //         }
+        //       `)
+        this.graph.fromJSON(data);
+        this.graph.history.redo();
+        this.graph.history.undo();
+        // 鼠标移入移出节点
+        this.graph.on('node:mouseenter', FunctionExt.debounce(() => {
+          const container = this.$refs['graphContainer']
+          const ports = container.querySelectorAll('.x6-port-body')
+          this.showPorts(ports, true)
+        }), 500);
+        this.graph.on('node:mouseleave', () => {
+          const container = this.$refs['graphContainer']
+          const ports = container.querySelectorAll('.x6-port-body')
+          this.showPorts(ports, false)
+        });
+        this.graph.on('blank:click', () => {
+          this.type = 'grid';
+          //选中网格需清空选中节点的数据
+          this.selectCell = '';
+          console.log('blank:click')
+        });
+        this.graph.on('cell:click', ({ cell }) => {
+          this.type = cell.isNode() ? 'node' : 'edge';
+          this.$emit('cellClick', cell);
+          console.log('cell:click', cell, cell.isNode(), cell.attrs.nodeData, cell)
+        });
+        //监听框选
+        this.graph.on('selection:changed', args => {
+          //记录当前选中的数量
+          this.selectCellNum = args.selected.length;
+          if (this.selectCellNum > 1) {
+            //只有选中一个节点时才允许修改节点信息
+            this.type = 'grid';
+            this.selectCell = '';
+          } else {
+            this.selectCell = args.added[0];
+          }
+          args.added.forEach(cell => {
+            // this.selectCell = cell
+            if (cell.isEdge()) {
+              cell.isEdge() && cell.attr('line/strokeDasharray', 5) //虚线蚂蚁线
+              cell.addTools([
+                {
+                  name: 'vertices',
+                  args: {
+                    padding: 4,
+                    attrs: {
+                      strokeWidth: 0.1,
+                      stroke: '#2d8cf0',
+                      fill: '#ffffff',
+                    }
+                  },
+                },
+              ])
+            }
+          })
+          args.removed.forEach(cell => {
+            cell.isEdge() && cell.attr('line/strokeDasharray', 0);  //正常线
+            cell.removeTools();
+          })
+        });
+        /**
+         * 处理history
+         */
+        //监听撤销
+        this.graph.history.on('undo', args => {
+          // code here
+          this.$emit('historyUndo', args);
+        });
+        //监听重做
+        this.graph.history.on('redo', args => {
+          // code here
+          this.$emit('historyRedo', args);
+        });
+        this.graph.history.on('change', () => {
+          this.historyChange();
+        });
+        // 监听节点新增
+        this.graph.on('node:added', args => {
+          const { cell } = args;
+          // 检查禁用情况
+          this.checkNode();
+          //选中
+          this.graph.resetSelection(cell);
+          this.type = 'node';
+          const data = cell.store.data;
+          switch (data.attrs.nodeData.type) {
+            case 'Start':
+              //开始
+              this.haveStart = true;
+              break;
+            case 'End':
+              //结束
+              this.haveEnd = true;
+              break;
+          }
+        });
+        //监听节点删除
+        this.graph.on('node:removed', args => {
+          const { cell } = args;
+          const data = cell.store.data;
+          // 检查禁用情况
+          this.checkNode();
+          switch (data.attrs.nodeData.type) {
+            case 'Start':
+              //开始
+              this.haveStart = false;
+              break;
+            case 'End':
+              //结束
+              this.haveEnd = false;
+              break;
+          }
+        });
+        //边连接
+        this.graph.on('edge:connected', args => {
+          //选中节点
+          this.graph.resetSelection(args.edge);
+          this.type = 'edge';
+          console.log('edge:connected', args.edge)
+        })
+      },
+      showPorts(ports, show) {
+        for (let i = 0, len = ports.length; i < len; i = i + 1) {
+          ports[i].style.visibility = show ? 'visible' : 'hidden'
+        }
+      },    // 拖拽生成正方���或者圆形
+      startDrag(type, e) {
+        if (this.disabled) {
+          return;
+        }
+        //只能有一个开始和结束
+        if (type === 'Start') {
+          if (this.haveStart) {
+            return;
+          }
+        } else if (type === 'End') {
+          if (this.haveEnd) {
+            return;
+          }
+        }
+        startDragToGraph(this.graph, type, e);
+      },
+      // 删除节点
+      deleteNode() {
+        if (this.disabled) {
+          return;
+        }
+        const cell = this.graph.getSelectedCells();
+        this.graph.removeCells(cell);
+        this.type = 'grid';
+      },
+      // 保存png
+      savePic() {
+        this.$nextTick(() => {
+          this.graph.toPNG(dataUri => {
+            // 下载
+            DataUri.downloadDataUri(dataUri, this.title + '.png');
+          }, {
+            backgroundColor: 'white',
+            padding: {
+              top: 50,
+              right: 50,
+              bottom: 50,
+              left: 50
+            },
+            quality: 1,
+            copyStyles: false
+          })
+        })
+      },
+      /**
+       * 检查是否可以禁用关联模型
+       * 当节点中存在非开始和结束节点时不允许编辑 
+       */ 
+       checkNode(checkStartAndEnd) {
+        const node = this.graph.getNodes();
+        const ignores = ['Start', 'End'];
+        let diabledEntity = false;
+        console.log('node', node)
+        for (let i = node.length - 1; i > -1; i--) {
+          if (ignores.indexOf(node[i].attrs.nodeData.type) < 0) {
+            diabledEntity = true;
+            if (!checkStartAndEnd) {
+              break;
+            }
+          } else if (checkStartAndEnd) {
+            if (node[i].attrs.nodeData.type === 'Start') {
+              this.haveStart = true;
+            } else {
+              this.haveEnd = true;
+            }
+          }
+        }
+        this.diabledEntity = diabledEntity;
+      },
+      //流程图完整性校验
+      checkFlow() {
+        const graphToJson = this.graph.toJSON();
+        const { cells } = graphToJson;
+        /**
+         * 1、完整的流程图必需包含开始节点和结束节点且仅有一个
+         * 2、每个节点必定有至少一条与其他节点的连接线;
+         * 3、开始节点只能是连接线起点
+         * 4、结束节点只能是连接线终点
+         */
+        let errs = [], //错误消息集合
+          nodeList = [],
+          edgeList = [],
+          startNum = 0,
+          endNum = 0,
+          len = cells.length,
+          alls = [],   //所有节点
+          edges = [];  //与其他节点有连接关系的节点集合(不包含自己与自己连接的)
+        if (len === 0) {
+          errs.push('画布不能为空');
+        } else {
+          for (let i = 0; i < len; i++) {
+            //非连线(节点)
+            if (cells[i].shape !== 'edge') {
+              //节点数据
+              const nodeData = cells[i].attrs.nodeData;
+              //节点名称
+              const nodeText = cells[i].attrs.label.text;
+              const flowNode = nodeData.flowNode;
+              if (nodeData.type === 'Start') {
+                startNum++;
+              } else if (nodeData.type === 'End') {
+                endNum++;
+              } else if (nodeData.type === 'Rect') {
+                let errTxt = '任务节点【' + nodeText + '】';
+                switch (flowNode.flowNodeType) {
+                  case '':
+                    errs.push(errTxt + '未选择节点类型');
+                    break;
+                  case '2':
+                    //执行规则组
+                    if (flowNode.groupIdentify === '') {
+                      errs.push(errTxt + '未选择执行规则组');
+                    }
+                    break;
+                  case '6':
+                    //子流程
+                    if (flowNode.childFlowIdentify === '') {
+                      errs.push(errTxt + '未选择子流程');
+                    }
+                    break;
+                  case '7':
+                    //源码节点
+                    if (flowNode.methodName === '') {
+                      errs.push(errTxt + '未选择方法');
+                    } else if (flowNode.hasMethodParams && flowNode.methodParams === '') {
+                      errs.push(errTxt + '方法未输入参数');
+                    }
+                    break;
+                }
+              } else if (nodeData.type === 'polygon') {
+                const errTxt = '条件节点【' + nodeText + '】';
+                //处理条件节点的边
+                let conditionEdges = this.graph.getOutgoingEdges(cells[i].id);
+                console.log('conditionEdges', conditionEdges)
+                if (conditionEdges) {
+                  conditionEdges = conditionEdges.filter(item => {
+                    return item.source.cell !== item.target.cell;
+                  });
+                  let trueNum = 0,
+                    falseNum = 0;
+                  conditionEdges.forEach(item => {
+                    console.log('item', item)
+                    let labelText = item.store.data.labels ? item.store.data.labels[0].text != undefined ? item.store.data.labels[0].text : item.store.data.labels[0].attrs.label.text : '';
+                    if (labelText === '是') {
+                      trueNum++;
+                    } else if (labelText === '否') {
+                      falseNum++;
+                    }
+                  });
+                  if (trueNum < 1) {
+                    errs.push(errTxt + '缺少是判断的连线');
+                  } else if (trueNum > 1) {
+                    errs.push(errTxt + '过多是判断的连线');
+                  }
+                  if (falseNum < 1) {
+                    errs.push(errTxt + '缺少否判断的连线');
+                  } else if (falseNum > 1) {
+                    errs.push(errTxt + '过多否判断的连线');
+                  }
+                }
+                //校验条件表单
+                if (flowNode.flowJudges.length < 1) {
+                  errs.push(errTxt + '未选择判断条件');
+                }
+              }
+              alls.push({
+                id: cells[i].id,
+                type: cells[i].attrs.nodeData.type
+              });
+              nodeList.push(cells[i]);
+            } else {
+              //连接线
+              if (cells[i].source.cell !== cells[i].target.cell) {
+                edges.push({
+                  source: cells[i].source.cell,
+                  target: cells[i].target.cell
+                });
+              }
+              edgeList.push(cells[i]);
+            }
+          }
+  
+          if (startNum !== 1) {
+            errs.push('开始节点必需存在,且仅有一个');
+          }
+          if (endNum !== 1) {
+            errs.push('结束节点必需存在,且仅有一个');
+          }
+  
+          let startEdgeErr = false,
+            endEdgeErr = false,
+            edgeErr = false;
+          //遍历所有节点
+          alls.forEach(item => {
+            let hasEdge = false;
+            for (let i = 0, len = edges.length; i < len; i++) {
+              if (item.id === edges[i].source || item.id === edges[i].target) {
+                hasEdge = true;
+              }
+              if (item.type === 'Start') {
+                if (item.id === edges[i].target && !startEdgeErr) {
+                  startEdgeErr = true;
+                  errs.push('开始节点不能为连线终点');
+                }
+              } else if (item.type === 'End') {
+                if (item.id === edges[i].source && !endEdgeErr) {
+                  endEdgeErr = true;
+                  errs.push('结束节点不能为连线起点');
+                }
+              }
+            }
+            if (!hasEdge && !edgeErr) {
+              edgeErr = true;
+              errs.push('节点间必需有连接线关联');
+            }
+          });
+        }
+        return {
+          graphToJson,
+          nodeList,
+          edgeList,
+          errs
+        };
+      },
+      //保存流程图
+      saveFlow() {
+        // 先取消所有选中的节点和边, 防止把选中状态一起保存了
+        this.graph.cleanSelection();
+        const { errs, nodeList, edgeList, graphToJson } = this.checkFlow();
+        console.log('nodeList', nodeList)
+        console.log('edgeList', edgeList)
+        console.log('saveData', {
+          nodes: nodeList,
+          edges: edgeList
+        })
+      },
+      //历史状态改变
+      historyChange() {
+        this.canUndo = this.graph.history.canUndo();
+        this.canRedo = this.graph.history.canRedo();
+      },
+      //撤销
+      onUndo() {
+        if (this.disabled) {
+          return;
+        }
+        this.graph.history.undo();
+      },
+      //重做
+      onRedo() {
+        if (this.disabled) {
+          return;
+        }
+        this.graph.history.redo();
+      },
+      getJson() {
+        console.log(this.graph.toJSON());
+      }
+    }
+  }
+  </script>
+  <style lang="scss" scoped>
+  .graph-drawer-box{
+    display: flex;
+  }
+  </style>

+ 794 - 0
ui/src/views/flow/index.vue

@@ -0,0 +1,794 @@
+<template>
+  <div class="flow-container">
+    <div class="toolbar">
+      <el-button-group>
+        <el-button @click="addStartNode">开始节点</el-button>
+        <el-button @click="addProcessNode">流程节点</el-button>
+        <el-button @click="addEndNode">结束节点</el-button>
+      </el-button-group>
+      <el-divider direction="vertical" />
+      <el-button-group>
+        <el-button @click="saveGraph">保存</el-button>
+        <el-button @click="exportGraph">导出</el-button>
+        <el-button @click="importGraph">导入</el-button>
+        <el-button @click="clearGraph">清空</el-button>
+      </el-button-group>
+      <el-divider direction="vertical" />
+      <el-button-group>
+        <el-button @click="undo" :disabled="!canUndo">撤销</el-button>
+        <el-button @click="redo" :disabled="!canRedo">重做</el-button>
+      </el-button-group>
+    </div>
+
+    <div ref="container" class="graph-container"></div>
+
+    <el-dialog
+      title="编辑节点"
+      :visible.sync="editModalVisible"
+      @close="handleEditCancel"
+      width="600px"
+    >
+      <el-form :model="editForm" label-width="100px">
+        <el-form-item label="节点名称">
+          <el-input v-model="editForm.label"></el-input>
+        </el-form-item>
+
+        <el-form-item label="节点类型">
+          <el-select v-model="editForm.type" placeholder="请选择节点类型">
+            <el-option label="审批节点" value="approval"></el-option>
+            <el-option label="条件节点" value="condition"></el-option>
+            <el-option label="执行节点" value="action"></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="节点描述">
+          <el-input
+            type="textarea"
+            v-model="editForm.description"
+            :rows="3"
+          ></el-input>
+        </el-form-item>
+
+        <!-- 属性列表 -->
+        <el-form-item label="节点属性">
+          <div
+            v-for="(prop, index) in editForm.properties"
+            :key="index"
+            class="property-item"
+          >
+            <el-input
+              v-model="prop.key"
+              placeholder="属性名"
+              style="width: 200px"
+            ></el-input>
+            <el-input
+              v-model="prop.value"
+              placeholder="属性值"
+              style="width: 200px"
+            ></el-input>
+            <el-button type="text" @click="removeProperty(index)"
+              >删除</el-button
+            >
+          </div>
+          <el-button type="text" @click="addProperty">添加属性</el-button>
+        </el-form-item>
+
+        <!-- 条件设置(当节点类型为条件节点时显示) -->
+        <template v-if="editForm.type === 'condition'">
+          <el-form-item label="条件设置">
+            <div
+              v-for="(condition, index) in editForm.conditions"
+              :key="index"
+              class="condition-item"
+            >
+              <el-input
+                v-model="condition.expression"
+                placeholder="条件表达式"
+                style="width: 300px"
+              ></el-input>
+              <el-input
+                v-model="condition.description"
+                placeholder="条件描述"
+                style="width: 200px"
+              ></el-input>
+              <el-button type="text" @click="removeCondition(index)"
+                >删除</el-button
+              >
+            </div>
+            <el-button type="text" @click="addCondition">添加条件</el-button>
+          </el-form-item>
+        </template>
+      </el-form>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="handleEditCancel">取 消</el-button>
+        <el-button type="primary" @click="handleEditOk">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog
+      title="编辑连线标签"
+      :visible.sync="edgeLabelModalVisible"
+      @close="handleEdgeLabelCancel"
+      width="400px"
+    >
+      <el-form :model="edgeLabelForm" label-width="80px">
+        <el-form-item label="标签文本">
+          <el-input v-model="edgeLabelForm.label"></el-input>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="handleEdgeLabelCancel">取 消</el-button>
+        <el-button type="primary" @click="handleEdgeLabelOk">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Graph, Shape, Addon } from "@antv/x6";
+import { Message } from "element-ui";
+
+export default {
+  name: "FlowEditor",
+  data() {
+    return {
+      graph: null,
+      selectedNode: null,
+      editModalVisible: false,
+      editForm: {
+        label: "",
+        description: "",
+        type: "",
+        properties: [],
+        conditions: [],
+      },
+      history: null,
+      dnd: null,
+      selectedEdge: null,
+      edgeLabelModalVisible: false,
+      edgeLabelForm: {
+        label: ''
+      }
+    };
+  },
+  computed: {
+    canUndo() {
+      return this.history?.canUndo() || false;
+    },
+    canRedo() {
+      return this.history?.canRedo() || false;
+    },
+  },
+  mounted() {
+    // 确保DOM加载完成后再初始化
+    this.$nextTick(() => {
+      this.initGraph();
+      this.bindEvents();
+    });
+  },
+  methods: {
+    initGraph() {
+      // 注册自定义节点
+      this.registerCustomNodes();
+
+      // 初始化画布
+      this.graph = new Graph({
+        container: this.$refs.container,
+        width: "100%",
+        height: 600,
+        grid: {
+          type: "mesh",
+          size: 10,
+          visible: true,
+        },
+        connecting: {
+          snap: true,
+          allowBlank: false,
+          allowLoop: false,
+          highlight: true,
+          connector: "normal",
+          anchor: "center",
+          // 使用 manhattan 路由实现直角连线
+          router: {
+            name: "manhattan",
+            args: {
+              padding: 20,
+              startDirections: ["bottom"],
+              endDirections: ["top"],
+            },
+          },
+          connectionPoint: {
+            name: "boundary",
+            args: {
+              sticky: true,
+            },
+          },
+          validateMagnet() {
+            return true;
+          },
+          createEdge() {
+            return this.createEdge({
+              attrs: {
+                line: {
+                  stroke: "#333",
+                  strokeWidth: 2,
+                  targetMarker: {
+                    name: "classic",
+                    size: 8,
+                  },
+                },
+              },
+              labels: [{
+                attrs: {
+                  text: {
+                    text: '',
+                    fill: '#333',
+                    fontSize: 12,
+                    textAnchor: 'middle',
+                    textVerticalAnchor: 'middle',
+                  },
+                  rect: {
+                    fill: '#fff',
+                    stroke: '#333',
+                    strokeWidth: 1,
+                    rx: 3,
+                    ry: 3,
+                    refWidth: '100%',
+                    refHeight: '100%',
+                    refX: 0,
+                    refY: 0,
+                  }
+                },
+                position: {
+                  distance: 0.5,
+                  options: {
+                    absoluteDistance: true,
+                    reverseDistance: true,
+                    keepGradient: true,
+                    ensureLegibility: true,
+                  }
+                }
+              }]
+            });
+          },
+          validateConnection: ({ sourceCell, targetCell }) => {
+            if (targetCell.shape === "flow-start") return false;
+            if (sourceCell.shape === "flow-end") return false;
+            return true;
+          },
+          defaultEdge: {
+            attrs: {
+              line: {
+                stroke: "#333",
+                strokeWidth: 2,
+                targetMarker: {
+                  name: "classic",
+                  size: 8,
+                },
+              },
+            },
+            router: {
+              name: "manhattan",
+              args: {
+                padding: 20,
+                startDirections: ["bottom"],
+                endDirections: ["top"],
+              },
+            },
+            interactive: true,
+            vertices: true,
+            vertexAddable: true,
+            vertexMovable: true,
+            vertexRemovable: true,
+          },
+        },
+        highlighting: {
+          magnetAvailable: {
+            name: "stroke",
+            args: {
+              padding: 4,
+              attrs: {
+                strokeWidth: 4,
+                stroke: "#31d0c6",
+              },
+            },
+          },
+        },
+        panning: true,
+        mousewheel: {
+          enabled: true,
+          modifiers: ["ctrl", "meta"],
+        },
+        selecting: {
+          enabled: true,
+          multiple: true,
+          rubberband: true,
+          showNodeSelectionBox: true,
+        },
+        snapline: true,
+        history: true,
+        clipboard: true,
+        interacting: {
+          nodeMovable: true,
+          edgeMovable: true,
+          edgeLabelMovable: false,
+          vertexMovable: true,
+          vertexAddable: true,
+          vertexDeletable: true,
+        },
+      });
+      // 添加边线双击事件
+      this.graph.on('edge:contextmenu', ({ edge }) => {
+        this.selectedEdge = edge;
+        const currentLabel = edge.getLabels()?.[0]?.attrs?.text?.text || '';
+        this.edgeLabelForm.label = currentLabel;
+        this.edgeLabelModalVisible = true;
+      });
+      // 修改双击事件绑定
+      this.graph.on("cell:dblclick", ({ node, e }) => {
+        console.log("Node double clicked:", node);
+        this.selectedNode = node;
+        const nodeData = node.getData() || {};
+
+        // 获取节点数据
+        this.editForm = {
+          label: node.attr("label/text") || "",
+          description: nodeData.description || "",
+          type: nodeData.type || "",
+          properties: Array.isArray(nodeData.properties)
+            ? nodeData.properties
+            : [],
+          conditions: Array.isArray(nodeData.conditions)
+            ? nodeData.conditions
+            : [],
+        };
+
+        this.editModalVisible = true;
+      });
+      
+      // 添加单击事件用于测试事件绑定
+      this.graph.on("node:click", ({ node }) => {
+        console.log("Node clicked:", node);
+      });
+
+      // 连线完成事件
+      this.graph.on("edge:connected", ({ edge }) => {
+        edge.attr({
+          line: {
+            stroke: "#333",
+            strokeWidth: 2,
+            targetMarker: {
+              name: "classic",
+              size: 8,
+            },
+          },
+        });
+      });
+
+      // 快捷键支持
+      this.graph.bindKey(["meta+c", "ctrl+c"], () => {
+        const cells = this.graph.getSelectedCells();
+        if (cells.length) {
+          this.graph.copy(cells);
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+v", "ctrl+v"], () => {
+        if (!this.graph.isClipboardEmpty()) {
+          const cells = this.graph.paste({ offset: 32 });
+          this.graph.cleanSelection();
+          this.graph.select(cells);
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+z", "ctrl+z"], () => {
+        if (this.canUndo) {
+          this.undo();
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
+        if (this.canRedo) {
+          this.redo();
+        }
+        return false;
+      });
+      // 初始化历史记录
+      this.history = this.graph.history;
+
+      // 初始化拖拽
+      this.dnd = new Addon.Dnd({ target: this.graph });
+    },
+
+    registerCustomNodes() {
+      // 注册开始节点
+      Graph.registerNode("flow-start", {
+        inherit: "circle",
+        width: 60,
+        height: 60,
+        attrs: {
+          body: {
+            fill: "#2ecc71",
+            stroke: "#27ae60",
+            magnet: true,
+          },
+          label: {
+            text: "开始",
+            fill: "#fff",
+            fontSize: 14,
+          },
+        },
+        markup: [
+          {
+            tagName: "circle",
+            selector: "body",
+          },
+          {
+            tagName: "text",
+            selector: "label",
+          },
+        ],
+      });
+
+      // 注册流程节点
+      Graph.registerNode("flow-process", {
+        inherit: "rect",
+        width: 120,
+        height: 60,
+        attrs: {
+          body: {
+            fill: "#fff",
+            stroke: "#333",
+            strokeWidth: 2,
+            rx: 8,
+            ry: 8,
+            magnet: true,
+          },
+          label: {
+            fontSize: 14,
+            fill: "#333",
+          },
+        },
+        markup: [
+          {
+            tagName: "rect",
+            selector: "body",
+          },
+          {
+            tagName: "text",
+            selector: "label",
+          },
+        ],
+      });
+
+      // 注册结束���点
+      Graph.registerNode("flow-end", {
+        inherit: "circle",
+        width: 60,
+        height: 60,
+        attrs: {
+          body: {
+            fill: "#e74c3c",
+            stroke: "#c0392b",
+            magnet: true,
+          },
+          label: {
+            text: "结束",
+            fill: "#fff",
+            fontSize: 14,
+          },
+        },
+        markup: [
+          {
+            tagName: "circle",
+            selector: "body",
+          },
+          {
+            tagName: "text",
+            selector: "label",
+          },
+        ],
+      });
+    },
+
+    addStartNode() {
+      const node = this.graph.addNode({
+        shape: "flow-start",
+        x: 100,
+        y: 100,
+      });
+    },
+
+    addProcessNode() {
+      const node = this.graph.addNode({
+        shape: "flow-process",
+        x: 200,
+        y: 200,
+        attrs: {
+          body: {
+            fill: "#fff",
+            stroke: "#333",
+            strokeWidth: 2,
+            rx: 8,
+            ry: 8,
+            magnet: true,
+          },
+          label: {
+            text: "流程节点",
+            fontSize: 14,
+            fill: "#333",
+          },
+        },
+        data: {
+          type: "",
+          description: "",
+          properties: [],
+          conditions: [],
+        },
+        interactive: true,
+        selectable: true,
+      });
+    },
+
+    addEndNode() {
+      const node = this.graph.addNode({
+        shape: "flow-end",
+        x: 300,
+        y: 300,
+      });
+    },
+
+    bindEvents() {
+      // 修改双击事件绑定
+      this.graph.on("cell:dblclick", ({ node, e }) => {
+        console.log("Node double clicked:", node);
+        this.selectedNode = node;
+        const nodeData = node.getData() || {};
+
+        // 获取节点数据
+        this.editForm = {
+          label: node.attr("label/text") || "",
+          description: nodeData.description || "",
+          type: nodeData.type || "",
+          properties: Array.isArray(nodeData.properties)
+            ? nodeData.properties
+            : [],
+          conditions: Array.isArray(nodeData.conditions)
+            ? nodeData.conditions
+            : [],
+        };
+
+        this.editModalVisible = true;
+      });
+
+      // 添加单击事件用于测试事件绑定
+      this.graph.on("node:click", ({ node }) => {
+        console.log("Node clicked:", node);
+      });
+
+      // 连线完成事件
+      this.graph.on("edge:connected", ({ edge }) => {
+        edge.attr({
+          line: {
+            stroke: "#333",
+            strokeWidth: 2,
+            targetMarker: {
+              name: "classic",
+              size: 8,
+            },
+          },
+        });
+      });
+
+      // 快捷键支持
+      this.graph.bindKey(["meta+c", "ctrl+c"], () => {
+        const cells = this.graph.getSelectedCells();
+        if (cells.length) {
+          this.graph.copy(cells);
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+v", "ctrl+v"], () => {
+        if (!this.graph.isClipboardEmpty()) {
+          const cells = this.graph.paste({ offset: 32 });
+          this.graph.cleanSelection();
+          this.graph.select(cells);
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+z", "ctrl+z"], () => {
+        if (this.canUndo) {
+          this.undo();
+        }
+        return false;
+      });
+
+      this.graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
+        if (this.canRedo) {
+          this.redo();
+        }
+        return false;
+      });
+
+      // 添加边线双击事件
+      this.graph.on('edge:dblclick', ({ edge }) => {
+        this.selectedEdge = edge;
+        this.edgeLabelForm.label = edge.getLabels()?.[0]?.attrs?.text || '';
+        this.edgeLabelModalVisible = true;
+      });
+    },
+
+    handleEditOk() {
+      if (this.selectedNode) {
+        // 更新节点文本
+        this.selectedNode.attr("label/text", this.editForm.label);
+
+        // 更新节点数据
+        this.selectedNode.setData({
+          description: this.editForm.description,
+          type: this.editForm.type,
+          properties: this.editForm.properties,
+          conditions: this.editForm.conditions,
+        });
+      }
+      this.editModalVisible = false;
+    },
+
+    handleEditCancel() {
+      this.editModalVisible = false;
+    },
+
+    saveGraph() {
+      const data = this.graph.toJSON();
+      localStorage.setItem("flowData", JSON.stringify(data));
+      message.success("保存成功");
+    },
+
+    exportGraph() {
+      const data = this.graph.toJSON();
+      const dataStr = JSON.stringify(data, null, 2);
+      const blob = new Blob([dataStr], { type: "application/json" });
+      const url = URL.createObjectURL(blob);
+      const link = document.createElement("a");
+      link.download = "flow.json";
+      link.href = url;
+      link.click();
+    },
+
+    importGraph() {
+      const input = document.createElement("input");
+      input.type = "file";
+      input.accept = ".json";
+      input.onchange = (e) => {
+        const file = e.target.files[0];
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          try {
+            const data = JSON.parse(event.target.result);
+            this.graph.fromJSON(data);
+            message.success("导入成功");
+          } catch (error) {
+            message.error("导入失败:无效的文件格式");
+          }
+        };
+        reader.readAsText(file);
+      };
+      input.click();
+    },
+
+    clearGraph() {
+      this.graph.clearCells();
+    },
+
+    undo() {
+      this.history.undo();
+    },
+
+    redo() {
+      this.history.redo();
+    },
+
+    addProperty() {
+      this.editForm.properties.push({
+        key: "",
+        value: "",
+      });
+    },
+
+    removeProperty(index) {
+      this.editForm.properties.splice(index, 1);
+    },
+
+    addCondition() {
+      this.editForm.conditions.push({
+        expression: "",
+        description: "",
+      });
+    },
+
+    removeCondition(index) {
+      this.editForm.conditions.splice(index, 1);
+    },
+
+    handleEdgeLabelOk() {
+      if (this.selectedEdge) {
+        this.selectedEdge.setLabels([{
+          attrs: {
+            text: {
+              text: this.edgeLabelForm.label,
+              fill: '#333',
+              fontSize: 12,
+              textAnchor: 'middle',
+              textVerticalAnchor: 'middle',
+            },
+            rect: {
+              fill: '#fff',
+              stroke: '#333',
+              strokeWidth: 1,
+              rx: 3,
+              ry: 3,
+              refWidth: '100%',
+              refHeight: '100%',
+              refX: 0,
+              refY: 0,
+            }
+          },
+          position: {
+            distance: 0.5,
+            options: {
+              absoluteDistance: true,
+              reverseDistance: true,
+              keepGradient: true,
+              ensureLegibility: true,
+            }
+          }
+        }]);
+      }
+      this.edgeLabelModalVisible = false;
+    },
+
+    handleEdgeLabelCancel() {
+      this.edgeLabelModalVisible = false;
+    }
+  },
+};
+</script>
+
+<style scoped>
+.flow-container {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.graph-container {
+  flex: 1;
+  background-color: #f5f5f5;
+  position: relative;
+}
+
+.toolbar {
+  padding: 8px;
+  border-bottom: 1px solid #ddd;
+  background: #fff;
+}
+
+.property-item,
+.condition-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  gap: 10px;
+}
+
+.el-form-item {
+  margin-bottom: 22px;
+}
+</style>

+ 264 - 0
ui/src/views/flow/logicflow.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="flow-editor">
+    <div class="toolbar">
+      <el-button-group>
+        <el-button size="small" @click="downloadImage">导出图片</el-button>
+        <el-button size="small" @click="saveData">保存数据</el-button>
+        <el-button size="small" @click="clearGraph">清空画布</el-button>
+        <el-button size="small" @click="undo">撤销</el-button>
+        <el-button size="small" @click="redo">重做</el-button>
+      </el-button-group>
+    </div>
+    <div class="bpmn-example-container">
+      <div class="dnd-panel"></div>
+      <div ref="logicContainer" class="viewport" />
+    </div>
+  </div>
+</template>
+
+<script>
+import LogicFlow from "@logicflow/core";
+import { DndPanel, Control, Menu, SelectionSelect } from "@logicflow/extension";
+import "@logicflow/core/dist/style/index.css";
+import "@logicflow/extension/lib/style/index.css";
+/* import { StartNode, StartNodeModel } from "./nodes/StartNode";
+import { EndNode, EndNodeModel } from "./nodes/EndNode"; */
+
+export default {
+  data() {
+    return {
+      lf: null,
+      config: {
+        stopScrollGraph: false,
+        stopZoomGraph: false,
+        metaKeyMultipleSelected: true,
+        grid: {
+          size: 10,
+          type: "dot",
+        },
+        keyboard: {
+          enabled: true,
+        },
+        snapline: true,
+        background: {
+          color: "#f7f9ff",
+        },
+        nodeTextEdit: true,
+        edgeTextEdit: true,
+        plugins: [DndPanel, Control, Menu, SelectionSelect]
+      },
+    };
+  },
+  mounted() {
+    this.$nextTick(() => {
+      setTimeout(() => {
+        this.initLogicFlow();
+      }, 500);
+    });
+  },
+  methods: {
+    initLogicFlow() {
+      try {
+        const container = this.$refs.logicContainer;
+        if (!container) {
+          throw new Error('找不到画布容器元素');
+        }
+
+        this.lf = new LogicFlow({
+          container: container,
+          ...this.config,
+          pluginOptions: {
+            dndPanel: {
+              enable: true,
+              container: '#dnd-panel'
+            }
+          }
+        });
+        console.log("flow",this.lf);
+        this.registerNode();
+        this.lf.render();
+        this.initDndPanel();
+        console.log('LogicFlow和插件初始化成功');
+        
+      } catch (error) {
+        console.error('LogicFlow初始化失败:', error);
+      }
+    },
+    
+    initDndPanel() {
+      try {
+        if (!this.lf) {
+          throw new Error('LogicFlow实例未创建');
+        }
+        console.log(this.lf);
+        const dndPanel = this.lf.extension.dndPanel;
+        if (!dndPanel) {
+          throw new Error('DndPanel插件未正确加载');
+        }
+
+        dndPanel.setPatternItems([
+          {
+            label: '开始节点',
+            type: 'start',
+            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADWSURBVDhPY2AYNKCoqIgTiOcD8X8gBgFkOQZSDDwPxH+B+D8Qg8B9IP4MxLNhgkgG/gNiEAAZtBaI1wPxByA+DsT3gPgWEF8C4t1AbAnEbDADQRjEwQVABlEKQAY+ggpjA8+BeDkQGwGxGhA/hSrBBj4B8SwgVgViYySsjST3FYjPAjE/ELMiYTmomZ+A+AcQXwBiNiRsDjXzH9RQOAAZxoWEQeAzEP8G4mtAzIuEdaFmwg2kBEANfQPEF4GYDwmDXPsbauZqICbJUFIww+C2/QBif2ocCQBT+3tCYGwpmwAAAABJRU5ErkJggg==',
+            properties: {}
+          },
+          {
+            label: '结束节点',
+            type: 'end',
+            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADWSURBVDhPY2AYNKCoqIgTiOcD8X8gBgFkOQZSDDwPxH+B+D8Qg8B9IP4MxLNhgkgG/gNiEAAZtBaI1wPxByA+DsT3gPgWEF8C4t1AbAnEbDADQRjEwQVABlEKQAY+ggpjA8+BeDkQGwGxGhA/hSrBBj4B8SwgVgViYySsjST3FYjPAjE/ELMiYTmomZ+A+AcQXwBiNiRsDjXzH9RQOAAZxoWEQeAzEP8G4mtAzIuEdaFmwg2kBEANfQPEF4GYDwmDXPsbauZqICbJUFIww+C2/QBif2ocCQBT+3tCYGwpmwAAAABJRU5ErkJggg==',
+            properties: {}
+          },
+          {
+            label: '普通节点',
+            type: 'rect',
+            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAsSURBVDhPY2AYBaOA5gDIpxN5/x/CwQKQJUYBPWLYqIGjBo4aOGrg4DUQAPo5DuFWOqnJAAAAAElFTkSuQmCC',
+            properties: {}
+          },
+          {
+            label: '判断节点',
+            type: 'diamond',
+            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAA7SURBVDhPY2AYBaOAKMDIyPgfi4QYEhsrIC6Q/v//j1UBshyyHF6ArIBxNFBHA3U0UEcDdTRQRwN1GAAAWsMsD2BZkH8AAAAASUVORK5CYII=',
+            properties: {}
+          }
+        ]);
+
+        console.log('DndPanel初始化成功');
+        
+      } catch (error) {
+        console.error('DndPanel初始化失败:', error);
+      }
+    },
+    registerNode() {
+      try {
+        if (!this.lf) {
+          throw new Error('LogicFlow实例未创建');
+        }
+
+        const nodeTypes = [
+          { type: 'start', view: StartNode, model: StartNodeModel },
+          { type: 'end', view: EndNode, model: EndNodeModel }
+        ];
+
+        nodeTypes.forEach(({ type, view, model }) => {
+          this.lf.register({
+            type,
+            view,
+            model
+          });
+        });
+
+        console.log('节点注册成功');
+        
+      } catch (error) {
+        console.error('节点注册失败:', error);
+      }
+    },
+    downloadImage() {
+      this.lf.getSnapshot();
+    },
+    saveData() {
+      const data = this.lf.getGraphData();
+      console.log("流程数据", data);
+    },
+    clearGraph() {
+      this.lf.clearData();
+    },
+    undo() {
+      this.lf.undo();
+    },
+    redo() {
+      this.lf.redo();
+    },
+  },
+  beforeDestroy() {
+    if (this.lf) {
+      this.lf.dispose();
+      this.lf = null;
+    }
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.flow-editor {
+  width: 100%;
+  height: 100%;
+}
+
+.bpmn-example-container {
+  height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  background: #fff;
+  display: flex;
+  
+  .viewport {
+    flex: 1;
+    height: 100%;
+  }
+
+  .dnd-panel {
+    width: 200px;
+    height: 100%;
+    border-right: 1px solid #e8e8e8;
+    background: #f8f8f8;
+    padding: 10px;
+    box-sizing: border-box;
+  }
+}
+
+.toolbar {
+  padding: 8px;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+#dnd-panel {
+  width: 200px;
+  height: 100%;
+  position: relative;
+  background: #f8f8f8;
+  border-right: 1px solid #e8e8e8;
+  z-index: 999;
+  overflow: visible;
+}
+
+:deep(.lf-dnd-panel) {
+  position: relative !important;
+  width: 100% !important;
+  height: 100% !important;
+  
+  .lf-dnd-item {
+    margin-bottom: 10px;
+    display: flex;
+    align-items: center;
+    padding: 8px 16px;
+    background: #fff;
+    border: 1px solid #e8e8e8;
+    border-radius: 4px;
+    cursor: move;
+    
+    &:hover {
+      background: #e6f7ff;
+      border-color: #1890ff;
+    }
+    
+    img {
+      width: 20px;
+      height: 20px;
+      margin-right: 8px;
+    }
+  }
+}
+
+.node-start,
+.node-end,
+.node-rect,
+.node-diamond {
+  cursor: move;
+  
+  &:hover {
+    background: #e6f7ff;
+  }
+}
+</style>

+ 23 - 0
ui/src/views/flow/nodes/EndNode.js

@@ -0,0 +1,23 @@
+// src/views/flow/nodes/EndNode.js
+import { CircleNode, CircleNodeModel } from '@logicflow/core'
+
+export class EndNode extends CircleNode {}
+
+export class EndNodeModel extends CircleNodeModel {
+  constructor(data, graphModel) {
+    super(data, graphModel)
+    this.r = 30
+    this.fill = '#ff4d4f'
+    this.stroke = '#ff4d4f'
+    this.text.value = '结束'
+  }
+
+  getConnectedTargetRules() {
+    const rules = super.getConnectedTargetRules()
+    rules.push({
+      message: '结束节点不能作为连线的终点',
+      validate: () => false
+    })
+    return rules
+  }
+}

+ 23 - 0
ui/src/views/flow/nodes/StartNode.js

@@ -0,0 +1,23 @@
+// src/views/flow/nodes/StartNode.js
+import { CircleNode, CircleNodeModel } from '@logicflow/core'
+
+export class StartNode extends CircleNode {}
+
+export class StartNodeModel extends CircleNodeModel {
+  constructor(data, graphModel) {
+    super(data, graphModel)
+    this.r = 30
+    this.fill = '#55ff7f'
+    this.stroke = '#55ff7f'
+    this.text.value = '开始'
+  }
+
+  getConnectedSourceRules() {
+    const rules = super.getConnectedSourceRules()
+    rules.push({
+      message: '开始节点不能作为连线的起点',
+      validate: () => false
+    })
+    return rules
+  }
+}

+ 825 - 0
ui/src/views/flow/nodes/data.js

@@ -0,0 +1,825 @@
+const data = {
+    "nodes": [
+        {
+            "position": {
+                "x": 80,
+                "y": 240
+            },
+            "size": {
+                "width": 100,
+                "height": 60
+            },
+            "attrs": {
+                "body": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "fill": "#ffffff",
+                    "rx": 18,
+                    "ry": 18
+                },
+                "label": {
+                    "text": "开始",
+                    "fill": "#1890ff",
+                    "fontSize": 14,
+                    "textWrap": {
+                        "width": -10,
+                        "height": -10,
+                        "ellipsis": true
+                    }
+                },
+                "nodeData": {
+                    "type": "Start",
+                    "flowNode": {
+                        "entityId": "",
+                        "itemId": "",
+                        "flowNodeType": "",
+                        "groupIdentify": "",
+                        "childFlowIdentify": "",
+                        "methodName": "",
+                        "methodParams": "",
+                        "methodType": "",
+                        "hasMethodParams": false,
+                        "methodExp": "",
+                        "flowJudges": []
+                    }
+                }
+            },
+            "visible": true,
+            "shape": "rect",
+            "id": "a08519e5-4fe4-4cce-ad9f-fe8d0e08c27c",
+            "zIndex": 1,
+            "ports": {
+                "groups": {
+                    "top": {
+                        "position": "top",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "bottom": {
+                        "position": "bottom",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "left": {
+                        "position": "left",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "right": {
+                        "position": "right",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    }
+                },
+                "items": [
+                    {
+                        "id": "port1",
+                        "group": "top"
+                    },
+                    {
+                        "id": "port2",
+                        "group": "bottom"
+                    },
+                    {
+                        "id": "port3",
+                        "group": "left"
+                    },
+                    {
+                        "id": "port4",
+                        "group": "right"
+                    }
+                ]
+            }
+        },
+        {
+            "position": {
+                "x": 643,
+                "y": 240
+            },
+            "size": {
+                "width": 100,
+                "height": 60
+            },
+            "attrs": {
+                "body": {
+                    "stroke": "#000000",
+                    "strokeWidth": 1,
+                    "fill": "#ffffff",
+                    "rx": 18,
+                    "ry": 18
+                },
+                "label": {
+                    "text": "结束",
+                    "fill": "#000000",
+                    "fontSize": 14,
+                    "textWrap": {
+                        "width": -10,
+                        "height": -10,
+                        "ellipsis": true
+                    }
+                },
+                "nodeData": {
+                    "type": "End",
+                    "flowNode": {
+                        "entityId": "",
+                        "itemId": "",
+                        "flowNodeType": "",
+                        "groupIdentify": "",
+                        "childFlowIdentify": "",
+                        "methodName": "",
+                        "methodParams": "",
+                        "methodType": "",
+                        "hasMethodParams": false,
+                        "methodExp": "",
+                        "flowJudges": []
+                    }
+                }
+            },
+            "visible": true,
+            "shape": "rect",
+            "id": "1e580ed2-a3ae-4c6a-99b0-5362a641349a",
+            "zIndex": 2,
+            "ports": {
+                "groups": {
+                    "top": {
+                        "position": "top",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "bottom": {
+                        "position": "bottom",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "left": {
+                        "position": "left",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "right": {
+                        "position": "right",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    }
+                },
+                "items": [
+                    {
+                        "id": "port1",
+                        "group": "top"
+                    },
+                    {
+                        "id": "port2",
+                        "group": "bottom"
+                    },
+                    {
+                        "id": "port3",
+                        "group": "left"
+                    },
+                    {
+                        "id": "port4",
+                        "group": "right"
+                    }
+                ]
+            }
+        },
+        {
+            "position": {
+                "x": 263,
+                "y": 210
+            },
+            "size": {
+                "width": 120,
+                "height": 120
+            },
+            "attrs": {
+                "label": {
+                    "text": "条件节点",
+                    "fill": "#000000",
+                    "fontSize": 14,
+                    "textWrap": {
+                        "width": -50,
+                        "height": "70%",
+                        "ellipsis": true
+                    }
+                },
+                "body": {
+                    "fill": "#ffffff",
+                    "stroke": "#000000",
+                    "refPoints": "0,10 10,0 20,10 10,20",
+                    "strokeWidth": 1
+                },
+                "nodeData": {
+                    "type": "polygon",
+                    "flowNode": {
+                        "entityId": "",
+                        "itemId": "",
+                        "flowNodeType": "",
+                        "groupIdentify": "",
+                        "childFlowIdentify": "",
+                        "methodName": "",
+                        "methodParams": "",
+                        "methodType": "",
+                        "hasMethodParams": false,
+                        "methodExp": "",
+                        "flowJudges": []
+                    }
+                }
+            },
+            "visible": true,
+            "shape": "polygon",
+            "id": "4a5786b9-d792-4fca-ab6e-1ef3ca66cd8c",
+            "zIndex": 3,
+            "ports": {
+                "groups": {
+                    "top": {
+                        "position": "top",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "bottom": {
+                        "position": "bottom",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "left": {
+                        "position": "left",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "right": {
+                        "position": "right",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    }
+                },
+                "items": [
+                    {
+                        "id": "port1",
+                        "group": "top"
+                    },
+                    {
+                        "id": "port2",
+                        "group": "bottom"
+                    },
+                    {
+                        "id": "port3",
+                        "group": "left"
+                    },
+                    {
+                        "id": "port4",
+                        "group": "right"
+                    }
+                ]
+            }
+        },
+        {
+            "position": {
+                "x": 460,
+                "y": 100
+            },
+            "size": {
+                "width": 100,
+                "height": 60
+            },
+            "attrs": {
+                "body": {
+                    "stroke": "#000000",
+                    "strokeWidth": 1,
+                    "fill": "#ffffff"
+                },
+                "label": {
+                    "text": "任务节点1",
+                    "fill": "#000000",
+                    "fontSize": 14,
+                    "textWrap": {
+                        "width": -10,
+                        "height": -10,
+                        "ellipsis": true
+                    }
+                },
+                "nodeData": {
+                    "type": "Rect",
+                    "flowNode": {
+                        "entityId": "",
+                        "itemId": "",
+                        "flowNodeType": "",
+                        "groupIdentify": "",
+                        "childFlowIdentify": "",
+                        "methodName": "",
+                        "methodParams": "",
+                        "methodType": "",
+                        "hasMethodParams": false,
+                        "methodExp": "",
+                        "flowJudges": []
+                    }
+                }
+            },
+            "visible": true,
+            "shape": "rect",
+            "id": "0541c729-89e2-4b69-8987-4c46bf392291",
+            "zIndex": 4,
+            "ports": {
+                "groups": {
+                    "top": {
+                        "position": "top",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "bottom": {
+                        "position": "bottom",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "left": {
+                        "position": "left",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "right": {
+                        "position": "right",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    }
+                },
+                "items": [
+                    {
+                        "id": "port1",
+                        "group": "top"
+                    },
+                    {
+                        "id": "port2",
+                        "group": "bottom"
+                    },
+                    {
+                        "id": "port3",
+                        "group": "left"
+                    },
+                    {
+                        "id": "port4",
+                        "group": "right"
+                    }
+                ]
+            }
+        },
+        {
+            "position": {
+                "x": 460,
+                "y": 375
+            },
+            "size": {
+                "width": 100,
+                "height": 60
+            },
+            "attrs": {
+                "body": {
+                    "stroke": "#000000",
+                    "strokeWidth": 1,
+                    "fill": "#ffffff"
+                },
+                "label": {
+                    "text": "任务节点2",
+                    "fill": "#000000",
+                    "fontSize": 14,
+                    "textWrap": {
+                        "width": -10,
+                        "height": -10,
+                        "ellipsis": true
+                    }
+                },
+                "nodeData": {
+                    "type": "Rect",
+                    "flowNode": {
+                        "entityId": "",
+                        "itemId": "",
+                        "flowNodeType": "",
+                        "groupIdentify": "",
+                        "childFlowIdentify": "",
+                        "methodName": "",
+                        "methodParams": "",
+                        "methodType": "",
+                        "hasMethodParams": false,
+                        "methodExp": "",
+                        "flowJudges": []
+                    }
+                }
+            },
+            "visible": true,
+            "shape": "rect",
+            "id": "1ae306ed-4995-4c5c-a3d1-4b9d279a7be2",
+            "zIndex": 5,
+            "ports": {
+                "groups": {
+                    "top": {
+                        "position": "top",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "bottom": {
+                        "position": "bottom",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "left": {
+                        "position": "left",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    },
+                    "right": {
+                        "position": "right",
+                        "attrs": {
+                            "circle": {
+                                "r": 4,
+                                "magnet": true,
+                                "stroke": "#2D8CF0",
+                                "strokeWidth": 2,
+                                "fill": "#fff"
+                            }
+                        }
+                    }
+                },
+                "items": [
+                    {
+                        "id": "port1",
+                        "group": "top"
+                    },
+                    {
+                        "id": "port2",
+                        "group": "bottom"
+                    },
+                    {
+                        "id": "port3",
+                        "group": "left"
+                    },
+                    {
+                        "id": "port4",
+                        "group": "right"
+                    }
+                ]
+            }
+        }
+    ],
+    "edges": [
+        {
+            "shape": "edge",
+            "attrs": {
+                "line": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "targetMarker": {
+                        "name": "classic",
+                        "size": 8
+                    },
+                    "strokeDasharray": 0,
+                    "style": {
+                        "animation": "ant-line 30s infinite linear"
+                    }
+                }
+            },
+            "id": "d4aa73ba-9c33-451a-a1c6-03ce1132b41b",
+            "connector": "normal",
+            "router": {
+                "name": "normal"
+            },
+            "zIndex": 0,
+            "labels": [
+                {
+                    "text": ""
+                }
+            ],
+            "source": {
+                "cell": "a08519e5-4fe4-4cce-ad9f-fe8d0e08c27c",
+                "port": "port4"
+            },
+            "target": {
+                "cell": "4a5786b9-d792-4fca-ab6e-1ef3ca66cd8c",
+                "port": "port3"
+            }
+        },
+        {
+            "shape": "edge",
+            "attrs": {
+                "line": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "targetMarker": {
+                        "name": "classic",
+                        "size": 8
+                    },
+                    "strokeDasharray": 0,
+                    "style": {
+                        "animation": "ant-line 30s infinite linear"
+                    }
+                }
+            },
+            "id": "0b46c457-2d25-4b2b-932f-e5d494faa1a5",
+            "connector": "normal",
+            "router": {
+                "name": "normal"
+            },
+            "zIndex": 0,
+            "labels": [
+                {
+                    "text": ""
+                }
+            ],
+            "source": {
+                "cell": "4a5786b9-d792-4fca-ab6e-1ef3ca66cd8c",
+                "port": "port4"
+            },
+            "target": {
+                "cell": "0541c729-89e2-4b69-8987-4c46bf392291",
+                "port": "port3"
+            },
+            "vertices": [
+                {
+                    "x": 420,
+                    "y": 270
+                },
+                {
+                    "x": 420,
+                    "y": 130
+                }
+            ]
+        },
+        {
+            "shape": "edge",
+            "attrs": {
+                "line": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "targetMarker": {
+                        "name": "classic",
+                        "size": 8
+                    },
+                    "strokeDasharray": 0,
+                    "style": {
+                        "animation": "ant-line 30s infinite linear"
+                    }
+                }
+            },
+            "id": "f619ac70-3ba8-4d65-8115-b17f32ec8bf8",
+            "connector": "normal",
+            "router": {
+                "name": "normal"
+            },
+            "zIndex": 0,
+            "labels": [
+                {
+                    "attrs": {
+                        "label": {
+                            "text": "是"
+                        }
+                    }
+                }
+            ],
+            "source": {
+                "cell": "4a5786b9-d792-4fca-ab6e-1ef3ca66cd8c",
+                "port": "port4"
+            },
+            "target": {
+                "cell": "1ae306ed-4995-4c5c-a3d1-4b9d279a7be2",
+                "port": "port3"
+            },
+            "vertices": [
+                {
+                    "x": 420,
+                    "y": 270
+                },
+                {
+                    "x": 420,
+                    "y": 405
+                }
+            ]
+        },
+        {
+            "shape": "edge",
+            "attrs": {
+                "line": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "targetMarker": {
+                        "name": "classic",
+                        "size": 8
+                    },
+                    "strokeDasharray": 0,
+                    "style": {
+                        "animation": "ant-line 30s infinite linear"
+                    }
+                }
+            },
+            "id": "52e9c9ed-6b93-4acc-b9ee-0494e6b4af5c",
+            "connector": "normal",
+            "router": {
+                "name": "normal"
+            },
+            "zIndex": 0,
+            "labels": [
+                {
+                    "text": ""
+                }
+            ],
+            "source": {
+                "cell": "1ae306ed-4995-4c5c-a3d1-4b9d279a7be2",
+                "port": "port4"
+            },
+            "target": {
+                "cell": "1e580ed2-a3ae-4c6a-99b0-5362a641349a",
+                "port": "port3"
+            },
+            "vertices": [
+                {
+                    "x": 600,
+                    "y": 405
+                },
+                {
+                    "x": 600,
+                    "y": 270
+                }
+            ]
+        },
+        {
+            "shape": "edge",
+            "attrs": {
+                "line": {
+                    "stroke": "#1890ff",
+                    "strokeWidth": 1,
+                    "targetMarker": {
+                        "name": "classic",
+                        "size": 8
+                    },
+                    "strokeDasharray": 0,
+                    "style": {
+                        "animation": "ant-line 30s infinite linear"
+                    }
+                }
+            },
+            "id": "8a546bda-00ca-4d39-bc90-7e4c1cbdc7a1",
+            "connector": "normal",
+            "router": {
+                "name": "normal"
+            },
+            "zIndex": 0,
+            "labels": [
+                {
+                    "attrs": {
+                        "label": {
+                            "text": "任务节点1到结束"
+                        }
+                    }
+                }
+            ],
+            "source": {
+                "cell": "0541c729-89e2-4b69-8987-4c46bf392291",
+                "port": "port4"
+            },
+            "target": {
+                "cell": "1e580ed2-a3ae-4c6a-99b0-5362a641349a",
+                "port": "port3"
+            },
+            "vertices": [
+                {
+                    "x": 600,
+                    "y": 130
+                },
+                {
+                    "x": 600,
+                    "y": 270
+                }
+            ]
+        }
+    ]
+}
+export default data

+ 224 - 0
ui/src/views/flow/nodes/methods.js

@@ -0,0 +1,224 @@
+import '@antv/x6-vue-shape';
+import { Graph, Shape, Addon, FunctionExt, Dnd } from '@antv/x6';
+
+
+// 拖拽生成四边形或者圆形
+export const startDragToGraph = (graph, type, e) => {
+  const dnd = new Dnd({
+    target: graph,
+    validateNode: (droppingNode, options) => {
+      return true; // 可以根据需要添加验证逻辑
+    },
+  });
+
+  const node = type === 'Start' ? graph.createNode({
+    width: 100,
+    height: 60,
+    attrs: {
+      label: {
+        text: '开始',
+        fill: '#1890ff',
+        fontSize: 14,
+        textWrap: {
+          width: -10,
+          height: -10,
+          ellipsis: true,
+        },
+      },
+      body: {
+        stroke: '#1890ff',
+        strokeWidth: 1,
+        fill: '#ffffff',
+        rx: 18,
+        ry: 18
+      },
+      nodeData: getNodeData(type)
+    },
+    ports: ports,
+  }) : type === 'End' ? graph.createNode({
+    width: 100,
+    height: 60,
+    attrs: {
+      label: {
+        text: '结束',
+        fill: '#000000',
+        fontSize: 14,
+        textWrap: {
+          width: -10,
+          height: -10,
+          ellipsis: true,
+        },
+      },
+      body: {
+        stroke: '#000000',
+        strokeWidth: 1,
+        fill: '#ffffff',
+        rx: 18,
+        ry: 18
+      },
+      nodeData: getNodeData(type)
+    },
+    ports: ports
+  }) : type === 'Rect' ? graph.createNode({
+    width: 100,
+    height: 60,
+    attrs: {
+      label: {
+        text: '任务节点',
+        fill: '#000000',
+        fontSize: 14,
+        textWrap: {
+          width: -10,
+          height: -10,
+          ellipsis: true,
+        },
+      },
+      body: {
+        stroke: '#000000',
+        strokeWidth: 1,
+        fill: '#ffffff',
+      },
+      nodeData: getNodeData(type)
+    },
+    ports: ports
+  }) : type === 'Circle' ? graph.createNode({
+    shape: 'ellipse',
+    width: 100,
+    height: 100,
+    attrs: {
+      label: {
+        text: '循环节点',
+        fill: '#000000',
+        fontSize: 14,
+        textWrap: {
+          width: -20,
+          height: -10,
+          ellipsis: true,
+        },
+      },
+      body: {
+        stroke: '#000000',
+        strokeWidth: 1,
+        fill: '#ffffff',
+      },
+      nodeData: getNodeData(type)
+    },
+    ports: ports
+  }) : graph.createNode({
+    shape: 'polygon',
+    x: 40,
+    y: 40,
+    width: 120,
+    height: 120,
+    attrs: {
+      label: {
+        text: '条件节点',
+        fill: '#000000',
+        fontSize: 14,
+        textWrap: {
+          width: -50,
+          height: '70%',
+          ellipsis: true,
+        },
+      },
+      body: {
+        fill: '#ffffff',
+        stroke: '#000000',
+        refPoints: '0,10 10,0 20,10 10,20',
+        strokeWidth: 1,
+      },
+      nodeData: getNodeData(type)
+    },
+    ports: ports
+  })
+  dnd.start(node, e);
+};
+const ports = {
+  groups: {
+    // 输入链接桩群组定义
+    top: {
+      position: 'top',
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#2D8CF0',
+          strokeWidth: 2,
+          fill: '#fff',
+        },
+      },
+    },
+    // 输出链接桩群组定义
+    bottom: {
+      position: 'bottom',
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#2D8CF0',
+          strokeWidth: 2,
+          fill: '#fff',
+        },
+      },
+    },
+    left: {
+      position: 'left',
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#2D8CF0',
+          strokeWidth: 2,
+          fill: '#fff',
+        },
+      },
+    },
+    right: {
+      position: 'right',
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#2D8CF0',
+          strokeWidth: 2,
+          fill: '#fff',
+        },
+      },
+    },
+  },
+  items: [
+    {
+      id: 'port1',
+      group: 'top',
+    }, {
+      id: 'port2',
+      group: 'bottom',
+    }, {
+      id: 'port3',
+      group: 'left',
+    }, {
+      id: 'port4',
+      group: 'right',
+    }
+  ],
+};
+// 自定义参数存放处
+const getNodeData = (type) => {
+  return {
+    type: type,
+    flowNode: {  
+      entityId: '',
+      itemId: '',
+      flowNodeType: '',
+      groupIdentify: '',
+      childFlowIdentify: '',  //子流程
+      methodName: '',
+      methodParams: '',
+      methodType: '',
+      hasMethodParams: false,
+      methodExp: '',
+      flowJudges: [],
+      // isJudgeNode: '0'
+    }
+  }
+}

+ 162 - 0
ui/src/views/flow/nodes/right-drawer.vue

@@ -0,0 +1,162 @@
+<template>
+    <div ref="rightDrawer" class="right-drawer">
+      <div ref="miniMap" class="drawer-map"></div>
+      <div class="right-drawer-content">
+        <div class="right-drawer-title">属性面板</div>
+        <div class="right-drawer-body">
+          <div class="right-drawer-body-item">
+            <div class="right-drawer-body-item-title">节点名称</div>
+            <div class="right-drawer-body-item-content">
+              <input class="normal-input" type="text" v-model="drawerNode.nodeText" @change="changeNodeText"/>
+            </div>
+          </div>
+          <div class="right-drawer-body-item">
+            <div class="right-drawer-body-item-title">线条文本</div>
+            <div class="right-drawer-body-item-content">
+              <input class="normal-input" type="text" v-model="drawerEdge.edgeText" @change="changeEdgeText"/>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </template>
+  <script>
+  export default {
+    props: {
+      drawerType: {
+        type: String,
+        default: 'grid'
+      },
+      selectCell: {
+        type: Object | String,
+        default: () => {}
+      },
+      graph: {
+        type: Object | String,
+        default: () => {}
+      },
+      grid: {
+        type: Object
+      },
+    },
+    data() {
+      return {
+        drawerNode: {
+          fill: '',
+          nodeText: '',
+          fontSize: null,
+          fontFill: '',
+          strokeWidth: null,
+          stroke: '',
+          //其它参数
+          nodeType: '',
+          entityId: '',
+          itemId: '',
+          flowNodeType: '',
+          groupIdentify: '',
+          childFlowIdentify: '',
+          methodName: '',
+          methodParams: '',
+          methodType: '',
+          hasMethodParams: false,
+          methodExp: '',
+          flowJudges: [],
+          // isJudgeNode: '0'
+        },
+        drawerEdge: {
+          isCondition: false,
+          edgeText: '',
+          edgeSelect: true,
+          edgeWidth: null,
+          edgeColor: ''
+        },
+      }
+    },
+    watch: {
+      selectCell: {
+        handler(val) {
+          if (val) {
+            if (val.isNode()) { //节点
+              this.drawerNode.fill = val.store.data.attrs.body.fill;
+              this.drawerNode.nodeText = val.store.data.attrs.label.text;
+              this.drawerNode.fontFill = val.store.data.attrs.label.fill;
+              this.drawerNode.fontSize = Number(val.store.data.attrs.label.fontSize);
+              this.drawerNode.strokeWidth = Number(val.store.data.attrs.body.strokeWidth);
+              this.drawerNode.stroke = val.store.data.attrs.body.stroke;
+              this.drawerNode.nodeType = val.store.data.attrs.nodeData.type;
+              const flowNode = val.store.data.attrs.nodeData.flowNode;
+              this.drawerNode.entityId = flowNode.entityId;
+              this.drawerNode.itemId = flowNode.itemId;
+              this.drawerNode.flowNodeType = flowNode.flowNodeType;
+              this.drawerNode.groupIdentify = flowNode.groupIdentify;
+              this.drawerNode.childFlowIdentify = flowNode.childFlowIdentify;
+              this.drawerNode.methodExp = flowNode.methodExp;
+              this.drawerNode.methodType = flowNode.methodType;
+              this.drawerNode.flowJudges = flowNode.flowJudges;
+            } else { //边
+              this.drawerEdge.edgeText = val.store.data.labels ? val.store.data.labels[0].text != undefined ? val.store.data.labels[0].text : val.store.data.labels[0].attrs.label.text : '';
+              console.log('his.drawerEdge.edgeText',this.drawerEdge.edgeText)
+              this.drawerEdge.edgeWidth = Number(val.store.data.attrs.line.strokeWidth);
+              this.drawerEdge.edgeColor = val.store.data.attrs.line.stroke;
+              //处理条件连线
+              let sourceNode = this.graph.getCellById(val.store.data.source.cell);
+              // console.log(sourceNode, val)
+              //判断源节点是否为条件节点
+              // if (sourceNode.store.data.attrs.nodeData.type === 'polygon') {
+              //   this.drawerEdge.isCondition = true;
+              //   if (this.drawerEdge.edgeText === '') {
+              //     this.drawerEdge.edgeText = this.drawerEdge.edgeSelect ? '是' : '否';
+              //     //修改线条文本
+              //     this.changeEdgeText();
+              //   }
+              //   this.drawerEdge.edgeSelect = this.drawerEdge.edgeText === '是' ? true : false;
+              // } else {
+              //   this.drawerEdge.isCondition = false;
+              // }
+            }
+          }
+        },
+        immediate: true,
+        deep: false
+      },
+    },
+    methods: {
+      // 节点设置
+      changeNodeText() {
+        this.selectCell.attr('label/text', this.drawerNode.nodeText);
+      },
+     // 边设置
+     changeEdgeText() {
+        // console.log(this.drawerEdge.edgeText);
+        this.selectCell.setLabels(
+          [{ attrs: { label: { text: this.drawerEdge.edgeText } } }]
+        )
+      },
+    }
+  }
+  </script>
+  <style lang="scss" scoped>
+  .right-drawer{
+    margin-left: 20px;
+    flex: 1;
+    .drawer-map{
+      height: 150px;
+    }
+    .right-drawer-content{
+      margin-top: 20px;
+      .right-drawer-title{
+        font-size: 16px;
+        font-weight: bold;
+        line-height: 30px;
+        background-color: #e5f1f1;
+      }
+      .right-drawer-body-item-title{
+        line-height: 24px;
+      }
+      .normal-input{
+        width: 200px;
+        padding: 5px 10px;
+      }
+    }
+  }
+  </style>