当前位置:   article > 正文

vue2中使用jsplumb完成流程图_vue jsplumb

vue jsplumb

前言

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

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

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

代码

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

  1. export const readyConfig = {
  2. Container: 'plumbBox',
  3. anchor: ['Bottom', 'Top', 'Left', 'Right'],
  4. connector: 'Straight',
  5. endpoint: 'Blank',
  6. PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
  7. Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
  8. endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
  9. }
  10. export const sourceConfig = {
  11. filter: '.pointNode',
  12. filterExclude: false,
  13. allowLoopback: true,
  14. maxConnections: -1,
  15. Container: 'plumbBox',
  16. anchor: ['Bottom', 'Top', 'Left', 'Right'],
  17. connector: 'Straight',
  18. endpoint: 'Blank',
  19. PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
  20. Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
  21. endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
  22. }
  23. export const targetConfig = {
  24. filter: '.pointNode',
  25. filterExclude: false,
  26. allowLoopback: true,
  27. maxConnections: -1,
  28. Container: 'plumbBox',
  29. anchor: ['Bottom', 'Top', 'Left', 'Right'],
  30. connector: 'Straight',
  31. endpoint: 'Blank',
  32. PaintStyle: { stroke: '#8b8c8d', strokeWidth: 2, outlineStroke: 'transparent', outlineWidth: 10 },
  33. Overlays: [['Arrow', { width: 10, length: 5, location: 0.5, direction: 1 }]],
  34. endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 }
  35. }

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

2.使用简易数据 data.js

  1. export const leftMenuList = [
  2. {
  3. name: "节点1",
  4. id: "app1",
  5. },
  6. {
  7. name: "节点2",
  8. id: "app2",
  9. },
  10. {
  11. name: "节点3",
  12. id: "app3",
  13. },
  14. {
  15. name: "节点4",
  16. id: "app4",
  17. },
  18. ]

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

3.组件部分

  1. <template>
  2. <div class="flowBox">
  3. <div class="leftMenu">
  4. <h4 @click="checkInfo">左侧菜单</h4>
  5. <draggable @start="start" @end="end" :sort="false">
  6. <div
  7. v-for="(item, index) in leftMenuList"
  8. :key="item.id"
  9. @mousedown="(el) => downNode(el, item)"
  10. class="leftNode"
  11. >
  12. {{ item.name }}
  13. </div>
  14. <h4>操作提示</h4>
  15. <hr />
  16. <p>左侧拖拽至右侧画布</p>
  17. <p>右键节点是删除节点,左键线条是删除线条</p>
  18. </draggable>
  19. </div>
  20. <div class="plumbBox" id="plumbBox">
  21. <div
  22. v-for="(item, index) in dataInfo"
  23. :key="item.id"
  24. :id="item.id"
  25. :style="nodeStyle(item)"
  26. @click.right="deleteNode($event, item)"
  27. >
  28. {{ item.name }}
  29. <div class="pointNode"></div>
  30. </div>
  31. </div>
  32. </div>
  33. </template>
  34. <script>
  35. import { jsPlumb } from "jsplumb";
  36. import { readyConfig, sourceConfig, targetConfig } from "./config";
  37. import { leftMenuList } from "./data";
  38. import { cloneDeep } from "lodash";
  39. import draggable from "vuedraggable";
  40. export default {
  41. name: "flowChart",
  42. components: { draggable },
  43. data() {
  44. return {
  45. //节点列表
  46. leftMenuList: leftMenuList,
  47. dataInfo: [],
  48. //plumb实例
  49. PlumbInit: null,
  50. //关系列表
  51. renderList: [],
  52. //画布信息(大小和位置)
  53. canvasInfo: null,
  54. //鼠标位置精准判定
  55. nodePositionDiff: null,
  56. //选中的左侧列表节点
  57. ativeNodeItem: null,
  58. //线条的信息
  59. deleteLineInfo: null,
  60. };
  61. },
  62. mounted() {
  63. this.jsPlumbInit();
  64. this.readyPlumbDataFun("once");
  65. },
  66. methods: {
  67. //初始化
  68. jsPlumbInit() {
  69. this.PlumbInit = jsPlumb.getInstance();
  70. this.PlumbInit.importDefaults(readyConfig);
  71. },
  72. //组织渲染用的数据
  73. readyPlumbDataFun(flag) {
  74. this.renderList = [];
  75. this.PlumbInit.deleteEveryConnection();
  76. this.PlumbInit.deleteEveryEndpoint();
  77. this.$nextTick(() => {
  78. this.dataInfo = cloneDeep(this.dataInfo);
  79. //根据数据创建关联信息(连线关系)
  80. this.dataInfo.forEach((item) => {
  81. if (item.to && item.to.length > 0) {
  82. item.to.forEach((item1) => {
  83. let nodeConfig = Object.assign(
  84. {
  85. source: item.id,
  86. target: item1,
  87. },
  88. readyConfig
  89. );
  90. this.renderList.push(nodeConfig);
  91. });
  92. }
  93. this.makeNodeConfig(item);
  94. });
  95. this.readyPlumbNodeFun(flag);
  96. });
  97. },
  98. //配置节点的具体信息
  99. makeNodeConfig(item) {
  100. this.PlumbInit.setSourceEnabled(item.id, true);
  101. this.PlumbInit.setTargetEnabled(item.id, true);
  102. this.PlumbInit.makeSource(item.id, sourceConfig);
  103. this.PlumbInit.makeTarget(item.id, targetConfig);
  104. this.PlumbInit.setDraggable(item.id, true);
  105. this.PlumbInit.draggable(item.id, {
  106. containment: "parent",
  107. stop: function (el) {
  108. item.left = el.pos[0];
  109. item.top = el.pos[1];
  110. },
  111. });
  112. },
  113. //渲染页面关系
  114. readyPlumbNodeFun(flag) {
  115. this.PlumbInit.ready(() => {
  116. this.renderList.forEach((item) => {
  117. this.PlumbInit.connect(item);
  118. });
  119. if (flag !== "once") {
  120. return;
  121. }
  122. //连线事件
  123. this.PlumbInit.bind("connection", (info) => {
  124. const sourceNode = this.dataInfo.find(
  125. (item) => item.id === info.sourceId
  126. );
  127. if (sourceNode.to.includes(info.targetId)) {
  128. return false;
  129. }
  130. console.log("调用了几次");
  131. sourceNode.to.push(info.targetId);
  132. // this.$nextTick(() => {
  133. // this.readyPlumbDataFun()
  134. // })
  135. return true;
  136. });
  137. //点击线条
  138. this.PlumbInit.bind("click", (con) => {
  139. this.deleteLineInfo = {
  140. source: con.sourceId,
  141. target: con.targetId,
  142. };
  143. this.deleteLine(this.deleteLineInfo);
  144. });
  145. });
  146. },
  147. //plumbNode的样式
  148. nodeStyle(item) {
  149. return {
  150. position: "absolute",
  151. left: item.left + "px",
  152. top: item.top + "px",
  153. width: "200px",
  154. height: "40px",
  155. lineHeight: "40px",
  156. textAlign: "center",
  157. borderLeft: "2px solid blue",
  158. borderRadius: "4%",
  159. cursor: "pointer",
  160. boxShadow: "#eee 3px 3px 3px 3px",
  161. };
  162. },
  163. //拖动开始
  164. start() {},
  165. //拖动结束
  166. end(e) {
  167. this.refreshCanvas();
  168. // 判断位置
  169. this.judgePosition(
  170. this.ativeNodeItem,
  171. this.canvasInfo,
  172. e.originalEvent.x,
  173. e.originalEvent.y
  174. );
  175. },
  176. //添加节点
  177. addNode(positionInfo, nodeInfo) {
  178. if (this.dataInfo.find((item) => item.id === nodeInfo.id)) {
  179. this.$message.error("该节点已经存在");
  180. return;
  181. }
  182. this.dataInfo.push({
  183. name: nodeInfo.name,
  184. id: nodeInfo.id,
  185. left: positionInfo.left,
  186. top: positionInfo.top,
  187. to: [],
  188. });
  189. this.$nextTick(() => {
  190. this.readyPlumbDataFun();
  191. });
  192. },
  193. //删除节点
  194. deleteNode($event, nodeInfo) {
  195. $event.returnValue = false;
  196. let index = this.dataInfo.findIndex((item) => item.id === nodeInfo.id);
  197. this.dataInfo.splice(index, 1);
  198. this.readyPlumbDataFun();
  199. },
  200. //删除线
  201. deleteLine(deleteLineInfo) {
  202. let dataInfo = cloneDeep(this.dataInfo);
  203. let node = dataInfo.find((val) => val.id === deleteLineInfo.source);
  204. let index = node.to.findIndex((val) => val === deleteLineInfo.target);
  205. node.to.splice(index, 1);
  206. this.dataInfo = null;
  207. this.dataInfo = dataInfo;
  208. this.readyPlumbDataFun();
  209. },
  210. checkInfo() {
  211. console.log(this.dataInfo, "dataInfo");
  212. console.log(this.renderList, "渲染关系");
  213. },
  214. //获取画布信息
  215. refreshCanvas() {
  216. this.canvasInfo = document
  217. .querySelector("#plumbBox")
  218. .getBoundingClientRect();
  219. },
  220. //判断节点拖拽位置
  221. judgePosition(dragNodeInfo, plumbBoxPositionInfo, x, y) {
  222. if (
  223. x - this.nodePositionDiff.leftDiff < plumbBoxPositionInfo.left ||
  224. x + 200 - this.nodePositionDiff.leftDiff > plumbBoxPositionInfo.right ||
  225. y - this.nodePositionDiff.topDiff < plumbBoxPositionInfo.top ||
  226. y + 40 - this.nodePositionDiff.topDiff > plumbBoxPositionInfo.bottom
  227. ) {
  228. this.$message.error("节点不能拖拽至画布之外");
  229. } else {
  230. const positionInfo = {
  231. top: y - plumbBoxPositionInfo.top - this.nodePositionDiff.topDiff,
  232. left: x - plumbBoxPositionInfo.left - this.nodePositionDiff.leftDiff,
  233. };
  234. this.addNode(positionInfo, dragNodeInfo);
  235. }
  236. },
  237. //鼠标抬起时,距离判定
  238. downNode(el, nodeItem) {
  239. this.ativeNodeItem = nodeItem;
  240. const mousedownPositionInfo = { x: el.clientX, y: el.clientY };
  241. // 被拖拽节点初始的位置信息
  242. const moveBoxBeforePosition = {
  243. x: el.target.getBoundingClientRect().x,
  244. y: el.target.getBoundingClientRect().y,
  245. };
  246. this.nodePositionDiff = {
  247. leftDiff: mousedownPositionInfo.x - moveBoxBeforePosition.x,
  248. topDiff: mousedownPositionInfo.y - moveBoxBeforePosition.y,
  249. };
  250. console.log(this.nodePositionDiff, "位置判定");
  251. },
  252. },
  253. };
  254. </script>
  255. <style scoped>
  256. .flowBox {
  257. display: flex;
  258. height: 100%;
  259. }
  260. .leftMenu {
  261. width: 300px;
  262. /* height: 100%; */
  263. border: 1px solid #1a1919;
  264. }
  265. h4 {
  266. margin-top: 10px;
  267. margin-left: 110px;
  268. }
  269. .leftNode {
  270. width: 200px;
  271. height: 40px;
  272. line-height: 40px;
  273. text-align: center;
  274. margin: 30px;
  275. border: dashed 1px #362c2c;
  276. cursor: move;
  277. }
  278. .plumbBox {
  279. flex: 1;
  280. /* height: 100%; */
  281. position: relative;
  282. }
  283. .pointNode {
  284. border-radius: 50%;
  285. width: 10px;
  286. height: 10px;
  287. background: royalblue;
  288. position: absolute;
  289. bottom: -5px;
  290. left: 95px;
  291. }
  292. </style>

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

效果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/816653
推荐阅读
相关标签
  

闽ICP备14008679号