行业知识
vue2中使用jsplumb完成流程图
2026-10-27 09:13:34
来源:互联网转载
前言
之前的几个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.组件部分
左侧菜单
downNode(el, item)" > {{ item.name }}操作提示
左侧拖拽至右侧画布
右键节点是删除节点,左键线条是删除线条
{{ item.name }} 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.right || y - 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, "位置判定"); }, },};.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;}
各个函数的注释都写好了,操作方法也在其中,最主要的是根据插件暴露的api去领悟其中的每一个方法。
效果
上一篇:轻松Win10系统升级攻略