导入关键包
npm install @antv/x6 --save
npm install @antv/x6-vue-shape
保存插件 (可选)
npm install --save @antv/x6-plugin-clipboard @antv/x6-plugin-history @antv/x6-plugin-keyboard @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-stencil @antv/x6-plugin-transform insert-css
写好的组件直接导入即可
javascript
<template>
<div>
<el-container>
<el-aside>
<div id="stencil">
<div>
<div class="dnd-circle dnd-start" @mousedown="startDrag('start',$event)"></div>
<span>开始</span>
</div>
<div>
<div class="dnd-rect" @mousedown="startDrag('rect',$event)"></div>
<span>节点1</span>
</div>
<div>
<div class="dnd-polygon" @mousedown="startDrag('polygon',$event)"></div>
<span>节点2</span>
</div>
<div>
<div class="dnd-circle" @mousedown="startDrag('end',$event)"></div>
<span>结束</span>
</div>
</div>
</el-aside>
<el-main>
<div ref="graphContainer">
</div>
</el-main>
</el-container>
<!-- todo drawer 抽屉实现节点内容编辑 -->
<el-drawer
title="节点属性编辑"
:visible.sync="drawer"
:direction="direction"
:before-close="handleClose">
<el-form :data="editNode" :inline="true">
<el-form-item label="节点名称" prop="label">
<el-input v-model="editNode.label"></el-input>
</el-form-item>
<el-form-item label="节点形状" prop="shape">
<el-select v-model="editNode.shape">
<el-option v-for="(item,index) in shapeList" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-button type="primary" @click="saveNode">保存</el-button>
</el-form>
</el-drawer>
</div>
</template>
<script>
import {Graph} from '@antv/x6'
import '@antv/x6-vue-shape'
// 插件 键盘监听事件
import {Keyboard} from '@antv/x6-plugin-keyboard'
// 拖拽事件
import {Dnd} from '@antv/x6-plugin-dnd'
import {Stencil} from '@antv/x6-plugin-stencil'
import {Transform} from '@antv/x6-plugin-transform'
import {Selection} from '@antv/x6-plugin-selection'
import {Snapline} from '@antv/x6-plugin-snapline'
import {Clipboard} from '@antv/x6-plugin-clipboard'
import {History} from '@antv/x6-plugin-history'
import {register} from '@antv/x6-vue-shape'
import insertCss from 'insert-css'
export default {
name: 'MindMap',
data () {
return {
graphOut: {},
drawer: false,
direction: 'rtl',
currentNode: {},
editNode: {},
dnd: {},
// 节点形状
shapeList: [{
label: '矩形',
value: 'rect'
}, {
label: '圆形',
value: 'circle'
}, {
label: '椭圆',
value: 'ellipse'
}, {
label: '多边形',
value: 'polygon'
}, {
label: '折线',
value: 'polyline'
}, {
label: '路径',
value: 'path'
}, {
label: '图片',
value: 'image'
},],
// 连接桩
ports: {
groups: {
top: {
position: 'top',
attrs: {
circle: {
magnet: true,
stroke: 'black',
r: 4,
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
magnet: true,
stroke: 'black',
r: 4,
},
},
},
left: {
position: 'left',
attrs: {
circle: {
magnet: true,
stroke: 'black',
r: 4,
},
},
},
right: {
position: 'right',
attrs: {
circle: {
magnet: true,
stroke: 'black',
r: 4,
},
},
},
},
items: [
{
id: 'port_1',
group: 'bottom',
}, {
id: 'port_2',
group: 'top',
}, {
id: 'port_3',
group: 'left',
}, {
id: 'port_4',
group: 'right',
}
]
}
}
},
mounted () {
this.graphOut = this.initGraph()
},
methods: {
initGraph () {
const graph = new Graph({
container: this.$refs.graphContainer,
// autoResize: true, // 大小自适应
height: 400,
width: '100%',
grid: true,
magnetThreshold: 'onleave',
panning: {
enabled: true,
modifiers: 'shift',
magnetThreshold: 1,
// 鼠标画布移动
eventTypes: ['leftMouseDown']
},
// 开启自动吸附
connecting: {
// 距离节点或者连接桩 50 px 触发自动吸附
snap: true,
// 是否允许连接到画布空白位置的点
allowBlank: false,
// 是否允许创建循环连线
allowLoop: false,
// 拖动边时,是否高亮显示所有可用连接桩或节点
highlight: true,
},
modes: {
default: ['drag-node']
},
background: {
color: '#F2F7FA',
},
mousewheel: {
// 是否开启滚轮缩放交互
enabled: true,
// 滚动缩放因子 默认 1.2
factor: 1.2,
// 是否将鼠标位置作为中心缩放、默认为true
zoomAtMousePosition: true,
// 按下什么键 才会缩放
modifiers: ['ctrl', 'meta'],
// 判断什么情况下 滚轮事件被处理
// guard: false,
},
connector: {
name: 'rounded',
args: {
radius: 8
}
}
})
// 支持拖拽
this.dnd = new Dnd({
target: graph,
scaled: false,
})
Graph.registerNode(
'custom-node-width-port',
{
inherit: 'rect',
width: 100,
height: 40,
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
// 上下左右 四条边都有连接桩
ports: this.ports
},
true,
)
Graph.registerNode(
'custom-circle-start',
{
inherit: 'circle',
ports: this.ports
},
true,
)
Graph.registerNode(
'custom-polygon',
{
inherit: 'polygon',
points: '0,10 10,0 20,10 10,20',
ports: this.ports
},
true,
)
Graph.registerNode(
'custom-rect',
{
inherit: 'rect',
ports: this.ports
},
true,
)
graph.addNode({
x: 100,
y: 40,
width: 180,
height: 30,
label: '中心主题',
shape: 'custom-node-width-port', // 节点形状
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#333',
},
type: 'root'
},
tools: [
{
name: 'boundary',
args: {
attrs: {
fill: '#16B8AA',
stroke: '#2F80EB',
strokeWidth: 1,
fillOpacity: 0.1,
},
},
}
]
})
// 添加 plugin 插件
graph.use(new Keyboard()) // 键盘事件
.use(new Selection({
enabled: true,
multiple: true,
rubberband: true,
movable: true,
showEdgeSelectionBox: true,
showNodeSelectionBox: true,
pointerEvents: 'none'
})) // 绑定框选
.use(new Snapline({
enabled: true,
sharp: true,
})) // 对齐线
.use(new Clipboard())
.use(new History({enabled: true})) // 绑定撤销
// 鼠标事件
this.mouseEvent(graph)
// 键盘时间
this.keyboardEvent(graph)
// 添加子节点的逻辑...
return graph
},
addChildNode (nodeId, type) {
console.log(nodeId, type)
},
handleClose (done) {
this.$confirm('确认关闭?')
.then(_ => {
done()
})
.catch(_ => {
})
},
saveNode () {
this.$confirm('确认保存?')
.then(_ => {
console.log(this.editNode)
this.currentNode['label'] = this.editNode['label']
// this.currentNode['shape'] = this.editNode['shape']
})
.catch(_ => {
})
// 关闭当前 抽屉 el-drawer
this.drawer = false
},
startDrag (type, e) {
this.startDragToGraph(this.graphOut, type, e)
},
startDragToGraph (graph, type, e) {
const startNode = this.graphOut.createNode({
shape: 'custom-circle-start',
width: 38,
height: 38,
attrs: {
body: {
strokeWidth: 1,
stroke: '#000000',
fill: '#ffffff',
rx: 10,
ry: 10,
},
},
})
const polygonNode = this.graphOut.createNode({
shape: 'custom-polygon',
width: 80,
height: 60,
attrs: {
body: {
strokeWidth: 1,
stroke: '#000000',
fill: '#ffffff',
rx: 10,
ry: 10,
},
label: {
fontSize: 13,
fontWeight: 'bold',
},
},
})
const rectNode = this.graphOut.createNode({
shape: 'custom-rect',
width: 80,
height: 60,
attrs: {
body: {
strokeWidth: 1,
stroke: '#000000',
fill: '#ffffff',
rx: 10,
ry: 10,
},
label: {
fontSize: 13,
fontWeight: 'bold',
},
},
})
const endNode = this.graphOut.createNode({
shape: 'custom-circle-start',
width: 38,
height: 38,
key: 'end',
attrs: {
body: {
strokeWidth: 4,
stroke: '#000000',
fill: '#ffffff',
rx: 10,
ry: 10,
},
label: {
text: '结束',
fontSize: 13,
fontWeight: 'bold',
},
},
})
let dragNode
if (type === 'start') {
dragNode = startNode
} else if (type === 'end') {
dragNode = endNode
} else if (type === 'rect') {
dragNode = rectNode
} else if (type === 'polygon') {
dragNode = polygonNode
}
console.log('dnd', dragNode, e, type)
this.dnd.start(dragNode, e)
},
// 删除事件 节点
removeNode (node) {
this.graphOut.removeNode(node)
},
// 鼠标事件
mouseEvent (graph) {
// 鼠标事件
// 鼠标 Hover 时添加按钮
graph.on('node:mouseenter', ({node}) => {
node.addTools({
name: 'button',
args: {
x: 0,
y: 0,
offset: {x: 18, y: 18},
// onClick({ view }) { ... },
},
})
})
// 鼠标移开时删除按钮
graph.on('node:mouseleave', ({node}) => {
node.removeTools() // 删除所有的工具
})
graph.on('node:dblclick', ({node}) => {
// 添加连接桩
node.addPort({
group: 'top',
attrs: {
circle: {
magnet: true,
stroke: '#8f8f8f',
r: 5,
},
},
})
// 编辑node
this.currentNode = node
this.drawer = true
})
graph.on('edge:mouseenter', ({cell}) => {
cell.addTools([
{name: 'vertices'},
{
name: 'button-remove',
args: {distance: 20},
},
])
})
graph.on('node:click', ({node}) => {
this.currentNode = node
})
},
// 键盘事件
keyboardEvent (graph) {
// 键盘事件
graph.bindKey('tab', (e) => {
e.preventDefault()
const selectedNodes = graph.getCells().filter((item) => item.isNode())
console.log(selectedNodes)
if (selectedNodes.length) {
const node = selectedNodes[0]
const type = node.attrs['type']
this.addChildNode(node.id, type)
}
})
graph.bindKey('delete', (e) => {
this.removeNode(this.currentNode)
})
graph.bindKey('backspace', (e) => {
this.removeNode(this.currentNode)
})
},
},
watch: {
// currentNode: {
// handler (nwVal, old) {
// },
// immediate: true,
// deep: true
// }
}
}
</script>
<style>
/* 样式调整 */
#stencil {
width: 100px;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-right: 1px solid #dfe3e8;
text-align: center;
font-size: 12px;
}
.dnd-rect {
width: 50px;
height: 30px;
line-height: 40px;
text-align: center;
border: 2px solid #000000;
border-radius: 6px;
cursor: move;
font-size: 12px;
margin-top: 30px;
}
.dnd-polygon {
width: 35px;
height: 35px;
border: 2px solid #000000;
transform: rotate(45deg);
cursor: move;
font-size: 12px;
margin-top: 30px;
margin-bottom: 10px;
}
.dnd-circle {
width: 35px;
height: 35px;
line-height: 45px;
text-align: center;
border: 5px solid #000000;
border-radius: 100%;
cursor: move;
font-size: 12px;
margin-top: 30px;
}
.dnd-start {
border: 2px solid #000000;
}
.x6-widget-stencil {
background-color: #f8f9fb;
}
.x6-widget-stencil-title {
background: #eee;
font-size: 1rem;
}
.x6-widget-stencil-group-title {
font-size: 1rem !important;
background-color: #fff !important;
height: 40px !important;
}
.x6-widget-transform {
margin: -1px 0 0 -1px;
padding: 0px;
border: 1px solid #239edd;
}
.x6-widget-transform > div {
border: 1px solid #239edd;
}
.x6-widget-transform > div:hover {
background-color: #3dafe4;
}
.x6-widget-transform-active-handle {
background-color: #3dafe4;
}
.x6-widget-transform-resize {
border-radius: 0;
}
</style>