jsplumb + vue2
需求分析:两个表格分别展示需要拼接的数据,通过拖拽连线建立数据绑定关系。
实现步骤:
一、渲染表格数据
二、初始化节点数据
三、绑定事件
四、获取链接关系数据
1、根据已有数据初始化连线关系
2、新增数据不改变原连线添加节点
五、优化功能三:数据量大,表格滚动,滚动时连线跟随移动
#一、渲染数据
根据你的数据渲染出表格,注意绑定唯一字段,jsplumb使用
<template>
<div class="app-container">
<el-row id="container" :gutter="20">
<el-col :span="11">
<div id="leftTable" style="display: inline-block">
<div style="text-indent: 0px; font-size: 16px;text-align: center;margin-bottom:5px">数据对象</div>
<table style="height: 535px; overflow: scroll; display: inline-block" @scroll="connect">
<thead>
<tr>
<th
v-for="col in sourceColumn"
:key="col.dataIndex"
style=" text-align: left"
:style="{minWidth:col.width}"
>
{{ col.title }}
</th>
</tr>
</thead>
<tbody>
<tr
style="overflow:hidden;"
v-for="code in sourceList"
:id="code[sourceKey]"
:key="code"
name="joint"
>
<td
v-for="col in sourceColumn"
:key="index + col.dataIndex"
style="text-align: left;"
:style="{
textIndent:
col.dataIndex == sourceKey
? code.indent * 20 + 'px'
: '0px',
minWidth:col.width,
}"
>
{{ code[col.dataIndex] }}
</td>
<span
v-if="code.data_type != 'Object' && code.data_type != 'Array' && !code.disabled && code.dict_type"
style="
display: inline-block;
width: 14px;
height: 14px;
border-radius: 50%;
background: #ccc;
margin-left: -15px;
transform: translateY(5px);
"
:style="{transform:'translateY('+code.top + ')'}"
>
</span>
</tr>
</tbody>
</table>
</div>
</el-col>
<el-col :span="12" :offset="1">
<div
id="rightTable"
style="
height: 555px;
overflow-y: auto;
display: inline-block;
position: relative;
padding-left: 50px;
"
@scroll="connect"
>
<div
v-for="target in targetList"
:key="target.title"
style="position: relative; margin-bottom: 10px"
>
<div style="text-indent: 15px; font-size: 16px;text-align: center;margin-bottom:5px">
{{ target.title }}
</div>
<table style="margin-left: 15px">
<thead style="width: 100%">
<tr>
<th
v-for="col in targetColumn"
:key="col.dataIndex"
style="
min-width: 180px;
text-align: left;
text-indent: 40px;
"
>
{{ col.title }}
</th>
</tr>
</thead>
<tbody style="width: 100%">
<tr
v-for="(data, index) in target.list"
:key="index"
:id="'right' + (data[targetKey] ? data[targetKey] : data)"
name="data"
style="position: relative"
>
<td
style="text-align: left; text-indent: 40px"
v-for="col in targetColumn"
:key="index + col.dataIndex"
>
{{ data[col.dataIndex] }}
</td>
<span
style="
display: inline-block;
width: 14px;
height: 14px;
border-radius: 50%;
background: #ccc;
margin-top: 5px;
margin-left: -0px;
position: absolute;
left: 0;
top: 0;
z-index: 999;
"
></span>
</tr>
</tbody>
</table>
</div>
<div
v-if="targetList.length == 0"
style="
color: #ffffff88;
text-align: center;
font-size: 14px;
margin-top: 80px;
"
>
请选择字典
</div>
</div>
</el-col>
</el-row>
</div>
</template>
#二、初始化数据节点
<script>
//局部引入
import jsPlumb from "jsplumb";
//jsplumb使用
let $jsPlumb = jsPlumb.jsPlumb;
let jsPlumb_instance = null; // 缓存实例化的jsplumb对象
export default {
data() {
return {
// 左侧对照数据
// sourceList: [
// { name: "表字段1" },
// { name: "表字段2" },
// { name: "表字段3" }
// ],
// 右侧标准字典数据
// targetList: [
// { name: "表字段1" },
// { name: "字段2" },
// { name: "字段3" },
// { name: "字段4" }
// ],
pointList: null, //向后端传递的map映射关系
diffList: [],
count: 0
};
},
props: [
"sourceList",
"targetList",
"targetColumn",
"targetKey",
"sourceColumn",
"sourceKey"
],
mounted() {
setTimeout(() => {
this.showPlumb();
}, 100);
},
watch: {
targetList(newval, oldval) {
let oldList = oldval.map((val) => val.title);
this.diffList = newval.filter((val) => !oldList.includes(val.title));
setTimeout(() => {
this.showDiff();
}, 100);
}
},
methods: {
showPlumb() {
let self = this;
// 清除端点、连接
jsPlumb_instance?.reset();
this.pointList = new Map();
this.$forceUpdate();
jsPlumb_instance = $jsPlumb.getInstance({
Container: "container", // 选择器id
EndpointStyle: { radius: 0.11, fill: "#00c1de" }, // 端点样式
PaintStyle: { stroke: "#00c1de", strokeWidth: 4 }, // 绘画样式,默认8px线宽 #456
HoverPaintStyle: { stroke: "#1E90FF" }, // 默认悬停样式 默认为null
ConnectionOverlays: [
// 此处可以设置所有箭头的样式,因为我们要改变连接线的样式,故单独配置
// Arrow-箭头 Label-标签 PlainArrow-平头箭头 Diamond-菱形 Diamond(钻石)-钻石箭头 Custom-自定义
// [
// "Arrow",
// {
// // 设置参数可以参考中文文档
// location: 1,
// length: 8,
// paintStyle: {
// // stroke: "#409EFF",
// // fill: "#409EFF"
// fill: '#7AB02C',
// radius: 10
// }
// }
// ]
],
Connector: ["Straight"], // 连接器的类型:直线Straight,流程图flowchart,状态机state machine,贝塞尔曲线Bezier等 默认贝塞尔曲线
DrapOptions: { cursor: "crosshair", zIndex: 2000 }
// connectorStyle:{
// paintStyle: {
// fill: '#7AB02C',
// radius: 10
// },
// }
});
//调用初始化节点
jsPlumb_instance.batch(() => {
for (let i = 0; i < this.sourceList.length; i++) {
//根据条件筛选哪些数据可以连接
if (
this.sourceList[i].data_type != "Object" &&
this.sourceList[i].data_type != "Array" && !this.sourceList[i].disabled && this.sourceList[i].dict_type
) {
this.initLeaf(this.sourceList[i][this.sourceKey], "joint");
}
}
for (let j = 0; j < this.targetList.length; j++) {
for (let i = 0; i < this.targetList[j].list.length; i++) {
this.initLeaf(
"right" +
(this.targetList[j].list[i][this.targetKey] ?
this.targetList[j].list[i][this.targetKey] :
this.targetList[j].list[i]),
"data"
);
}
}
});
const joint = document.getElementsByName("joint");
const data = document.getElementsByName("data");
jsPlumb_instance.setSourceEnabled(joint, true);
jsPlumb_instance.setTargetEnabled(data, true);
jsPlumb_instance.setDraggable(joint, true); // 是否支持拖拽
jsPlumb_instance.setDraggable(data, true); // 是否支持拖拽
jsPlumb_instance.bind("dblclick", (conn, originalEvent) => {
// this.$confirm("确认删除映射么?", "提示", {
// confirmButtonText: "确定",
// cancelButtonText: "取消",
// closeOnClickModal: false,
// type: "warning"
// })
// .then(() => {
jsPlumb_instance.deleteConnection(conn);
// })
// .catch(() => {});
});
///两个表进行关联时 连线
jsPlumb_instance.bind("connection", function (info) {
//连线发盛变化时,处理自己需要的数据
self.pointList.set(info.sourceId, info.targetId.split("right")[1]);
self.$emit("change", self.pointList);
});
// 删除连线
jsPlumb_instance.bind("connectionDetached", function (evt) {
if (self.pointList.has(evt.sourceId)) {
self.pointList.delete(evt.sourceId);
self.$emit("change", self.pointList);
}
});
},
// 初始化具体节点
initLeaf(key, type) {
const ins = jsPlumb_instance;
const elem = document.getElementById(key);
// console.log(key, elem, 99);
if (type == "joint") {
ins.makeSource(elem, {
anchor: [1, 0.5, 0, 0], // 左 上 右 下
allowLoopback: false,
maxConnections: 1,
endpoint: "Dot"
});
} else {
ins.makeTarget(elem, {
anchor: [0, 0.5, 0, 0],
allowLoopback: false,
maxConnections: 100
// endpoint: "Dot",
// paintStyle: {
// fill: "blue",
// strokeWidth: 1
// }
});
}
},
initLeft() {
for (let i = 0; i < this.sourceList.length; i++) {
if (
this.sourceList[i].data_type != "Object" &&
this.sourceList[i].data_type != "Array"
) {
this.initLeaf(this.sourceList[i][this.sourceKey], "joint");
}
}
}
}
};
</script>
我这里用的props的数据,可以用自己的数据,注意数据结构即可
绑定事件上面方法以实现
#四、初始化已有的连接数据及右侧数据变化时将节点渲染为可连接节点
// 初始化字典连线
initLines () {
this.sourceList.forEach(item => {
if(item.dict_data){
console.log("right" + item.dict_data_path + "__" + item.dict_data + "__" +item.dict_data_type);
jsPlumb_instance.connect({
source: item[this.sourceKey],
target:"right" + item.dict_data_path + "__" + item.dict_data + "__" +item.data_type
});
}
})
},
遍历需要连接的数据,调用实例的connect方法连接
watch: {
targetList(newval, oldval) {
let oldList = oldval.map((val) => val.title);
this.diffList = newval.filter((val) => !oldList.includes(val.title));
setTimeout(() => {
this.showDiff();
}, 100);
}
},
methods: {
showDiff() {
for (let j = 0; j < this.diffList.length; j++) {
for (let i = 0; i < this.diffList[j].list.length; i++) {
this.initLeaf(
"right" +
(this.diffList[j].list[i][this.targetKey] ?
this.diffList[j].list[i][this.targetKey] :
this.diffList[j].list[i]),
"data"
);
}
}
},
}
监听数据变化,将新增的数据渲染节点
#五、页面滚动,连线跟随变化
触发滚动事件时,将现有的连接关系保存,将原有的连线删除,重新简历连线,这里设置滚动三次触发一次方法
connect() {
this.count++;
if (this.count >= 3) {
let connections = jsPlumb_instance.getAllConnections();
let linePoints = [];
for (let i = connections.length - 1; i >= 0; i--) {
linePoints.push([connections[i].sourceId, connections[i].targetId]);
jsPlumb_instance.deleteConnection(connections[i]);
}
for (let i = 0; i < linePoints.length; i++) {
jsPlumb_instance.connect({
source: linePoints[i][0],
target: linePoints[i][1]
});
}
this.count = 0;
}
},