npm install --save @antv/x6
<template>
<div class="dashboard-container">
<p>选择节点</p>
<button @click="save">保存</button>
<div class="antvBox">
<div class="menu-list">
<div
v-for="item in moduleList"
:key="item.id"
draggable="true"
@dragend="handleDragEnd($event, item)"
>
<img :src="item.image" alt="" />
<p>{{ item.name }}</p>
</div>
</div>
<div class="canvas-card">
<div id="container" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { Graph } from '@antv/x6'
import t1 from '../../assets/images/1.png'
import t2 from '../../assets/images/2.png'
import t3 from '../../assets/images/3.png'
import t4 from '../../assets/images/4.png'
import t5 from '../../assets/images/5.png'
const moduleList = ref([
{
id: 1,
name: '节点1',
image: t1
},
{
id: 8,
name: '节点2',
image: t2
},
{
id: 2,
name: '节点3',
image: t3
},
{
id: 3,
name: '节点4',
image: t4
},
{
id: 4,
name: '节点5',
image: t5
}
])
const curSelectNode = ref(null)
/**
* 拖拽左侧
* @param e
* @param item
*/
const handleDragEnd = (e, item) => {
addHandleNode(e.pageX - 300, e.pageY - 200, new Date().getTime(), item.image, item.name)
}
const graph = ref<Graph | null>(null)
const initGraph = () => {
const container = document.getElementById('container')
if (container === null) {
throw new Error('Container element not found')
}
graph.value = new Graph({
container: container, // 画布容器
width: container.offsetWidth, // 画布宽
height: container.offsetHeight, // 画布高
background: false, // 背景(透明)
snapline: true, // 对齐线
// 配置连线规则
connecting: {
snap: true, // 自动吸附
allowBlank: false, // 是否允许连接到画布空白位置的点
allowMulti: true, // 是否允许在相同的起始节点和终止之间创建多条边
allowLoop: true, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
highlight: true, // 拖动边时,是否高亮显示所有可用的节点
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#5F95FF',
stroke: '#5F95FF'
}
}
}
},
router: {
// 对路径添加额外的点
name: 'orth'
},
connector: {
// 边渲染到画布后的样式
name: 'rounded',
args: {
radius: 8
}
}
},
panning: {
enabled: false
},
mousewheel: {
enabled: true, // 支持滚动放大缩小
zoomAtMousePosition: true,
modifiers: 'ctrl',
minScale: 0.5,
maxScale: 3
},
grid: {
type: 'dot',
size: 20, // 网格大小 10px
visible: true, // 渲染网格背景
args: {
color: '#a0a0a0', // 网格线/点颜色
thickness: 2 // 网格线宽度/网格点大小
}
}
})
nodeAddEvent()
}
/**
* 添加画布到节点
* x坐标、y坐标、id节点唯一标识、image图片、name节点名称
*/
//添加节点到画布
const addHandleNode = (x, y, id, image, name) => {
graph.value.addNode({
id: id,
shape: 'image', // 指定使用何种图形,默认值为 'rect'
x: x,
y: y,
width: 60,
height: 60,
imageUrl: image,
attrs: {
body: {
stroke: '#ffa940',
fill: '#ffd591'
},
label: {
textWrap: {
width: 90,
text: name
},
fill: 'black',
fontSize: 12,
refX: 0.5,
refY: '100%',
refY2: 4,
textAnchor: 'middle',
textVerticalAnchor: 'top'
}
},
ports: {
groups: {
group1: {
position: [30, 30]
}
},
items: [
{
group: 'group1',
id: 'port1',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#ffffff',
strokeWidth: 2,
fill: '#5F95FF'
}
}
}
]
},
zIndex: 10
})
}
/**
* 鼠标移入节点再显示链接桩
*/
const nodeAddEvent = () => {
const container = document.getElementById('container')
if (container === null) {
throw new Error('Container element not found')
}
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.value.on('node:mouseenter', () => {
changePortsVisible(true)
})
graph.value.on('node:mouseleave', () => {
changePortsVisible(false)
})
// 节点绑定点击事件 删除节点
// eslint-disable-next-line @typescript-eslint/no-unused-vars
graph.value.on('node:click', ({ e, x, y, node, view }) => {
console.log('点击!!!', node)
// 判断是否有选中过节点
if (curSelectNode.value) {
// 移除选中状态
curSelectNode.value.removeTools()
// 判断两次选中节点是否相同
if (curSelectNode.value !== node) {
node.addTools([
{
name: 'boundary',
args: {
attrs: {
fill: '#16B8AA',
stroke: '#2F80EB',
strokeWidth: 1,
fillOpacity: 0.1
}
}
},
{
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
}
}
}
])
curSelectNode.value = node
} else {
curSelectNode.value = null
}
} else {
curSelectNode.value = node
node.addTools([
{
name: 'boundary',
args: {
attrs: {
fill: '#16B8AA',
stroke: '#2F80EB',
strokeWidth: 1,
fillOpacity: 0.1
}
}
},
{
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
}
}
}
])
}
})
// 删除链接节点的线
// 连线绑定悬浮事件
graph.value.on('cell:mouseenter', ({ cell }) => {
if (cell.shape == 'edge') {
cell.addTools([
{
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
}
}
}
])
cell.setAttrs({
line: {
stroke: '#409EFF'
}
})
cell.zIndex = 99 // 保证当前悬停的线在最上层,不会被遮挡
}
})
graph.value.on('cell:mouseleave', ({ cell }) => {
if (cell.shape === 'edge') {
cell.removeTools()
cell.setAttrs({
line: {
stroke: 'black'
}
})
cell.zIndex = 1 // 保证未悬停的线在下层,不会遮挡悬停的线
}
})
}
//保存画布,并提交
const save = () => {
console.log(graph.value.toJSON(), 'graph')
console.log(graph.value.getNodes(), 'node')
}
onMounted(() => {
initGraph()
})
</script>
<style lang="scss" scoped>
/* @use ''; 引入css类 */
.dashboard-container {
.antvBox {
display: flex;
width: 100%;
height: 100%;
color: black;
padding-top: 20px;
.menu-list {
height: 100%;
width: 300px;
padding: 0 10px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-content: flex-start;
flex-wrap: wrap;
> div {
margin-bottom: 10px;
border-radius: 5px;
padding: 0 10px;
box-sizing: border-box;
cursor: pointer;
color: black;
width: 105px;
display: flex;
flex-wrap: wrap;
justify-content: center;
img {
height: 50px;
width: 50px;
}
P {
width: 90px;
text-align: center;
}
}
}
.canvas-card {
width: 1700px;
height: 750px;
box-sizing: border-box;
> div {
width: 1400px;
height: 750px;
border: 2px dashed #2149ce;
}
}
}
}
</style>
借鉴https://blog.csdn.net/wzy_PROTEIN/article/details/136305034
标签:const,name,value,antvX6,cell,源码,vue3,container,节点 From: https://www.cnblogs.com/mengqc1995/p/18464558