npm install --save @antv/x6
<template><div class="dashboard-container"><p>选择节点</p><button @click="save">保存</button><div class="antvBox"><div class="menu-list"><divv-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, // 网格大小 10pxvisible: 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-varsgraph.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 = nodenode.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