vue2中使用jsplumb完成流程图

news2024/11/28 11:51:28

前言

之前的几个demo都是在vue3中写的,虽然可以直接拿去复用。

但是根据有些看客反馈,想用一个vue2版本的,毕竟很多人开发功能的时间都不是特别富裕。大多时候还是用现成的demo更好一些。

这里我就写一个简易版本的demo,可以实现绘制,并且删除连接线和节点等功能,篇幅也不大,适合应急的朋友,哈哈

代码

1.剥离公共的配置  config.js

export const readyConfig = {
    Container: 'plumbBox',
    anchor: ['Bottom', 'Top', 'Left', 'Right'],
    connector: 'Straight',
    endpoint: 'Blank',
    PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
    Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
    endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
}
export const sourceConfig = {
    filter: '.pointNode',
    filterExclude: false,
    allowLoopback: true,
    maxConnections: -1,
    Container: 'plumbBox',
    anchor: ['Bottom', 'Top', 'Left', 'Right'],
    connector: 'Straight',
    endpoint: 'Blank',
    PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
    Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
    endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
}
export const targetConfig = {
    filter: '.pointNode',
    filterExclude: false,
    allowLoopback: true,
    maxConnections: -1,
    Container: 'plumbBox',
    anchor: ['Bottom', 'Top', 'Left', 'Right'],
    connector: 'Straight',
    endpoint: 'Blank',
    PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
    Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
    endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
}

其实这些配置都大差不差,之所以分别罗列,是让大家搞明白,画布层级的配置,起点节点的配置以及终点节点的配置都是可以单独去配置,具体配置项可以看我之前的文档。

2.使用简易数据 data.js

export const leftMenuList = [
    {
        name: "节点1",
        id: "app1",
    },
    {
        name: "节点2",
        id: "app2",
    },
    {
        name: "节点3",
        id: "app3",
    },
    {
        name: "节点4",
        id: "app4",
    },
]

 这里的数据其实是左侧的数据

3.组件部分

<template>
  <div class="flowBox">
    <div class="leftMenu">
      <h4 @click="checkInfo">左侧菜单</h4>
      <draggable @start="start" @end="end" :sort="false">
        <div
          v-for="(item, index) in leftMenuList"
          :key="item.id"
          @mousedown="(el) => downNode(el, item)"
          class="leftNode"
        >
          {{ item.name }}
        </div>
        <h4>操作提示</h4>
        <hr />
        <p>左侧拖拽至右侧画布</p>
        <p>右键节点是删除节点,左键线条是删除线条</p>
      </draggable>
    </div>
    <div class="plumbBox" id="plumbBox">
      <div
        v-for="(item, index) in dataInfo"
        :key="item.id"
        :id="item.id"
        :style="nodeStyle(item)"
        @click.right="deleteNode($event, item)"
      >
        {{ item.name }}
        <div class="pointNode"></div>
      </div>
    </div>
  </div>
</template>
<script>
import { jsPlumb } from "jsplumb";
import { readyConfig, sourceConfig, targetConfig } from "./config";
import { leftMenuList } from "./data";
import { cloneDeep } from "lodash";
import draggable from "vuedraggable";
export default {
  name: "flowChart",
  components: { draggable },
  data() {
    return {
      //节点列表
      leftMenuList: leftMenuList,
      dataInfo: [],
      //plumb实例
      PlumbInit: null,
      //关系列表
      renderList: [],
      //画布信息(大小和位置)
      canvasInfo: null,
      //鼠标位置精准判定
      nodePositionDiff: null,
      //选中的左侧列表节点
      ativeNodeItem: null,
      //线条的信息
      deleteLineInfo: null,
    };
  },
  mounted() {
    this.jsPlumbInit();
    this.readyPlumbDataFun("once");
  },
  methods: {
    //初始化
    jsPlumbInit() {
      this.PlumbInit = jsPlumb.getInstance();
      this.PlumbInit.importDefaults(readyConfig);
    },
    //组织渲染用的数据
    readyPlumbDataFun(flag) {
      this.renderList = [];
      this.PlumbInit.deleteEveryConnection();
      this.PlumbInit.deleteEveryEndpoint();
      this.$nextTick(() => {
        this.dataInfo = cloneDeep(this.dataInfo);
        //根据数据创建关联信息(连线关系)
        this.dataInfo.forEach((item) => {
          if (item.to && item.to.length > 0) {
            item.to.forEach((item1) => {
              let nodeConfig = Object.assign(
                {
                  source: item.id,
                  target: item1,
                },
                readyConfig
              );
              this.renderList.push(nodeConfig);
            });
          }
          this.makeNodeConfig(item);
        });
        this.readyPlumbNodeFun(flag);
      });
    },
    //配置节点的具体信息
    makeNodeConfig(item) {
      this.PlumbInit.setSourceEnabled(item.id, true);
      this.PlumbInit.setTargetEnabled(item.id, true);
      this.PlumbInit.makeSource(item.id, sourceConfig);
      this.PlumbInit.makeTarget(item.id, targetConfig);
      this.PlumbInit.setDraggable(item.id, true);
      this.PlumbInit.draggable(item.id, {
        containment: "parent",
        stop: function (el) {
          item.left = el.pos[0];
          item.top = el.pos[1];
        },
      });
    },
    //渲染页面关系
    readyPlumbNodeFun(flag) {
      this.PlumbInit.ready(() => {
        this.renderList.forEach((item) => {
          this.PlumbInit.connect(item);
        });
        if (flag !== "once") {
          return;
        }
        //连线事件
        this.PlumbInit.bind("connection", (info) => {
          const sourceNode = this.dataInfo.find(
            (item) => item.id === info.sourceId
          );
          if (sourceNode.to.includes(info.targetId)) {
            return false;
          }
          console.log("调用了几次");
          sourceNode.to.push(info.targetId);
          // this.$nextTick(() => {
          //   this.readyPlumbDataFun()
          // })
          return true;
        });
        //点击线条
        this.PlumbInit.bind("click", (con) => {
          this.deleteLineInfo = {
            source: con.sourceId,
            target: con.targetId,
          };
          this.deleteLine(this.deleteLineInfo);
        });
      });
    },
    //plumbNode的样式
    nodeStyle(item) {
      return {
        position: "absolute",
        left: item.left + "px",
        top: item.top + "px",
        width: "200px",
        height: "40px",
        lineHeight: "40px",
        textAlign: "center",
        borderLeft: "2px solid blue",
        borderRadius: "4%",
        cursor: "pointer",
        boxShadow: "#eee 3px 3px 3px 3px",
      };
    },
    //拖动开始
    start() {},
    //拖动结束
    end(e) {
      this.refreshCanvas();
      // 判断位置
      this.judgePosition(
        this.ativeNodeItem,
        this.canvasInfo,
        e.originalEvent.x,
        e.originalEvent.y
      );
    },
    //添加节点
    addNode(positionInfo, nodeInfo) {
      if (this.dataInfo.find((item) => item.id === nodeInfo.id)) {
        this.$message.error("该节点已经存在");
        return;
      }
      this.dataInfo.push({
        name: nodeInfo.name,
        id: nodeInfo.id,
        left: positionInfo.left,
        top: positionInfo.top,
        to: [],
      });
      this.$nextTick(() => {
        this.readyPlumbDataFun();
      });
    },
    //删除节点
    deleteNode($event, nodeInfo) {
      $event.returnValue = false;
      let index = this.dataInfo.findIndex((item) => item.id === nodeInfo.id);
      this.dataInfo.splice(index, 1);
      this.readyPlumbDataFun();
    },
    //删除线
    deleteLine(deleteLineInfo) {
      let dataInfo = cloneDeep(this.dataInfo);
      let node = dataInfo.find((val) => val.id === deleteLineInfo.source);
      let index = node.to.findIndex((val) => val === deleteLineInfo.target);
      node.to.splice(index, 1);
      this.dataInfo = null;
      this.dataInfo = dataInfo;
      this.readyPlumbDataFun();
    },
    checkInfo() {
      console.log(this.dataInfo, "dataInfo");
      console.log(this.renderList, "渲染关系");
    },
    //获取画布信息
    refreshCanvas() {
      this.canvasInfo = document
        .querySelector("#plumbBox")
        .getBoundingClientRect();
    },
    //判断节点拖拽位置
    judgePosition(dragNodeInfo, plumbBoxPositionInfo, x, y) {
      if (
        x - this.nodePositionDiff.leftDiff < plumbBoxPositionInfo.left ||
        x + 200 - this.nodePositionDiff.leftDiff > plumbBoxPositionInfo.right ||
        y - this.nodePositionDiff.topDiff < plumbBoxPositionInfo.top ||
        y + 40 - this.nodePositionDiff.topDiff > plumbBoxPositionInfo.bottom
      ) {
        this.$message.error("节点不能拖拽至画布之外");
      } else {
        const positionInfo = {
          top: y - plumbBoxPositionInfo.top - this.nodePositionDiff.topDiff,
          left: x - plumbBoxPositionInfo.left - this.nodePositionDiff.leftDiff,
        };
        this.addNode(positionInfo, dragNodeInfo);
      }
    },
    //鼠标抬起时,距离判定
    downNode(el, nodeItem) {
      this.ativeNodeItem = nodeItem;
      const mousedownPositionInfo = { x: el.clientX, y: el.clientY };
      // 被拖拽节点初始的位置信息
      const moveBoxBeforePosition = {
        x: el.target.getBoundingClientRect().x,
        y: el.target.getBoundingClientRect().y,
      };
      this.nodePositionDiff = {
        leftDiff: mousedownPositionInfo.x - moveBoxBeforePosition.x,
        topDiff: mousedownPositionInfo.y - moveBoxBeforePosition.y,
      };
      console.log(this.nodePositionDiff, "位置判定");
    },
  },
};
</script>
<style scoped>
.flowBox {
  display: flex;
  height: 100%;
}

.leftMenu {
  width: 300px;
  /* height: 100%; */
  border: 1px solid #1a1919;
}

h4 {
  margin-top: 10px;
  margin-left: 110px;
}

.leftNode {
  width: 200px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  margin: 30px;
  border: dashed 1px #362c2c;
  cursor: move;
}

.plumbBox {
  flex: 1;
  /* height: 100%; */
  position: relative;
}
.pointNode {
  border-radius: 50%;
  width: 10px;
  height: 10px;
  background: royalblue;
  position: absolute;
  bottom: -5px;
  left: 95px;
}
</style>

各个函数的注释都写好了,操作方法也在其中,最主要的是根据插件暴露的api去领悟其中的每一个方法。

效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1295767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

麻雀1号开发板开箱

麻雀1号是上海睿赛德电子科技有限公司全新推出的一款高性价比音频Wi-Fi开发板&#xff0c;内置RT-Thread&#xff0c;主打 Wi-Fi、音频和摄像头拍照功能&#xff0c;配合丰富的组件及例程&#xff0c;可降低多媒体应用的开发门槛。 开发板介绍 正面&#xff1a; 背面&#x…

手搓图片滑动验证码_JavaScript进阶

手搓图片滑动验证码 背景代码效果图展示网站 背景 在做前端项目开发的时候&#xff0c;少不了登录注册部分&#xff0c;既然有登录注册就少不了机器人验证&#xff0c;验证的方法有很多种&#xff0c;比如短信验证码、邮箱验证码、图片滑动、图片验证码等。 由于鄙人在开发中…

docker安装与详细配置redis

docker安装redis 连接虚拟机 vagrant up //启动虚拟机 vagrant ssh //连接虚拟机进入root用户 su root输入密码&#xff1a;和账户名一样 vagrant 下载redis 直接下载redis镜像,下载redis最新镜像 docker pull redis下载的都是DockerHub中默认的官方镜像 创建文件目…

golang slice 数组针对某个字段进行排序

这里主要用到golang的sort.Sort方法&#xff0c;先看这个函数的简介&#xff1a; 介绍链接&#xff1a;https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter03/03.1.html 如何实现&#xff1a; import "sort"// UserInfo 用户信息结构…

Bionorica成功完成SAP S/4HANA升级 提升医药制造业务效率

企业如何成功地将其现有的ERP ECC系统转换升级到SAP S/4HANA&#xff0c; 并挖掘相关潜力来推动其数字化战略&#xff1f;Bionorica应用SNP软件实施了实时ERP套件&#xff0c;为进一步的增长和未来的创新奠定了基础。 草药市场的领导者&#xff1a;Bionorica Bionorica是世界领…

.NET使用分布式网络爬虫框架DotnetSpider快速开发爬虫功能

前言 前段时间有同学在微信群里提问&#xff0c;要使用.NET开发一个简单的爬虫功能但是没有做过无从下手。今天给大家推荐一个轻量、灵活、高性能、跨平台的分布式网络爬虫框架&#xff08;可以帮助 .NET 工程师快速的完成爬虫的开发&#xff09;&#xff1a;DotnetSpider。 注…

Java实现屏幕截图程序(一)

在Java中&#xff0c;可以使用Robot类来实现屏幕截图程序。Robot类提供了一组用于生成输入事件和控制鼠标和键盘的方法。 Java实现屏幕截图的步骤如下&#xff1a; 导入Robot类 import java.awt.Robot;创建Robot对象 Robot robot new Robot();获取屏幕分辨率信息 Dimensi…

力扣面试题 08.12. 八皇后(java回溯解法)

Problem: 面试题 08.12. 八皇后 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 八皇后问题的性质可以利用回溯来解决&#xff0c;将大问题具体分解成如下待解决问题&#xff1a; 1.以棋盘的每一行为回溯的决策阶段&#xff0c;判断当前棋盘位置能否放置棋子 2.如何判…

JavaScript <关于逆向RSA非对称加密算法的案例(代码剖析篇)>--案例(五点一)

引用上文: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/134857857 剖析: var bitsPerDigit16; // 每个数组元素可以表示的二进制位数// 数组复制函数&#xff0c;将源数组部分复制到目标数组的指定位置 function arrayCopy(src, srcStart, dest, destStart, n) {var m…

OpenVINS学习1——数据集配置与运行

前言 OpenVINS是基于MSCKF的开源VIO算法&#xff0c;有非常详细的官网文档可以学习使用&#xff0c;将来一段时间的主要实践工作&#xff0c;就是深度掌握这份开源代码。 https://docs.openvins.com/ 一、环境配置与Euroc数据集运行 我的环境是Ubuntu20.04&#xff0c;ROS1&a…

EasyX图形化学习

1.EasyX是什么&#xff1f; 是基于Windows的图形编程&#xff0c;给用户提供函数接口&#xff0c;最终函数调用会由Windows的API实现。 注&#xff1a;EasyX只适配 c 。 2.头文件&#xff1a; <easyx.h>---只包含最新的函数 <graphics.h>---包含<easyx.h&g…

前程无忧接口分析

前程无忧接口分析 所需用到的工具URL解析通过抓包软件或者开发者选项抓取数据包对代码中的参数解析分析对acw_sc__v2进行分析对acw_sc__v2进行转换代码生成生成outPutList数组生成arg2参数生成arg3参数最终的效果 对详情页面的分析对timestamp__1258的生成分析 所需用到的工具 …

一些系统日常运维命令和语句

一、前言 记录一些日常系统运维的命令和语句 二、linux命令与语句 1、linux查看各目录使用磁盘情况 du -h /home home为目录 du -h /home 2.查看内存使用情况 free -h 3、查看进程和CPU使用情况 top top 三、数据库语句 1、统计mysql数据库表数量 SELECT COUNT(*) A…

【爬取二手车并将数据保存在数据库中】

爬取二手车并将数据保存在数据库中 查看网页结构分析爬取步骤解密加密信息将密文解密代码&#xff1a; 进行爬取&#xff1a;爬取函数写入解密文件函数和获取城市函数解密文件&#xff0c;返回正确字符串函数保存到数据库 运行结果 查看网页结构分析爬取步骤 可以看出网页使用…

新版本AndroidStudio删除无用资源

第一步&#xff1a; 第二步&#xff1a; 第三步&#xff0c;等加载完&#xff0c;自己选择要删除的文件。 注意&#xff01;&#xff01;&#xff01; 可能会遇到没有显示无用资源&#xff0c;这时把项目运行在真机上就出来了。

学习pytorch19 pytorch使用GPU训练

pytorch使用GPU进行训练 1. 数据 模型 损失函数调用cuda()2. 使用谷歌免费GPU gogle colab 需要创建谷歌账号登录使用, 网络能访问谷歌3. 执行4. 代码 B站土堆学习视频&#xff1a; https://www.bilibili.com/video/BV1hE411t7RN/?p30&spm_id_frompageDriver&vd_sourc…

Python Authlib库:构建安全可靠的身份验证系统

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在现代应用程序中&#xff0c;安全性是至关重要的&#xff0c;特别是在处理用户身份验证时。Authlib库为Python开发者提供了一套强大的工具&#xff0c;用于简化和增强身份验证和授权流程。本文将深入探讨Authli…

js/jQuery常见操作 之各种语法例子(包括jQuery中常见的与索引相关的选择器)

js/jQuery常见操作 之各种语法例子&#xff08;包括jQuery中常见的与索引相关的选择器&#xff09; 1. 操作table常见的1.1 动态给table添加title&#xff08;指定td&#xff09;1.1.1 给td动态添加title&#xff08;含&#xff1a;获取tr的第几个td&#xff09;1.1.2 动态加工…

elasticsearch聚合、自动补全、数据同步

目录 一、数据聚合1.1 聚合的种类1.2 DSL实现聚合1.2.1 Bucket聚合语法1.2.2 聚合结果排序1.2.3 限定聚合范围1.2.4 Metric聚合语法 1.3 RestAPI实现聚合 二、自动补全2.1 拼音分词器2.2 自定义分词器2.3 自动补全查询2.4 RestAPI实现自动补全 三、数据同步3.1 思路分析3.1.1 同…

Java来实现二叉树算法,将一个二叉树左右倒置(左右孩子节点互换)

文章目录 二叉树算法二叉树左右变换数据 今天来和大家谈谈常用的二叉树算法 二叉树算法 二叉树左右变换数据 举个例子&#xff1a; Java来实现二叉树算法&#xff0c;将一个二叉树左右倒置&#xff08;左右孩子节点互换&#xff09;如下图所示 实现的代码如下&#xff1a;以…