效果
说在前面
流程图在技术领域是一种常见的可视化工具,用于展示系统、应用或业务流程的各个步骤以及它们之间的关系。它们可以帮助开发人员和项目团队更好地理解和规划复杂的流程,从而提高工作效率和准确性。但是,传统的静态流程图有时无法满足用户的需求,因此实现可拖拽的流程图组件成为了一个重要的需求。
实现可拖拽的流程图组件的目的和意义是为了提供一种交互性更强、用户体验更好的流程图展示方式。通过该组件,用户可以轻松地拖拽节点进行重新排序,自由调整流程图的结构和布局,从而更好地满足项目需求并提高工作效率。
组件设计
首先需要设计好配置参数,然后就是讲配置参数融入到样式的设计处理上,最后是实现组件拖动并实时展示效果。
参数
整体参数
参数 | 描述 |
---|---|
title | 标题(String) |
dragAble | 是否可拖拽(Boolean) |
width | 图标最小宽度(number) |
radius | 是否圆角(Boolean) |
data | 流程项(Array) |
data流程项参数
结构如下
[{icon:require('@/assets/logo.png'),//图标text:'准备'//文字},{icon:require('@/assets/1.png'),text:'开始'},
]
功能
拖拽事件监听
通过监听鼠标的按下、移动和抬起等事件,实现节点的拖拽功能。
if (this.chartData.dragAble) {document.getElementById("flow-chart").addEventListener("mouseup", this.handleMouseup);document.getElementById("flow-chart").addEventListener("mouseover", this.handleMouseover);document.getElementById("flow-chart").addEventListener("touchend", this.handleMouseup);document.getElementById("flow-chart").addEventListener("touchmove", this.handleMouseover);
}
阻止默认事件
为了确保拖拽功能正常运作,需要在拖拽过程中阻止浏览器默认的拖拽行为。
//阻止默认事件
preventEvent() {document.getElementById("flow-chart").ondragstart = function () {return false;};document.getElementById("flow-chart").onselectstart = function () {return false;};
},
初始化样式和数据
在组件加载时,需要初始化节点的样式和位置,以及计算每个节点的宽度和每行显示的数量。
//初始化样式变量
initStyle() {let chartContent = this.$refs.chartContent;let width = chartContent.offsetWidth - 40;let itemWidth = Math.max(20, Math.floor(width / 7));if (this.chartData.width) {itemWidth = this.chartData.width;}this.itemWidth = itemWidth;this.itemNum = Math.floor(width / (itemWidth + itemWidth / 5));
},
//初始化数据
initData() {let data = this.vChartDataList;let res = [],flag = true,temp = [];for (let i = 1; i <= data.length; i++) {data[i - 1].id = "item" + "-" + res.length + "-" + (i - 1);if (flag) temp.push(data[i - 1]);else temp.unshift(data[i - 1]);if (i % this.itemNum == 0 || i == data.length) {res.push([...temp]);temp = [];flag = !flag;}}this.chartDataList = res;
},
//重组class
getClass(res, str) {if (this.chartData[str]) res += " " + str;return res;
},
//重组行样式
getColumnStyle(index) {let res = {};if (index < this.chartDataList.lenth - 1 || index % 2 == 0)return this.styleConcat(res);res["margin-left"] = "auto";res["margin-right"] = -this.itemWidth / 5 + "px;";return this.styleConcat(res);
},
//重组每个item的样式
getItemStyle(item = "") {let res = {};if (item != "") {if (item.opacity) {res.opacity = item.opacity;}return this.styleConcat(res);}res.width = this.itemWidth + "px;";res["margin-right"] = this.itemWidth / 5 + "px;";return this.styleConcat(res);
},
//重组每个item的icon的样式
getIconStyle(str) {let res = {};res.width = this.itemWidth - 5 + "px;";res.height = this.itemWidth - 5 + "px";if (str == "text") {res["line-height"] = this.itemWidth - 5 + "px";res["font-size"] = "large";res["border"] = "1px solid blue";res["background-color"] = "skyblue";}return this.styleConcat(res);
},
//获取连接线样式
getLineStyle(index, index1, flag) {if (index1 == this.chartDataList.length - 1 &&index == this.chartDataList[index1].length - 1)return "";let res = {};res["border-top"] = "1px solid black";res.width = this.itemWidth / 3 + "px";if (flag == "right")res["margin-right"] = -this.itemWidth / 3 + "px;";else {res["margin-left"] = -this.itemWidth / 3 + "px;";res["border-left"] = "1px solid black";}res["margin-top"] = this.itemWidth / 2 + "px;";if (index == this.chartDataList[0].length - 1 &&index1 < this.chartDataList.length - 1) {if (index1 % 2 == 0) {res["border-right"] = "1px solid black";}}if (index1 % 2 == 1) {if (index == this.chartDataList[index1].length - 1) return "";}return this.styleConcat(res);
},
//json变量转换为style字符串
styleConcat(obj) {let res = "";for (let k in obj) {res += k + ":" + obj[k] + ";";}return res;
},
处理鼠标抬起事件
当鼠标抬起时,将拖拽的节点插入到新的位置,并更新节点的样式和位置。
//鼠标抬起时
handleMouseup(event) {const chartContent = document.getElementById("chartContent");if (this.vChartDataList[this.oldInd])this.vChartDataList[this.oldInd].opacity = 1;chartContent.style.border = "none";this.operateDom = null;this.operateDomNum = null;this.oldInd = null;
},
处理鼠标移动事件
在拖拽过程中,根据鼠标的位置计算节点的新样式和位置,实现拖拽时的效果。
handleMouseover(event) {if (this.vChartDataList.length < this.chartData.data.length) {this.vChartDataList.unshift({ ...this.chartData.data[0] });}if (this.operateDom != null) {const w = this.operateDom.offsetWidth,h = this.operateDom.offsetHeight;let x = event.pageX,y = event.pageY;this.operateDom.style.position = "fixed";this.operateDom.style.opacity = "0.5";this.operateDom.style.left = x - w / 2 - window.scrollX + "px";this.operateDom.style.top = y - h / 2 - window.scrollY + "px";let { tx, ty } = this.getItemCoords(x, y);let oldInd = this.oldInd;if (oldInd >= 0) {this.vChartDataList.splice(oldInd, 1);this.initData();}let nty =parseInt(ty) % 2 == 0? parseInt(tx): this.itemNum - parseInt(tx);nty = Math.min(nty, this.itemNum);nty = Math.max(nty, 0);oldInd = parseInt(ty) * this.itemNum + nty;oldInd = Math.min(this.chartData.data.length - 1, oldInd);oldInd = Math.max(0, oldInd);this.oldInd = oldInd;if (oldInd < 0) return;this.vChartDataList.splice(oldInd, 0, { ...this.selectedItem });this.initData();}
},
//获取当前移动到的坐标
getItemCoords(x, y) {let d = document.getElementById("chartContent");let left = d.offsetLeft;let top = d.offsetTop;(x = x - left), (y = y - top);let itemNum = this.itemNum;let w = d.offsetWidth;let h = d.offsetHeight;let moveDiv = document.getElementById("moveDiv");let th = moveDiv.offsetHeight;w = Math.ceil(w / itemNum);(x = Math.floor(x / w)), (y = Math.floor(y / th));return { tx: x, ty: y };
},
源码
Gitee地址:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse
预览地址
组件文档:http://jyeontu.xyz/jvuewheel/#/flowChartView
公众号
关注公众号『前端也能这么有趣
』,获取更多有趣内容。
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。