zoukankan      html  css  js  c++  java
  • jsplumb 流程图,常用功能配置记录

    前言:

    jsplumb 有2个版本一个Toolkit Edition(付费版),另外一个就是Community Edition(社区版本)。Toolkit Edition版本功能集成的比较丰富,社区版本的就差好多,很多功能都没有,需要我们自己去添加,当然了自己添加多多少少有些麻烦,而且也不完善。但是我们还是用Community Edition(社区版本),毕竟不收费,没办法,下边所说的版本默认都是以社区版。

    最近公司项目有用到这个流程图,所以也就看到了这个框架,这个框架是英文版本的,地址:https://jsplumbtoolkit.com/community/doc/home.html(可以用浏览器翻译了看)。他的缺陷就是文档不全,api感觉也有点乱,实例交代的也不清楚,github地址是:https://github.com/jsplumb/jsplumb (里面有demo,自己可以下载运行,多动手试试)。如果只是简单的画个图,这个框架是没有什么问题的,demo里也有,但是如果要实现高级的动能呢鞥,还是得多多尝试。此文也是记录一下我自己用到的一些功能,很多我还没用到,用到了在慢慢补充。

     
    jsplumb.png

    上图也就是我这次用到的jsplumb实现的功能,连接线能够拖拽生成,也可以删除,编辑label。

    1、数据结构

    {
      "nodes": [{  //节点集合
        "icon": "el-icon-loading",
        "id": "start",
        "nodeStyle": {
          "top": 100,
          "left": 200
        },
        "text": "开始",
        "type": "circle"
      }, {
        "icon": "el-icon-upload",
        "id": "end",
        "nodeStyle": {
          "top": 300,
          "left": 400
        },
        "text": "结束",
        "type": "circle"
      }] ,
      "connections": [{  //连接线集合
          "sourceId": "start",
          "targetId": "end",
          "label":"编辑"
        }]
    }
    

    jsplumb实例里面的数据结构就是这样的,这里我们沿用他的数据结构,你也可以自己定义自己想的数据结构,但是对比起来这个结构是最清晰也最方便的。

    2、初始化

    jsplumb在DOM渲染完毕之后才会执行,所以需要为jsplumb的执行代码绑定一个ready事件:

    jsPlumb.ready(function() { 
        // your jsPlumb related init code goes here
    });
    

    jsplumb默认是注册在浏览器窗口的,将整个页面提供给我们作为一个实例,但我们也可以使用getInstance方法建立页面中一个独立的实例:

    var _instance = jsPlumb.getInstance();
    

    3、功能实现(允许哪些元素拖拽,允许拆卸连接)

    let instance = jsPlumb.getInstance({
                    PaintStyle:{ 
                        strokeWidth:2, 
                        stroke:"#567567", 
                    }
                })
            //拖拽功能
            var els = document.querySelectorAll(".box");//.box是允许拖拽的元素class类名
            instance.draggable(els,{
               containment:true,
               filter: ".ep",//除去不能拖拽的,这里是个class类名
            });
            //不允许拆卸连接,不设置的话默认是可以的
            instance.importDefaults({ 
              ConnectionsDetachable:false
            });
    

    4、连线监听事件(拖动connection 事件)

            // 监听拖动connection 事件,判断是否有重复链接
            instance.bind("beforeDrop", function(info) {
                // info.connection.getOverlay("label").setLabel(info.connection.id);
                // 判断是否已有该连接
                let isSame = true;
                //下边的forEach循环就是处理数据结构里的connections不能自己跟自己连线。当然你也可以处理其他
                _this.chartData.connections.forEach(item => {
                  if ((item.targetId === info.targetId && item.sourceId === info.sourceId) ||  (item.targetId === info.sourceId && item.sourceId === info.targetId)) {
                    isSame = false;
                  }
                });
                if (isSame) {
                    //允许连线后处理的情况
                } else {
                  alert("不允许重复连接!");
                }
                return isSame;//这里返回了ture就会自定进行连线。
            });
    

    5、上图实现的完整代码

    下边代码就是实现上图的,需要指出的是运用了vue,但是里面掺杂了jquery,和jquery-ui,其实不想用这2个的,但是项目紧,之前项目也用到了,所以就延续了。还有就是上面代码是我自己的测试代码,写的可能有些杂乱,就是测试一个一个功能而写,写的有点乱。

    还有一个想说的就是之前想实现,缩放,引入了panzoom.js,流程图也实现了滚动鼠标放大放小,但是有个问题就是滚动鼠标放大放小后如果拖动单个元素或者连线,你就会发现鼠标点对不齐了,这点还没有解决,如果有好的方案,可以告知我下。Toolkit Edition(付费版)的这些功能都有,就不会出现这样的问题。

    <template>
      <div id="test6" style="height:100%;position:relative">
          <section id="focal"  style="position:relative;overflow:hidden;610px;height:610px;background:#fff;border:1px solid red">
            <div class="parent" id="parent" style="height:100%;">
              <div class="panzoom" id="panzoom" style="border:1px solid blue;6000px;height:6000px; transform:translate(-50%, -50%);position:absolute;">
                <div class="box" :id="item.id" :style="{'top':item.nodeStyle.top+'px','left':item.nodeStyle.left+'px'}" v-for="item in chartData.nodes" :key="item.id">
                    <i :class="item.icon" class="oldIcon" :title="item.text"></i>
                    <i class="el-icon-circle-close" style="display:none" :title="item.text" :id="item.id"></i>
                    <div class="ep"></div>
                </div>
              </div>
          </div>
        </section>
        <div class="source">
            <ul>
                <li v-for="(item,index) in list" :id="item.id" :key="index" class="sourceLi" :disabled="true" :data-icon="item.icon" :data-text="item.text" :data-type="item.type">{{item.text}}</li>
            </ul>
        </div>
    
            <el-dialog
                  title="修改label名称"
                  :visible.sync="dialogVisible"
                  width="30%"
                  :before-close="handleClose">
                  <el-input v-model="labelName" placeholder="请输入"></el-input>
                  <span slot="footer" class="dialog-footer">
                    <el-button @click="dialogVisible = false">取 消</el-button>
                    <el-button type="primary" @click="changeNote">确 定</el-button>
                  </span>
                </el-dialog>
      </div>
    </template>
    <script>
    import ChartNode from "@/components/ChartNode";
    export default {
      name: "test6",
      data() {
        return {
            dialogVisible:false,
            labelName:"",
            curSourceId:'',
            curTargetId:'',
            addLabelText:'',//拖拽后生成的连线label文字
            jsp:null,
            myscale:1,
            curScreen:[],//当前屏幕宽高
            chartData: {
                nodes: [],
                connections: [],//{ "targetId": "box2", "sourceId": "box1" }
                props: {},
                screen:[610,610]//提交屏幕宽高
            },
            list: [
              {
                icon: "el-icon-goods",
                text: "伴随车牌",
                type: "circle",
                id:'li1'
              },
              {
                icon: "el-icon-bell",
                text: "常住人口筛选",
                type: "diamond",
                id:"li2"
              },
              {
                icon: "el-icon-date",
                text: "伴随imsi",
                type: "circle",
                id:"li3"
              }
            ]
        };
      },
      mounted() {
      
        let _this = this
        jsPlumb.ready(function() {
            var $section = $('#focal');
            var $panzoom = $section.find('.panzoom').panzoom({ 
                 minScale: 0.3,
                 maxScale:2,
                 eventNamespace: ".panzoom",
                 $zoomRange: $(".jtk-endpoint"),
                 $set: $section.find('.jtk-overlay'),
                 eventsListenerElement: document.querySelector('.box')
            });
    
            $(document).on('mouseover','.box,.jtk-draggable,.jtk-overlay,.ep',function(){
              $('.panzoom').panzoom("disable");
            })
    
            $(document).on('mouseleave','.box,.jtk-draggable,.jtk-overlay,.ep',function(){
              $('.panzoom').panzoom("enable");
            })
    
            let instance = jsPlumb.getInstance({
                    PaintStyle:{ 
                        strokeWidth:2, 
                        stroke:"#567567", 
                    },
                    // Connector: ["Straight", { stub: [0,0], gap:[-30,-30] }],
                    Connector:[ "Straight", { curviness: 0 } ],
                    Endpoint: ["Blank",{ cssClass: "chart-dot", hoverClass: "chart-dot-hover", radius: 5 }],
                    EndpointStyle : { fill: "blue"  },
                    HoverPaintStyle:{
                        stroke:"red", 
                    },
                    DragOptions: { cursor: "pointer", zIndex: 2000 },
                    ConnectionOverlays: [
                      [
                        "Arrow",
                        {
                          location: 1,
                          visible: true,
                           11,
                          length: 11,
                          id: "ARROW",
                          events: {
                            click: function() {
                              alert("you clicked on the arrow overlay");
                            }
                          }
                        }
                      ],
                      ["Label", { label: "", id: "label", cssClass: "aLabel" }]
                    ],
                    Container: "panzoom"
                })
            _this.jsp = instance;
            //拖拽功能
            var els = document.querySelectorAll(".box");
            instance.draggable(els,{
               containment:true,
               filter: ".ep",//除去不能拖拽的
               grid:[50,50]
            });
            //不允许拆卸连接,不设置的话默认是可以的
            instance.importDefaults({ 
              ConnectionsDetachable:false
            });
            // 监听拖动connection 事件,判断是否有重复链接
            instance.bind("beforeDrop", function(info) {
                // info.connection.getOverlay("label").setLabel(info.connection.id);
                console.log(info);
                // 判断是否已有该连接
                let isSame = true;
                _this.chartData.connections.forEach(item => {
                  if ((item.targetId === info.targetId && item.sourceId === info.sourceId) ||  (item.targetId === info.sourceId && item.sourceId === info.targetId)) {
                    isSame = false;
                  }
                });
                if (isSame) {
                    _this.addLabelText = "新label"
                    _this.chartData.connections.push({
                        sourceId: info.sourceId,
                        targetId: info.targetId,
                        label:_this.addLabelText
                    });
                } else {
                  alert("不允许重复连接!");
                }
                return isSame;
            });
    
                var initNode = function(el) {
                         instance.draggable(el, {
                          // containment: true,
                          start(params) {
                            // 拖动开始
                            // console.log(params);
                          },
                          drag(params) {
                            // 拖动中
                            // console.log(params);
                          },
                          stop(params) {
                            // 拖动结束
                            console.log(params);
                            let id = params.el.id;
                            _this.chartData.nodes.forEach(item => {
                              if (item.id === id) {
                                item.nodeStyle.left = params.pos[0];
                                item.nodeStyle.top = params.pos[1] ;
                              }
                            });
                          }
                        });
                        instance.makeSource(el, {
                            filter: ".ep",
                            anchor: ["Perimeter", { shape: "Rectangle" }],
                            // anchor: ["Perimeter", { shape: "Dot" }],
                            connectorStyle: {
                                stroke: "#5c96bc",
                                strokeWidth: 2,
                                outlineStroke: "transparent",
                                outlineWidth: 4
                            },
                            extract: {
                                action: "the-action"
                            },
                            maxConnections: -1,
                            onMaxConnections: function(info, e) {
                                alert("Maximum connections (" + info.maxConnections + ") reached");
                            }
                        });
                        instance.makeTarget(el, {
                          dropOptions: { hoverClass: "dragHover" },
                          anchor: ["Perimeter", { shape: "Rectangle" }],
                          allowLoopback: false
                        });
    
                        // instance.fire("jsPlumbDemoNodeAdded", el);
                    };
                //初始化遮罩层   
                var init = function(connection) {
                    if(_this.addLabelText){
                        connection.getOverlay("label").setLabel(_this.addLabelText);
                    }else{
                         connection.getOverlay("label").setLabel('编辑');
                    }
                    $(connection.getOverlay("label").canvas).attr('mySourceId',connection.sourceId)
                    $(connection.getOverlay("label").canvas).attr('myTargetId',connection.targetId)
                };
    
      // 将模块拖入画板中
          $(".sourceLi").draggable({
            scope: "plant",
            helper: "clone",
            opacity: 0.7,
            containment: $("#test1")
          });
    
          $("#panzoom").droppable({
            scope: "plant",
            drop: function(ev, ui) {
              console.log(ev, ui);
              let helper = ui.helper;
              let id = jsPlumbUtil.uuid();
              let item = {
                id,
                icon: helper.attr("data-icon"),
                type: helper.attr("data-type"),
                text: helper.attr("data-text"),
                nodeStyle: {
                  top: ui.offset.top - $("#panzoom").offset().top ,
                  left: ui.offset.left - $("#panzoom").offset().left 
                }
              };
              console.log(ui.position)
              _this.chartData.nodes.push(item);
              _this.$nextTick(() => {
                initNode(id);
              });
            }
          });
    
             instance.batch(() => {
    
                    jsPlumb.getSelector(".box").forEach(item => {
                        console.log(item)
                        initNode(item);
                    });
    
                    instance.bind("connection", function(connInfo, originalEvent) {
                        init(connInfo.connection);
                        //显示删除按钮
                        $(connInfo.connection.getOverlay("label").canvas).hover(function() {
                            $(this).append('<div class="x" style="position: absolute;">X</div>');
                        }, function() {
                            $(this).find(".x").stop().remove();
                        })
                        //删除连接
                        $(connInfo.connection.getOverlay("label").canvas).on('click','.x',function(){
                            console.log("shanchu")
                            let _connections = _this.chartData.connections;
                            _connections.forEach((val,index)=>{
                                if(val.targetId == connInfo.connection.targetId && val.sourceId == connInfo.connection.sourceId){
                                    _connections.splice(index,1)
                                }
                            })
                            instance.deleteConnection(connInfo.connection);
                            $('.panzoom').panzoom("enable");//这个是为了杜绝删除前的禁止拖拽事件
                        })
                        //label双击事件
                        $(connInfo.connection.getOverlay("label").canvas).on("dblclick",function(conn, connInfo){
                            let _allConnections = _this.jsp.getAllConnections();
                            _this.dialogVisible = true
                            _this.curSourceId = $(conn.target).attr('mySourceId')
                            _this.curTargetId = $(conn.target).attr('myTargetId')
                            _allConnections.forEach((val,index)=>{
                                if(val.targetId == $(conn.target).attr('myTargetId') && val.sourceId == $(conn.target).attr('mySourceId')){
                                    _this.labelName =  val.getOverlay('label').label
                                }
                            })
                        })               
                    });
            });
            instance.fire("jsPlumbDemoLoaded", instance);
            $(document).on("dblclick",".box",function(){
                $(this).find(".oldIcon").css('display','none')
                $(this).find('.el-icon-circle-close').css('display','inline-block')
            })
            $(document).on("click",".el-icon-circle-close",function(){
                let _note = _this.chartData.nodes
                let _id = $(this).attr("id")
                let _connections = _this.chartData.connections;
                let _allConnections = instance.getAllConnections();
                _this.chartData.connections = _connections.filter((val)=>{
                    return (val.targetId != _id && val.sourceId != _id)
                })
                _note.forEach((val,index)=>{
                    if(val.id == _id){
                        _note.splice(index,1)
                    }
                })
                  _allConnections.forEach((val,index)=>{
                    if(val.targetId == _id || val.sourceId == _id){
                        instance.deleteConnectionsForElement(_id)
                    }
                })
            })
                _this.handleClickTemp(1)
          
    });
    
      },
      methods:{
            myclick(){
                alert("myclickmyclickmyclickmyclick")
            },
            // 初始化node节点
            initNode(el) {
              // initialise draggable elements.
              // 元素拖动,基于 katavorio.js 插件
              let _self = this;
              this.jsp.draggable(el, {
                // containment: true,
                start(params) {
                  // 拖动开始
                  // console.log(params);
                },
                drag(params) {
                  // 拖动中
                  // console.log(params);
                },
                stop(params) {
                  // 拖动结束
                  console.log(params);
                  let id = params.el.id;
                  _self.chartData.nodes.forEach(item => {
                    if (item.id === id) {
                      item.nodeStyle.left = params.pos[0]
                      item.nodeStyle.top = params.pos[1]
                    }
                  });
                }
              });
    
              this.jsp.makeSource(el, {
                filter: ".ep",
                // anchor: "Continuous",
                anchor: ["Perimeter", { shape: "Rectangle" }],
                connectorStyle: {
                  stroke: "#5c96bc",
                  strokeWidth: 2,
                  outlineStroke: "transparent",
                  outlineWidth: 4
                },
                extract: {
                  action: "the-action"
                },
                maxConnections: -1,
                onMaxConnections: function(info, e) {
                  alert("Maximum connections (" + info.maxConnections + ") reached");
                }
              });
    
              this.jsp.makeTarget(el, {
                dropOptions: { hoverClass: "dragHover" },
                anchor: ["Perimeter", { shape: "Rectangle" }],
                allowLoopback: false
              });
    
              // this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped
              // version of this demo to find out about new nodes being added.
              //
              this.jsp.fire("jsPlumbDemoNodeAdded", el);
            },
            handleClickTemp(key) {
              this.chartData = {
                nodes: [],
                connections: [],
                props: {}
              };
              this.jsp.empty("panzoom");
              if (key) {
                let url = "/static/json/" + 1 + ".json";
                this.$axios
                  .get(url)
                  .then(resp => {
                    console.log(resp);
                    
                    let _data = resp.data
                    let _reloatScreen = _data.screen
                    let _scale = $("#focal").width() / _data.screen[0]
                    let _focalWidth = $("#focal").width()
                    let _focalHeight = $("#focal").height()
                    let _panzoomWidth = $("#panzoom").width()
                    debugger
                    _data.nodes.forEach((val,index)=>{
                        val.nodeStyle.left = parseInt(val.nodeStyle.left) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 
                        val.nodeStyle.top  = parseInt(val.nodeStyle.top) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 
                    })
                    // $("#panzoom").css({'width':_panzoomWidth*_scale+'px','height':_panzoomWidth*_scale+'px'})
                    this.chartData = _data;
                    this.$nextTick(() => {
                      this.chartData.nodes.forEach(item => {
                        this.initNode(item.id);
                      });
                      this.chartData.connections.forEach(item => {
                        let _connects = this.jsp.connect({
                          source: item.sourceId,
                          target: item.targetId
                        });
                         _connects.getOverlay("label").setLabel(item.label)
                         $(_connects.getOverlay("label").canvas).attr('mySourceId',item.sourceId)
                         $(_connects.getOverlay("label").canvas).attr('myTargetId',item.targetId)
                      });
                    });
                  })
                  .catch(err => {
                    console.log(err);
                  });
              } else {
                this.$nextTick(() => {
                  this.chartData.nodes.push({
                    id: "start",
                    icon: "el-icon-loading",
                    type: "circle",
                    text: "开始",
                    nodeStyle: {
                      top: "100px",
                      left: "300px"
                    }
                  });
                  this.$nextTick(() => {
                    this.jsp.batch(() => {
                      this.initNode(jsPlumb.getSelector("#start"));
                    });
                  });
                });
              }
            },
            changeNote(){//修改label
                if(!this.labelName){
                    alert("名称没有填写")
                    return false
                }
                let _allConnections = this.jsp.getAllConnections();
                _allConnections.forEach((val,index)=>{
                    if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){
                        val.getOverlay("label").setLabel(this.labelName)
                    }
                })
                this.chartData.connections.forEach(val => {
                    if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){
                        val.label = this.labelName
                    }
               });
               this.dialogVisible = false
            },
            handleClose(){
                this.dialogVisible = false
            }
    
      },
      components: {
        ChartNode
      }
    };
    </script>
    
    <style lang="scss" scoped>
    #test1{
      position:relative;
      90%;
      height:90%;
      border:1px solid #ddd;
      background:#fff;
    }
    .box{
        border-radius:50%;
        text-align: center;
        cursor: pointer;
      background-color: white;
      border: 1px solid #346789;
      text-align: center;
      z-index: 24;
      cursor: pointer;
      box-shadow: 2px 2px 19px #aaa;
      -o-box-shadow: 2px 2px 19px #aaa;
      -webkit-box-shadow: 2px 2px 19px #aaa;
      -moz-box-shadow: 2px 2px 19px #aaa;
    
      position: absolute;
      color: black;
      padding: 0.5em;
       40px;
      height: 40px;
      display: flex;
      align-items: center;
      justify-content: center;
      -webkit-transition: -webkit-box-shadow 0.15s ease-in;
      -moz-transition: -moz-box-shadow 0.15s ease-in;
      -o-transition: -o-box-shadow 0.15s ease-in;
      transition: box-shadow 0.15s ease-in;
        .ep {
            opacity: 0;
            position: absolute;
            right: -10px;
            top: 0;
             10px;
            height: 10px;
            background: #409eff;
            border-radius: 5px;
          }
          &:hover {
            .ep {
              opacity: 1;
            }
          }
          &.dragHover {
            .ep {
              opacity: 0;
            }
          }
      }
    
    .box:hover {
      border: 1px solid #123456;
      box-shadow: 2px 2px 19px #444;
      -o-box-shadow: 2px 2px 19px #444;
      -webkit-box-shadow: 2px 2px 19px #444;
      -moz-box-shadow: 2px 2px 19px #fff;
      opacity: 0.9;
    }
    .box:hover,
    .box.jtk-source-hover,
    .box.jtk-target-hover {
      border: 1px solid orange;
      color: orange;
    }
    
    .box1{
      top:50px;
      left:50px;
    }
    .box2{
      top:160px;
      left:250px;
    }
    .box3{
      top:360px;
      left:150px;
    }
    .box4{
      top:350px;
      left:450px;
    }
    
    
    .chart-dot-hover{
      display: block;
      background: red
    }
    
    .source{
        position:absolute;
        top:50px;
        right:50px;
        border:1px solid red;
        200px;
        height:300px;
        li{
            line-height:36px;
            border:1px solid #ddd;
            margin-bottom:10px;
            cursor:pointer
        }
    }
    </style>
    
    <style>.aLabel{
           border: 1px solid blue;
           padding: 4px;
    }
    .x{
        top:-10px;
        right:-10px;
        cursor: pointer;
    }
    .jtk-overlay{
        padding: 0
    }
    </style>
    
     




  • 相关阅读:
    如何使用Java、Servlet创建二维码
    Java线程池主线程等待子线程执行完成
    Java多线程--让主线程等待所有子线程执行完毕
    查询及删除重复记录的方法
    聚集索引和非聚集索引
    数据库索引类型及实现方式
    各种排序算法的分析及java实现
    两个变量交换值的方法
    Java性能优化技巧
    JVM调优总结(九)-新一代的垃圾回收算法
  • 原文地址:https://www.cnblogs.com/zzsdream/p/10899911.html
Copyright © 2011-2022 走看看