jsplumb实现连线,表格之间实现批量字段映射

jsplumb + vue2

需求分析:两个表格分别展示需要拼接的数据,通过拖拽连线建立数据绑定关系。
实现步骤:
一、渲染表格数据
二、初始化节点数据
三、绑定事件
四、获取链接关系数据
1、根据已有数据初始化连线关系
2、新增数据不改变原连线添加节点
五、优化功能三:数据量大,表格滚动,滚动时连线跟随移动

#一、渲染数据
根据你的数据渲染出表格,注意绑定唯一字段,jsplumb使用

<template>
  <div class="app-container">
    <el-row id="container" :gutter="20">
      <el-col :span="11">
        <div id="leftTable" style="display: inline-block">
          <div style="text-indent: 0px; font-size: 16px;text-align: center;margin-bottom:5px">数据对象</div>
          <table style="height: 535px; overflow: scroll; display: inline-block"  @scroll="connect">
            <thead>
              <tr>
                <th
                  v-for="col in sourceColumn"
                  :key="col.dataIndex"
                  style=" text-align: left"
                  :style="{minWidth:col.width}"
                >
                  {{ col.title }}
                </th>
              </tr>
            </thead>
            <tbody>
              <tr
              style="overflow:hidden;"
                v-for="code in sourceList"
                :id="code[sourceKey]"
                :key="code"
                name="joint"
              >
                <td
                  v-for="col in sourceColumn"
                  :key="index + col.dataIndex"
                  style="text-align: left;"
                  :style="{
                    textIndent:
                      col.dataIndex == sourceKey
                        ? code.indent * 20 + 'px'
                        : '0px',
                        minWidth:col.width,
                  }"
                >
                  {{ code[col.dataIndex] }}
                </td>
                <span
                  v-if="code.data_type != 'Object' && code.data_type != 'Array' && !code.disabled && code.dict_type"
                  style="
                    display: inline-block;
                    width: 14px;
                    height: 14px;
                    border-radius: 50%;
                    background: #ccc;
                    margin-left: -15px;
                    transform: translateY(5px);
                  "
                  :style="{transform:'translateY('+code.top + ')'}"
                >
              </span>
              </tr>
            </tbody>
          </table>
        </div>
      </el-col>
      <el-col :span="12" :offset="1">
        <div
          id="rightTable"
          style="
            height: 555px;
            overflow-y: auto;
            display: inline-block;
            position: relative;
            padding-left: 50px;
          "
          @scroll="connect"
        >
          <div
            v-for="target in targetList"
            :key="target.title"
            style="position: relative; margin-bottom: 10px"
          >
            <div style="text-indent: 15px; font-size: 16px;text-align: center;margin-bottom:5px">
              {{ target.title }}
            </div>
            <table style="margin-left: 15px">
              <thead style="width: 100%">
                <tr>
                  <th
                    v-for="col in targetColumn"
                    :key="col.dataIndex"
                    style="
                      min-width: 180px;
                      text-align: left;
                      text-indent: 40px;
                    "
                  >
                    {{ col.title }}
                  </th>
                </tr>
              </thead>
              <tbody style="width: 100%">
                <tr
                  v-for="(data, index) in target.list"
                  :key="index"
                  :id="'right' + (data[targetKey] ? data[targetKey] : data)"
                  name="data"
                  style="position: relative"
                >
                  <td
                    style="text-align: left; text-indent: 40px"
                    v-for="col in targetColumn"
                    :key="index + col.dataIndex"
                  >
                    {{ data[col.dataIndex] }}
                  </td>
                  <span
                    style="
                      display: inline-block;
                      width: 14px;
                      height: 14px;
                      border-radius: 50%;
                      background: #ccc;
                      margin-top: 5px;
                      margin-left: -0px;
                      position: absolute;
                      left: 0;
                      top: 0;
                      z-index: 999;
                    "
                  ></span>
                </tr>
              </tbody>
            </table>
          </div>
          <div
            v-if="targetList.length == 0"
            style="
              color: #ffffff88;
              text-align: center;
              font-size: 14px;
              margin-top: 80px;
            "
          >
            请选择字典
          </div>
        </div>
      </el-col>
    </el-row>
  </div>
</template>

#二、初始化数据节点

<script>
//局部引入
import jsPlumb from "jsplumb";
//jsplumb使用
let $jsPlumb = jsPlumb.jsPlumb;
let jsPlumb_instance = null; // 缓存实例化的jsplumb对象
export default {
  data() {
    return {
      // 左侧对照数据
      // sourceList: [
      //   { name: "表字段1" },
      //   { name: "表字段2" },
      //   { name: "表字段3" }
      // ],
      // 右侧标准字典数据
      // targetList: [
      //   { name: "表字段1" },
      //   { name: "字段2" },
      //   { name: "字段3" },
      //   { name: "字段4" }
      // ],
      pointList: null, //向后端传递的map映射关系
      diffList: [],
      count: 0
    };
  },
  props: [
    "sourceList",
    "targetList",
    "targetColumn",
    "targetKey",
    "sourceColumn",
    "sourceKey"
  ],
  mounted() {
    setTimeout(() => {
      this.showPlumb();
    }, 100);
  },
  watch: {
    targetList(newval, oldval) {
      let oldList = oldval.map((val) => val.title);
      this.diffList = newval.filter((val) => !oldList.includes(val.title));
      setTimeout(() => {
        this.showDiff();
      }, 100);
    }
  },
  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: "#00c1de" }, // 端点样式
        PaintStyle: { stroke: "#00c1de", strokeWidth: 4 }, // 绘画样式,默认8px线宽  #456
        HoverPaintStyle: { stroke: "#1E90FF" }, // 默认悬停样式  默认为null
        ConnectionOverlays: [
          // 此处可以设置所有箭头的样式,因为我们要改变连接线的样式,故单独配置
          // Arrow-箭头  Label-标签  PlainArrow-平头箭头  Diamond-菱形  Diamond(钻石)-钻石箭头  Custom-自定义
          //   [
          //     "Arrow",
          //     {
          //       // 设置参数可以参考中文文档
          //       location: 1,
          //       length: 8,
          //       paintStyle: {
          //         // stroke: "#409EFF",
          //         // fill: "#409EFF"
          //         fill: '#7AB02C',
          // radius: 10
          //       }
          //     }
          //   ]
        ],
        Connector: ["Straight"], // 连接器的类型:直线Straight,流程图flowchart,状态机state machine,贝塞尔曲线Bezier等 默认贝塞尔曲线
        DrapOptions: { cursor: "crosshair", zIndex: 2000 }
        //     connectorStyle:{
        //       paintStyle: {
        //     fill: '#7AB02C',
        //     radius: 10
        // },
        //     }
      });
	//调用初始化节点
      jsPlumb_instance.batch(() => {
        for (let i = 0; i < this.sourceList.length; i++) {
        //根据条件筛选哪些数据可以连接
          if (
            this.sourceList[i].data_type != "Object" &&
            this.sourceList[i].data_type != "Array" && !this.sourceList[i].disabled && this.sourceList[i].dict_type
          ) {
            this.initLeaf(this.sourceList[i][this.sourceKey], "joint");
          }
        }
        for (let j = 0; j < this.targetList.length; j++) {
          for (let i = 0; i < this.targetList[j].list.length; i++) {
            this.initLeaf(
              "right" +
                (this.targetList[j].list[i][this.targetKey] ?
                  this.targetList[j].list[i][this.targetKey] :
                  this.targetList[j].list[i]),
              "data"
            );
          }
        }
      });

      const joint = document.getElementsByName("joint");
      const data = document.getElementsByName("data");

      jsPlumb_instance.setSourceEnabled(joint, true);
      jsPlumb_instance.setTargetEnabled(data, true);
      jsPlumb_instance.setDraggable(joint, true); // 是否支持拖拽
      jsPlumb_instance.setDraggable(data, true); // 是否支持拖拽
      jsPlumb_instance.bind("dblclick", (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.sourceId, info.targetId.split("right")[1]);
        self.$emit("change", self.pointList);
      });
      // 删除连线
      jsPlumb_instance.bind("connectionDetached", function (evt) {
        if (self.pointList.has(evt.sourceId)) {
          self.pointList.delete(evt.sourceId);
          self.$emit("change", self.pointList);
        }
      });
    },
    // 初始化具体节点
    initLeaf(key, type) {
      const ins = jsPlumb_instance;
      const elem = document.getElementById(key);
      // console.log(key, elem, 99);
      if (type == "joint") {
        ins.makeSource(elem, {
          anchor: [1, 0.5, 0, 0], // 左 上 右 下
          allowLoopback: false,
          maxConnections: 1,
          endpoint: "Dot"
        });
      } else {
        ins.makeTarget(elem, {
          anchor: [0, 0.5, 0, 0],
          allowLoopback: false,
          maxConnections: 100
          // endpoint: "Dot",
          // paintStyle: {
          //   fill: "blue",
          //   strokeWidth: 1
          // }
        });
      }
    },
    initLeft() {
      for (let i = 0; i < this.sourceList.length; i++) {
        if (
          this.sourceList[i].data_type != "Object" &&
          this.sourceList[i].data_type != "Array"
        ) {
          this.initLeaf(this.sourceList[i][this.sourceKey], "joint");
        }
      }
    }
  }
};
</script>

我这里用的props的数据,可以用自己的数据,注意数据结构即可
绑定事件上面方法以实现

#四、初始化已有的连接数据及右侧数据变化时将节点渲染为可连接节点

// 初始化字典连线
    initLines () {
      this.sourceList.forEach(item => {
        if(item.dict_data){
          console.log("right" + item.dict_data_path + "__" + item.dict_data + "__" +item.dict_data_type);
          
          jsPlumb_instance.connect({
            source: item[this.sourceKey],
            target:"right" + item.dict_data_path + "__" + item.dict_data + "__" +item.data_type
          });
        }
      })
    },

遍历需要连接的数据,调用实例的connect方法连接


watch: {
    targetList(newval, oldval) {
      let oldList = oldval.map((val) => val.title);
      this.diffList = newval.filter((val) => !oldList.includes(val.title));
      setTimeout(() => {
        this.showDiff();
      }, 100);
    }
  },
methods: {
    showDiff() {
      for (let j = 0; j < this.diffList.length; j++) {
        for (let i = 0; i < this.diffList[j].list.length; i++) {
          this.initLeaf(
            "right" +
              (this.diffList[j].list[i][this.targetKey] ?
                this.diffList[j].list[i][this.targetKey] :
                this.diffList[j].list[i]),
            "data"
          );
        }
      }
    },
    }

监听数据变化,将新增的数据渲染节点

#五、页面滚动,连线跟随变化
触发滚动事件时,将现有的连接关系保存,将原有的连线删除,重新简历连线,这里设置滚动三次触发一次方法

    connect() {
      this.count++;
      if (this.count >= 3) {
        let connections = jsPlumb_instance.getAllConnections();
        let linePoints = [];
        for (let i = connections.length - 1; i >= 0; i--) {
          linePoints.push([connections[i].sourceId, connections[i].targetId]);
          jsPlumb_instance.deleteConnection(connections[i]);
        }
        for (let i = 0; i < linePoints.length; i++) {
          jsPlumb_instance.connect({
            source: linePoints[i][0],
            target: linePoints[i][1]
          });
        }
        this.count = 0;
      }
    },
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值