效果图:
Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- import CSS -->
<link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.1/lib/theme-chalk/index.css">
<style>
.dnd-rect {
width: 100px;
height: 40px;
border: 2px solid #31d0c6;
text-align: center;
line-height: 40px;
margin: 16px;
cursor: move;
}
.el-drawer__body {
padding: 20px;
}
.el-drawer__body {
flex: 1;
}
.demo-drawer__content {
display: flex;
flex-direction: column;
height: 100%;
}
.el-drawer__body>* {
box-sizing: border-box;
}
.demo-drawer__content form {
flex: 1;
}
.demo-drawer__footer {
display: flex;
}
.demo-drawer__footer button {
flex: 1;
}
</style>
</head>
<body>
<div id='app'>
<div data-type="rect" class="dnd-rect" onmousedown="drag(event)">
Rect
</div>
<div id="container"></div>
<el-drawer title="编辑节点" :before-close="handleCloseNode" :visible.sync="drawerNode" direction="rtl"
custom-class="demo-drawer" ref="drawerNode">
<div class="demo-drawer__content">
<el-form :model="FormNode" label-position="top">
<el-form-item label="名称" :label-width="formLabelWidth" required>
<el-input v-model="FormNode.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述" :label-width="formLabelWidth">
<el-input v-model="FormNode.description" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="权限类型" :label-width="formLabelWidth">
<el-select v-model="FormNode.authorityType" placeholder="请选择权限类型">
<el-option label="用户" value="1"></el-option>
<el-option label="角色" value="2"></el-option>
<el-option label="组织/部门" value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审核人">
<el-row :gutter="20">
<el-col :span="21">
<el-input v-model="FormNode.authorityTarget" disabled placeholder="请选择审核人">
</el-input>
</el-col>
<el-col :span="2">
<el-button icon="el-icon-edit" type="primary" @click="SelectAuthorityTarget">
</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
</el-drawer>
<!-- /------------分割线--------------/ -->
<el-drawer title="编辑边" :before-close="handleCloseEdge" :visible.sync="drawerEdge" direction="rtl"
custom-class="demo-drawer" ref="drawerEdge">
<div class="demo-drawer__content">
<el-form :model="FormEdge" label-position="top">
<el-form-item label="名称" :label-width="formLabelWidth" required>
<el-input v-model="FormEdge.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述" :label-width="formLabelWidth">
<el-input v-model="FormEdge.description" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="条件类型" :label-width="formLabelWidth">
<el-select v-model="FormEdge.conditionType" placeholder="请选择权限类型">
<el-option label="同意" value="1"></el-option>
<el-option label="驳回" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="附加条件" :label-width="formLabelWidth">
<el-input v-model="FormEdge.additionCondition" autocomplete="off"></el-input>
</el-form-item>
</el-form>
</div>
</el-drawer>
</div>
<!-- import Antv X6 -->
<script src="https://unpkg.com/@antv/x6@1.17.2/dist/x6.js"></script>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui@2.15.1/lib/index.js"></script>
<!-- import axios -->
<script src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"></script>
<script script>
var vm = new Vue({
el: '#app',
data() {
return {
drawerNode: false, // 展开编辑节点菜单
drawerEdge: false, // 展开编辑边菜单
formLabelWidth: '80px',
FormNode: {
id: '',
name: '',
description: '',
stepType: "1",
authorityType: "1",
authorityTarget: "",
authorityTargetIds: [],
},
FormEdge: {
id: '',
name: '',
description: "",
startStepId: "",
endStepId: "",
conditionType: "1",
additionCondition: "",
},
};
},
methods: {
SelectAuthorityTarget: function () {
switch (this.FormNode.authorityType) {
case "1":
console.log("111");
break;
case "2":
console.log("222");
break;
case "3":
console.log("333");
break;
default:
break;
}
},
handleCloseNode(done) {
done();
},
handleCloseEdge(done) {
done();
},
}
})
// 定义节点
X6.Graph.registerNode(
'algo-node', {
inherit: 'rect',
width: 120,
height: 50,
ports: {
groups: {
in: {
position: 'top',
attrs: {
circle: {
r: 5,
magnet: true,
stroke: '#ccc',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
out: {
position: 'bottom',
attrs: {
circle: {
r: 5,
magnet: true,
stroke: '#ccc',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
},
},
},
true,
)
// 定义边
X6.Graph.registerConnector(
'algo-edge',
(source, target) => {
const offset = 4
const control = 80
const v1 = {
x: source.x,
y: source.y + offset + control
}
const v2 = {
x: target.x,
y: target.y - offset - control
}
return `M ${source.x} ${source.y}
L ${source.x} ${source.y + offset}
C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${target.x} ${target.y - offset}
L ${target.x} ${target.y}
`
},
true,
)
// 初始化画布
const graph = new X6.Graph({
container: document.getElementById('container'),
width: 1800,
height: 800,
history: true,
selecting: true,
background: {
color: '#fffbe6', // 设置画布背景颜色
},
grid: {
size: 10, // 网格大小 10px
visible: true, // 渲染网格背景
},
panning: true,
mousewheel: true,
// resizing: true, // 选中调整大小
connecting: {
snap: true,
allowBlank: false,
allowLoop: false,
highlight: true,
sourceAnchor: 'bottom',
targetAnchor: 'center',
connectionPoint: 'anchor',
connector: 'algo-edge',
// 连接线
createEdge() {
return graph.createEdge({
attrs: {
line: {
stroke: '#808080',
strokeWidth: 1,
targetMarker: {
name: 'block',
args: {
size: '6',
},
},
},
},
})
},
// 验证链接桩磁力
// 如果不是in 也就是out分组时 可以进行连接 否则当作拖动节点
// 当前out连接桩已连接的数量 当大于定义数时 验证返回错误
// validateMagnet({
// cell,
// magnet
// }) {
// if (magnet.getAttribute('port-group') !== 'in') {
// let count = 0
// const connectionCount = magnet.getAttribute('connection-count')
// const max = connectionCount ? parseInt(connectionCount, 10) : Number.MAX_SAFE_INTEGER
// const outgoingEdges = graph.getOutgoingEdges(cell)
// if (outgoingEdges) {
// outgoingEdges.forEach((edge) => {
// const edgeView = graph.findViewByCell(edge)
// if (edgeView.sourceMagnet === magnet) {
// count += 1
// }
// })
// }
// return count < max
// }
// return false
// },
// 验证连接
validateConnection({
sourceView,
targetView,
sourceMagnet,
targetMagnet
}) {
// 只能从输出链接桩创建连接
if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
return false
}
// 只能连接到输入链接桩
if (!targetMagnet || targetMagnet.getAttribute('port-group') !== 'in') {
return false
}
// 判断目标链接桩是否可连接
const portId = targetMagnet.getAttribute('port')
const node = targetView.cell
const port = node.getPort(portId)
if (port && port.connected) {
return false
}
return true
}
}
});
// 拖拽时使用
const dnd = new X6.Addon.Dnd({
target: graph,
scaled: false,
animation: true,
})
// 移入显示连接桩
const changePortsVisible = (visible) => {
const ports = container.querySelectorAll(
'.x6-port-body',
)
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = visible ? 'visible' : 'hidden'
}
}
graph.on('node:mouseenter', () => {
changePortsVisible(true)
})
graph.on('node:mouseleave', () => {
changePortsVisible(false)
})
// 拖入节点
function drag(e) {
console.log(e);
var node = graph.createNode({
shape: 'algo-node',
x: 120,
y: 50,
label: 'Hello',
attrs: {
label: {
text: 'Node',
fill: '#6a6c8a',
},
body: {
fill: '#fff',
stroke: '#31d0c6',
strokeWidth: 2,
},
},
data: {
tag: "edit-node",
},
ports: [{
group: 'in',
},
{
group: 'out',
attrs: {
circle: {
magnet: true,
// connectionCount: 1, // 自定义属性,控制连接桩可连接多少条边
}
}
},
],
})
dnd.start(node, e)
}
// 节点和边的移入删除按钮
graph.on('node:mouseenter', ({
node
}) => {
if (node.data.tag === "edit-node") {
node.addTools({
name: 'button-remove',
args: {
x: 0,
y: 0,
offset: {
x: 10,
y: 15
},
},
})
}
})
graph.on('node:mouseleave', ({
node
}) => {
if (node.data.tag === "edit-node") {
node.removeTools()
}
})
graph.on('edge:mouseenter', ({
edge
}) => {
edge.addTools({
name: 'button-remove',
args: {
x: 0,
y: 0,
offset: {
x: 0,
y: 0
},
},
})
})
graph.on('edge:mouseleave', ({
edge
}) => {
edge.removeTools()
})
// 监听改变选中节点时 改变对应的样式
graph.on('selection:changed', (args = {
added,
removed,
selected,
}) => {
args.selected.forEach((cell) => {
if (cell.isNode()) {
cell.attr('body', {
fill: '#ffd591',
})
} else {
cell.attr('line/stroke', '#ffa940')
}
})
args.removed.forEach((cell) => {
if (cell.isNode()) {
cell.attr('body', {
fill: '#fff',
})
} else {
cell.attr('line/stroke', '#808080')
}
})
})
//监听节点和边的单击
graph.on('cell:click', ({
e,
x,
y,
cell,
view
}) => {
if (cell.isNode()) {
if (cell.data.tag === "edit-node") {
vm.drawerEdge = false;
vm.drawerNode = !vm.drawerNode;
if (vm.drawerNode) {
console.log(cell);
if (cell.data == null) {
cell.data = {
id: '',
name: '',
description: '',
stepType: "1",
authorityType: "1",
authorityTarget: "",
authorityTargetIds: [],
}
}
cell.data.id = cell.id;
vm.FormNode = cell.data;
}
}
} else {
vm.drawerNode = false;
vm.drawerEdge = !vm.drawerEdge;
if (vm.drawerEdge) {
console.log(cell);
if (cell.data == null) {
cell.data = {
id: '',
name: '',
description: "",
startStepId: "",
endStepId: "",
conditionType: "1",
additionCondition: "",
}
}
cell.data.id = cell.id;
vm.FormEdge = cell.data;
}
}
})
// 初始化 添加节点和边
const begin = graph.addNode({
shape: 'algo-node',
x: -50,
y: -260,
label: '提交审核',
attrs: {
body: {
fill: '#fff',
stroke: '#87C7F4',
strokeWidth: 2,
},
},
ports: [{
group: 'out',
attrs: {
circle: {
magnet: true,
// connectionCount: 1, // 自定义属性,控制连接桩可连接多少条边
}
}
}, ],
data: {
tag: "",
},
})
const end = graph.addNode({
shape: 'algo-node',
x: -50,
y: 260,
label: '审核结束',
attrs: {
body: {
fill: '#fff',
stroke: '#87C7F4',
strokeWidth: 2,
},
},
ports: [{
group: 'in',
}, ],
data: {
tag: "",
},
})
graph.addEdge({
source: {
cell: begin,
port: begin.ports.items[0].id
},
target: {
cell: end,
port: end.ports.items[0].id
},
attrs: {
line: {
stroke: '#808080',
strokeWidth: 1,
targetMarker: {
name: 'block',
args: {
size: '6',
},
},
},
},
})
graph.centerContent()
</script>
<!--
g.getNodes() 所有节点 节点中data 中可以存放自定义信息
g.getEdges() 所有边 source 起始点id target 结束点id
g.toJson() 转json
-->
</body>
</html>