Vue3 + jsPlumb

需求

目标:两组数据,分为两个列表展示,把对应的数据进行关联
关联方式:从左边列表拖拽出一条线关联到右边列表,一进一出,一一对应,无对应关系可不进行对应

选型

看了一些库,很多看起来都不支持类似功能(没有相关demo演示)
最终选定两个库:jsPlumb 和 antv G6

G6: 左右列表在G6内部生成,支持类型比较局限,控制显示内容的字段label好像只支持string,不支持自定义dom,灵活性不够,不能自定义关联的操作按钮

jsPlumb:可以自定义dom,通过id绑定的方式,与dom进行关联,相对灵活,操作按钮也可以自定义添加

使用

安装依赖:yarn add jsplumb
  1. 自定义配置 + 含义:
// jsPlumb默认配置
  jsPlumbSetting: {
    Container: "helen", // 在指定范围内划线,不能超出此范围,以id确定范围
    // 设置锚点类型:静态锚点,动态锚点,周边锚点,连续锚点【 https://docs.jsplumbtoolkit.com/toolkit/6.x/lib/anchors 】
    // Anchors: [
    //   "Continuous", // "Top","TopCenter","TopRight", "TopLeft", "Right", "RightMiddle", "Bottom", "BottomCenter", "BottomRight", "BottomLeft", "Left", "LeftMiddle","AutoDefault","Perimeter","Continuous" ],
    // 设置连线的样式:StateMachine、Flowchart,Bezier、Straight【 https://docs.jsplumbtoolkit.com/toolkit/6.x/lib/connectors 】
    Connector: ["Straight", { gap: 5 }], // gap: 与端点间的距离; stub: 线出发多远开始弯折
    // 鼠标是否拖动删除线
    ConnectionsDetachable: true,
    // 删除线的时候节点不删除
    DeleteEndpointsOnDetach: true,
    // 连线的两端端点类型:矩形 Rectangle;圆形Dot; eight: 矩形的高 ,idth: 矩形的宽
    Endpoint: ["Dot", { radius: 5, cssClass: "yellow" }], //radius hoverClass:不能是局部样式 cssClass:不能是局部样式
    // 线端点的样式: 不支持多个配置参数EndpointStyles
    // EndpointStyle: { fill: "skyblue", radius: 3 },
    // 连线样式
    PaintStyle: {
      stroke: "#000000",
      strokeWidth: 1,
      outlineStroke: "red", // 设定线外边的颜色
      outlineWidth: 0, // 设定线外边的宽,单位px
    },
    // 设置所有箭头的样式, Overlays绘制连线箭头
    ConnectionOverlays: [
      [
        "Arrow",
        {
          // 设置参数可以参考中文文档
          width: 8, // 箭头尾部的宽度
          length: 8, // 从箭头的尾部到头部的距离
          location: 1, // 位置,建议使用0~1之间
          // direction: 1, // 方向,默认值为1(表示向前),可选-1(表示向后)
          // foldback: 0.623, // 折回,也就是尾翼的角度,默认0.623,当为1时,为正三角
          // paintStyle: { stroke: "#999", fill: "#999" }, // 箭头样式
        },
      ],
    ],
    // 绘制图的模式 svg、canvas
    RenderMode: "svg",
    // ReattachConnections : true, //是否重新连接使用鼠标分离的线
    // DragOptions: { cursor: "pointer", zIndex: 2000 },
    DrapOptions: { cursor: "crosshair", zIndex: 2000 },
    // 鼠标滑过线的样式
    HoverPaintStyle: { stroke: "yellow", strokeWidth: 3, cursor: "pointer" },
  },
  1. 初始化
onMounted(async () => {
  // dom加载完成后才可以初始化jsplumb实例
  await nextTick()
  // 创建jsPlumb实例
  jsPlumbInstanceRef.value = jsPlumb.getInstance(); 
});

  1. 创建端点:
    创建端点的方式有三种:
    3.1 makeSource/makeTarget:源端点和目标端点分别绑定,将绑定的dom元素当作端点,无连线时,无端点显示;有连线时才显示端点
const elem = document.getElementById(item.id);
if (item.info.group === "list1") {
   jsPlumbInstanceRef.value.makeSource(elem, {
    anchor: "Continuous", // 左 上 右 下
    allowLoopback: false, // 允许回连
    maxConnections: 1, //最大连接数(-1表示不限制)
   });
} else {
   jsPlumbInstanceRef.value.makeTarget(elem, {
    anchor: "Continuous",
    allowLoopback: false,
    maxConnections: 1,
   });
}

3.2 connect:创建连线时,会同时创建前后两个端点和一条连线,连线断开时,两边端点也会消失,无法再次连接
(文档说配置deleteEndpointsOnDetach:true可以在删除连线时保留端点,但是实际操作时配置未生效)

data.lineList.forEach((item) => {
    jsPlumbInstanceRef.value.connect({
      source: item.sourceId,
      target: item.targetId,
      deleteEndpointsOnDetach:true, // 不生效,删除线后,端点依然消失了
    });
 });

3.3 addEndpoint【采用这种】:创建端点,可以绑定uuid,connect连接时可以通过uuid进行端点连线,防止connect多次创建两边的端点,导致遮挡,拖拽不生效问题

  data.nodeList.forEach((item, index) => {
    jsPlumbInstanceRef.value.addEndpoint(
      item.id,
       {
        anchor: [item.info.group === "list1" ? "Right" : "Left"],
        maxConnections: 1,
        uuid: item.id,
      },
      {
        isSource: item.info.group === "list1", // 是否可以作为源
        isTarget: item.info.group === "list2", // 是否可以作为目标
        maxConnections: 1,
      }
    );
  });
   // 通过uuid绑定端点连线
   data.lineList.forEach((item) => {
     jsPlumbInstanceRef.value.connect({uuids: [item.sourceId, item.targetId]});
   });
  1. 给节点添加拖拽::不是给锚点添加拖拽,锚点本身可以拖拽
 // 通过draggable给节点添加拖拽:不是给锚点添加拖拽,锚点本身可以拖拽
 jsPlumbInstanceRef.value.draggable(item.id);
  1. 对操作进行批处理:处理期间不触发渲染
jsPlumbInstanceRef.value.batch(() => {
  // 进行操作
})
  1. 等待实例渲染完成后进行操作
jsPlumbInstanceRef.value.ready(() => {
  // 进行操作
})
  1. 进行事件绑定
    //连线成功时触发
    jsPlumbInstanceRef.value.bind("connection", connectLine);
    //连线断开时 触发
    jsPlumbInstanceRef.value.bind("connectionDetached", connectLineDetached);
    //连接取消connectionAborted
    jsPlumbInstanceRef.value.bind(
      "connectionAborted",
      (conn, originalEvent) => {
        console.log("连接取消", conn, originalEvent);
        message.info("目标节点上已有关联属性,不支持该连接");
      }
    );
    //在连线上点击右键触发
    jsPlumbInstanceRef.value.bind("contextmenu", (conn, originalEvent) => {
      console.log("点击右键", conn, originalEvent);
    });
    //点击连线
    jsPlumbInstanceRef.value.bind("click", (conn, originalEvent) => {
      console.log("click事件", conn, originalEvent);
    });

    //超过端点数量触发
    jsPlumbInstanceRef.value.bind("onMaxConnections", (conn, originalEvent) => {
      console.log("超过端点数量触发", conn, originalEvent);
    });
  1. 实例方法
jsPlumbInstanceRef.value.repaintEverything(); // 重绘
jsPlumbInstanceRef.value.deleteEveryConnection(); // 断掉所有连线

总结:

// jsPlumb实例ready之后初始化一些设置
    jsPlumbInstanceRef.value.ready(() => {
      // 导入准备好的jsPlumb配置,通过importDefaults对默认配置进行覆盖
      jsPlumbInstanceRef.value.importDefaults(data.jsPlumbSetting);
      // 通过batch进行批处理操作:处理期间不触发渲染
      jsPlumbInstanceRef.value.batch(() => {
        // 对节点进行配置:创建端点,进行连线
        data.nodeList.forEach((item, index) => {
          jsPlumbInstanceRef.value.addEndpoint(
            item.id,
            {
              anchor: [item.info.group === "list1" ? "Right" : "Left"],
              maxConnections: 1,
              uuid: item.id,
            },
            {
              isSource: item.info.group === "list1", // 是否可以作为源
              isTarget: item.info.group === "list2", // 是否可以作为目标
              maxConnections: 1,
            }
          );
        });
        data.lineList.forEach((item) => {
          jsPlumbInstanceRef.value.connect(item);
        });
      });
    });
    jsPlumbInstanceRef.value.repaintEverything(); // 重绘

    //连线时触发 存储连接信息
    jsPlumbInstanceRef.value.bind("connection", connectLine);
    //连线断开时 触发
    jsPlumbInstanceRef.value.bind("connectionDetached", connectLineDetached);
    //连接取消connectionAborted
    jsPlumbInstanceRef.value.bind(
      "connectionAborted",
      (conn, originalEvent) => {
        console.log("连接取消", conn, originalEvent);
        message.info("目标节点上已有关联属性,不支持该连接");
      }
    );
    //在连线上点击右键触发
    jsPlumbInstanceRef.value.bind("contextmenu", (conn, originalEvent) => {
      console.log("点击右键", conn, originalEvent);
    });
    //点击连线
    jsPlumbInstanceRef.value.bind("click", (conn, originalEvent) => {
      console.log("click事件", conn, originalEvent);
    });

    //超过端点数量触发
    jsPlumbInstanceRef.value.bind("onMaxConnections", (conn, originalEvent) => {
      console.log("超过端点数量触发", conn, originalEvent);
    });

问题汇总

Q1. 端点无法跟随窗口大小改变进行自适应
A1. 需要监听窗口大小变化,进行重绘,注意监听需要销毁

window.addEventListener('resize', () => {
    jsPlumbInstanceRef.value.repaintEverything(); // 重绘
})

Q2. 页面上下滚动时,端点不跟随滚动
A2. 端点是通过绝对定位的方式,跟随上一级相对定位的盒子进行定位,需要给包裹的容器盒子添加属性 position: relative 进行相对定位

Q3. 通过addEndpoint 创建端点之后,再使用connect进行连线,导致端点重复生成
A3. connect进行连线会同时生成两个端点和一条线,如果之前已有端点,可以通过端点的uuid创建连线,防止重复生成端点。(本文例子便是这种方式)

Q4. 排序后,对应连线不跟随端点位置变化,进行更新
A4. 连线未跟随端点绑定,需要手动进行重绘

Q5. 原连线可以进行拖拽,通过更新ConnectionsDetachable:false设置为不可拖拽,但原有端点仍可进行拖拽
A5. 配置发生变化后,已生成端点不会更新状态,需要先全部断开,再重新划线 。

Q6. 删除源数据后,端点及连线没有跟随变化,仍停留在原有位置
A6. 重新设置实例,端点及连线

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容