流程图在技术领域是一种常见的可视化工具,用于展示系统、应用或业务流程的各个步骤以及它们之间的关系。它们可以帮助开发人员和项目团队更好地理解和规划复杂的流程,从而提高工作效率和准确性。但是,传统的静态流程图有时无法满足用户的需求,因此实现可拖拽的流程图组件成为了一个重要的需求。
实现可拖拽的流程图组件的目的和意义是为了提供一种交互性更强、用户体验更好的流程图展示方式。通过该组件,用户可以轻松地拖拽节点进行重新排序,自由调整流程图的结构和布局,从而更好地满足项目需求并提高工作效率。
首先需要设计好配置参数,然后就是讲配置参数融入到样式的设计处理上,最后是实现组件拖动并实时展示效果。
参数 | 描述 |
---|---|
title | 标题(String) |
dragAble | 是否可拖拽(Boolean) |
width | 图标最小宽度(number) |
radius | 是否圆角(Boolean) |
data | 流程项(Array) |
结构如下
[
{
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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。