使用场景,在开发的项目中需要实现的功能是两个表单通过用户鼠标点击连线的方式来给两个表的字段做数据映射关系,点击提交把用户创建的映射关系以map的形式传给后端。当时各种百度都没找到相关的文档可以参考,后来才发现关键字搜错了,一直围绕数据映射搜索,这个功能点更准确的来说是字段映射。后来无意见看到一个博主的文档,才知道可以用jsPlumb实现,然后各种搜jsPlumb进行了解,都比较零散,所以把我这边的实现进行了一下整理

官方中文文档地址:https://github.com/wangduanduan/jsplumb-chinese-tutorial

官方文档地址:https://docs.jsplumbtoolkit.com/community/current/index.html

效果图:

 

1,引入jsPlumb

npm install jsPlumb --save

2,在main.js中全局引入,也可局部引入

 import jsPlumb from 'jsplumb'  //可局部引入
 Vue.prototype.$jsPlumb = jsPlumb.jsPlumb //全局引入,挂载

3,基本元素组成

Source: 源对象。jsPlumb 通过元素的 id 属性获取对象。

Target: 目标对象。jsPlumb 通过元素的 id 属性获取对象。Source 和 Target 都可以是任何元素,区别是,Source 是起点,Target 是终点。 例如,connector 中的箭头总是从 Source 指向 Target。

Anchor:锚点。是 Source 和 Target 对象上可以连接 Connector 的点。Anchor 并不是一个视觉概念,它是不可见的。

Connector: 连接线。

Endpoint: 端点。需要注意的是,箭头并不是一种端点样式,它是通过 overlay 添加的。

Overlay: 添加到连接线上的附件。例如箭头和标签。

4,节点元素

//场景  两个表格做映射关系
<template>
 <div class="app-container">
   <el-row id="container" :gutter="20">
      <el-col :span="10">
        <table id="leftTable">
          <thead>
            <tr>
              <th>名称</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(data, index) in leftTableDataList" :id="data.name"    :key="index"  name="joint">
              <td>{{ data.name }}</td>
            </tr>
          </tbody>
        </table>
      </el-col>
      <el-col :span="10" :offset="4">
        <table id="rightTable">
          <thead>
            <tr>
              <th>名称</th>
            </tr>
          </thead>
          <tbody>
          <!-- 通过type来进行不同接口获取参数 返回数据结构不同 通过判断来进行对应展示-->
          <!-- 通过名称来做映射关系  做映射关系的左右两边id不可重复 所以添加了right的拼接,在后续传递的数据里面进行截取就好-->
            <tr v-for="(data, index) in rightTableDataList" :key="index"
              :id="'right' + (data.name ? data.name : data)" name="data">
              <td>{{ data.name ? data.name : data }}</td>
            </tr>
          </tbody>
        </table>
      </el-col>
    </el-row>
  </div>
</template>

css代码

.el-card ::v-deep .el-card__body {
  height: calc(100vh - 170px);
}
#leftTable, #rightTable {
  width: 100%;
  margin: 15px 0;
  border-collapse: collapse;
  tr th {
    color: #909399;
    font-weight: bold;
    background-color: #f5f5f5;
  }
  tr th, tr td {
    font-size: 14px;
    border: 1px solid #EBEEF5;
    padding:10px;
    text-align: center;
  }
}

5,实例化jsPlumb

<script>
//局部引入
import jsPlumb from "jsplumb";
//jsplumb使用
let $jsPlumb = jsPlumb.jsPlumb;
let jsPlumb_instance = null; // 缓存实例化的jsplumb对象
export default {
  data() {
    return {
      // 左侧对照数据
      leftTableDataList: [
        { name: "字段1" },
        { name: "字段2" },
        { name: "字段3" },
      ],
      // 右侧标准字典数据
      rightTableDataList: [
        { name: "字段1" },
        { name: "字段2" },
        { name: "字段3" },
        { name: "字段4" },
      ],
      pointList: null, //向后端传递的map映射关系
    };
  },
  methods: {
    showPlumb() {
      let self = this;
      // 清除端点、连接
      jsPlumb_instance.reset();
      this.pointList = new Map();
      this.$forceUpdate();
      jsPlumb_instance = $jsPlumb.getInstance({
        Container: "container", // 选择器id
        EndpointStyle: { radius: 0.11, fill: "#409EFF" }, // 端点样式
        PaintStyle: { stroke: "#409EFF", strokeWidth: 2 }, // 绘画样式,默认8px线宽  #456
        HoverPaintStyle: { stroke: "#1E90FF" }, // 默认悬停样式  默认为null
        ConnectionOverlays: [
          // 此处可以设置所有箭头的样式,因为我们要改变连接线的样式,故单独配置
          // Arrow-箭头  Label-标签  PlainArrow-平头箭头  Diamond-菱形  Diamond(钻石)-钻石箭头  Custom-自定义
          [
            "Arrow",
            {
              // 设置参数可以参考中文文档
              location: 1,
              length: 8,
              paintStyle: {
                stroke: "#409EFF",
                fill: "#409EFF",
              },
            },
          ],
        ],
        Connector: ["Straight"], // 连接器的类型:直线Straight,流程图flowchart,状态机state machine,贝塞尔曲线Bezier等 默认贝塞尔曲线
        DrapOptions: { cursor: "crosshair", zIndex: 2000 },
      });

      jsPlumb_instance.batch(() => {
        for (let i = 0; i < this.leftTableDataList.length; i++) {
          this.initLeaf(this.leftTableDataList[i].name, "joint");
        }
        for (let i = 0; i < this.rightTableDataList.length; i++) {
          this.initLeaf("right"+(this.rightTableDataList[i].name?this.rightTableDataList[i].name : this.rightTableDataList[i]),"data")
        }
      })

     this.initConnect()//默认连线
      const joint = document.getElementsByName("joint");
      const data = document.getElementsByName("data");

      jsPlumb_instance.setSourceEnabled(joint, true);
      jsPlumb_instance.setTargetEnabled(data, true);
      jsPlumb_instance.setDraggable(joint, false); // 是否支持拖拽
      jsPlumb_instance.setDraggable(data, false); // 是否支持拖拽
      jsPlumb_instance.bind("click", (conn, originalEvent) => {
        this.$confirm("确认删除映射么?", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          closeOnClickModal: false,
          type: "warning",
        })
          .then(() => {
            jsPlumb_instance.deleteConnection(conn);
          })
          .catch(() => {});
      });
      ///两个表进行关联时 连线
      jsPlumb_instance.bind("connection", function (info) {
        self.pointList.set(info.targetId.split("right")[1], info.sourceId);
      });
      // 删除连线
      jsPlumb_instance.bind("connectionDetached", function (evt) {
        if (self.pointList.has(evt.targetId.split("right")[1])) {
          self.pointList.delete(evt.targetId.split("right")[1]);
        }
      });
    },
    // 初始化具体节点
    initLeaf(key, type) {
      const ins = jsPlumb_instance;
      const elem = document.getElementById(key);
      if (type == "joint") {
        ins.makeSource(elem, {
          anchor: [1, 0.5, 0, 0], // 左 上 右 下
          allowLoopback: false,
          maxConnections: 1,
        });
      } else {
        ins.makeTarget(elem, {
          anchor: [0, 0.5, 0, 0],
          allowLoopback: false,
          maxConnections: 1,
        });
      }
    },
 initConnect(){
  //实现默认连线 一一对应连线
      const jsplumbConnectOptions ={
        isSource: true,
        isTarget: true,
        // 动态锚点、提供了4个方向 Continuous、AutoDefault
        anchor: "Continuous",
      }
      let length =   Math.min(this.leftTableDataList.length,this.rightTableDataList.length)
      jsPlumb_instance.deleteEveryConnection()
      for (var i = 0; i < length; i++) {
        jsPlumb_instance.connect({
          source:  this.leftTableDataList[i].name,
          target: 'right'+this.rightTableDataList[i].name,
        }, jsplumbConnectOptions);
      } 
    },
  },
};
</script>

6,这里是封装了子组件,在父组件中获取数据,进行调用(如果不封装子组件可直接在mounted用调用showPlumb进行初始化)

//父组件获取数据 调用初始化 jsPlumb
//通过this.$refs.step2Child调用父组件中的方法和变量
async next() {
//校验第一步表单 进行第二步映射
 this.$refs.step1Child.getValid().then((res) => {
        await getTableData1().then((response) => {
          this.$refs.step2Child.rightTableDataList= response
          })  
        })
        await getTableData2().then((response) => {
            this.$nextTick(function () {
             this.$refs.step2Child.leftTableDataList = response //给子组件中的表单赋值
             this.$refs.step2Child.showPlumb(); //调用子组件中的方法进行初始化
              this.current++; 
            })
          })  
      })
    },

注意:

 因为jsPlumb 是通过DOM进行工作的,所以我们需要把jsPlumb初始化 放在mounted声明,
如果是在子组件中完成,需要保证子组件渲染完成才可进行处理,可以放在进行初始化this.$nextTick(()=>{  } )

Logo

前往低代码交流专区

更多推荐