基于vue2.x+antv x6实现可拖拽编辑拓流程拓扑图
最近公司要求做一个拓扑流程图,在网上搜寻了一些可行性方案之后,发现好一点的可视化拓扑图都是要收费的,于是决定自己在阿里的antv x6基础上做出一款简单的产品,以便于后期进行修改和操作
项目主要版本号:
vue版本:2.x,
antv:@antv/x6": "^1.17.3", "@antv/x6-vue-shape": "^1.2.0",
css预编译器: "less": "^3.10.3",
ui框架:iview ,"view-design": "^4.1.1",
项目主要贴图gif:


具体的项目代码下载地址,欢迎点赞+关注:https://gitee.com/yanggengzhen/vue-antvx6-demo/tree/master
贴上部分代码:
<template>
<div class="container_warp">
<div id="containerChart"></div>
<RightDrawer class="right_drawer" :drawerType="type" :selectCell="selectCell" :graph="graph" :grid="grid" @deleteNode="deleteNode"></RightDrawer>
<div class="operating">
<div class="btn-group">
<div class="btn" title="圆形节点" @mousedown="startDrag('Circle',$event)">
<i class="iconfont icon-circle"></i>
</div>
<div class="btn" title="正方形节点" @mousedown="startDrag('Rect',$event)">
<i class="iconfont icon-square"></i>
</div>
<div class="btn" title="条件节点">
<i class="iconfont icon-square rotate-square" @mousedown="startDrag('polygon',$event)"></i>
</div>
<div class="btn-group_tips" v-if="showTips">
拖拽生成</br>资产拓扑图形
</div>
</div>
<div class="btn-group">
<Tooltip content="直线箭头" placement="bottom">
<div :class=" ['btn',currentArrow === 1?'currentArrow':'']" @click="changeEdgeType('normal')">
<i class="iconfont icon-ai28"></i>
</div>
</Tooltip>
<Tooltip content="曲线箭头" placement="bottom">
<div :class=" ['btn',currentArrow === 2?'currentArrow':'']" @click="changeEdgeType('smooth')">
<i class="iconfont icon-Down-Right"></i>
</div>
</Tooltip>
<Tooltip content="直角箭头" placement="bottom">
<div :class=" ['btn',currentArrow === 3?'currentArrow':'']" @click="changeEdgeType()">
<i class="iconfont icon-jiantou"></i>
</div>
</Tooltip>
</div>
<div class="btn-group">
<Tooltip content="删除" placement="bottom">
<div class="btn" @click="deleteNode()" style="margin-top: 5px;">
<i class="iconfont icon-shanchu"></i>
</div>
</Tooltip>
<Tooltip content="保存PNG" placement="bottom">
<div class="btn" @click="saveToPNG()" title="保存">
<i class="iconfont icon-baocun"></i>
</div>
</Tooltip>
</div>
</div>
</div>
</template>
<script>
import '@antv/x6-vue-shape'
import { Graph,Shape,Addon,FunctionExt,DataUri} from '@antv/x6';
import RightDrawer from './components/RightDrawer';
import insertCss from 'insert-css';
import {startDragToGraph} from './Graph/methods.js'
const data = {};
export default {
data() {
return {
graph:'',
value1: true,
type:'grid',
selectCell:'',
connectEdgeType:{ //连线方式
connector: 'normal',
router: {
name: ''
}
},
showTips:false,
currentArrow:1,
grid:{ // 网格设置
size: 20, // 网格大小 10px
visible: true, // 渲染网格背景
type: 'mesh',
args: {
color: '#D0D0D0',
thickness: 1, // 网格线宽度/网格点大小
factor: 10,
},
}
}
},
components:{
RightDrawer
},
methods: {
initX6(){
var _that = this
this.graph = new Graph({
container: document.getElementById('containerChart'),
width: 1700,
height: '100%',
grid: _that.grid,
resizing: { //调整节点宽高
enabled: true,
orthogonal:false,
},
selecting: true, //可选
snapline: true,
interacting: {
edgeLabelMovable: true
},
connecting: { // 节点连接
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: true,
createEdge () {
return new Shape.Edge({
attrs: {
line: {
stroke: '#1890ff',
strokeWidth: 1,
targetMarker: {
name: 'classic',
size: 8
},
strokeDasharray: 0, //虚线
style: {
animation: 'ant-line 30s infinite linear',
},
},
},
label: {
text:''
},
connector: _that.connectEdgeType.connector,
router: {
name: _that.connectEdgeType.router.name || ''
},
zIndex: 0
})
},
},
highlighting: {
magnetAvailable: {
name: 'stroke',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: '#6a6c8a'
}
}
}
},
});
insertCss(`
@keyframes ant-line {
to {
stroke-dashoffset: -1000
}
}
`)
this.graph.fromJSON(data)
this.graph.history.redo()
this.graph.history.undo()
// 鼠标移入移出节点
this.graph.on('node:mouseenter',FunctionExt.debounce(() => {
const container = document.getElementById('containerChart')
const ports = container.querySelectorAll(
'.x6-port-body'
)
this.showPorts(ports, true)
}),
500
)
this.graph.on('node:mouseleave', () => {
const container = document.getElementById('containerChart')
const ports = container.querySelectorAll(
'.x6-port-body'
)
this.showPorts(ports, false)
})
this.graph.on('blank:click', () => {
this.type = 'grid'
})
this.graph.on('cell:click', ({ cell }) => {
this.type = cell.isNode() ? 'node' : 'edge'
})
this.graph.on('selection:changed', (args) => {
args.added.forEach(cell => {
this.selectCell = cell
if(cell.isEdge()){
cell.isEdge() && cell.attr('line/strokeDasharray', 5) //虚线蚂蚁线
cell.addTools([
{
name: 'vertices',
args: {
padding: 4,
attrs: {
strokeWidth: 0.1,
stroke: '#2d8cf0',
fill: '#ffffff',
}
},
},
])
}
})
args.removed.forEach(cell => {
cell.isEdge() && cell.attr('line/strokeDasharray', 0) //正常线
cell.removeTools()
})
})
},
showPorts (ports, show) {
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden'
}
},
// 拖拽生成正方形或者圆形
startDrag(type,e){
startDragToGraph(this.graph,type,e)
},
// 删除节点
deleteNode(){
const cell = this.graph.getSelectedCells()
this.graph.removeCells(cell)
this.type = 'grid'
},
// 保存png
saveToPNG(){
this.$nextTick(()=>{
this.graph.toPNG((dataUri) => {
// 下载
DataUri.downloadDataUri(dataUri, '资产拓扑图.png')
},{
backgroundColor: 'white',
padding: {
top: 50,
right: 50,
bottom: 50,
left: 50
},
quality: 1,
copyStyles:false
})
})
},
// 改变边形状
changeEdgeType(e){
if(e === 'normal'){
this.connectEdgeType = {
connector: 'normal',
router: {name: ''}
}
this.currentArrow = 1
}else if (e === 'smooth'){
this.connectEdgeType = {
connector: 'smooth',
router: {name: ''}
}
this.currentArrow = 2
}else{
this.connectEdgeType = {
connector: 'normal',
router: {name: 'manhattan'}
}
this.currentArrow = 3
}
}
},
mounted(){
this.initX6()
setTimeout(()=>{
this.showTips = true
},1000)
setTimeout(()=>{
this.showTips = false
},5000)
}
};
</script>
<style lang="less">
@import '../assets/iconfont.css';
@import './index.less';
</style>
<template>
<div class="drawer_container">
<div v-if="drawerType === 'grid'">
<div class="drawer_title">画布背景设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="85">
<FormItem label="是否显示网格" :label-width="100">
<i-switch v-model="showGrid" @on-change="changeGrid" />
</FormItem>
<div v-show="showGrid">
<FormItem label="网格类型">
<RadioGroup v-model="grid.type" @on-change="changeGridType">
<Radio v-for="item in gridTypeList" :label="item.value" :key="item.value">
<span>{{item.label}}</span>
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="网格大小">
<Slider v-model="grid.size" :min="0" :max="30" @on-change="changeGrid"></Slider>
</FormItem>
<FormItem label="网格颜色">
<ColorPicker v-model="grid.args.color" @on-change="changeGrid"/>
</FormItem>
<FormItem label="网格线宽度">
<Slider v-model="grid.args.thickness" :min="0" :max="20" @on-change="changeGrid"></Slider>
</FormItem>
</div>
</Form>
</div>
</div>
<div v-if="drawerType === 'node'">
<div class="drawer_title">节点设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="80">
<FormItem label="节点文本">
<Input v-model="drawerNode.nodeText" @on-change="changeNodeText"></Input>
</FormItem>
<FormItem label="节点背景">
<ColorPicker v-model="drawerNode.fill" @on-change="changeFill"/>
</FormItem>
<FormItem label="字体大小">
<Slider v-model="drawerNode.fontSize" :min="10" :max="20" @on-change="changefontSize"></Slider>
</FormItem>
<FormItem label="字体颜色">
<ColorPicker v-model="drawerNode.fontFill" @on-change="changeFontFill"/>
</FormItem>
<FormItem label="边框宽度">
<Slider v-model="drawerNode.strokeWidth" :min="0" :max="10" @on-change="changeStrokeWidth"></Slider>
</FormItem>
<FormItem label="边框颜色">
<ColorPicker v-model="drawerNode.stroke" @on-change="changeStroke"/>
</FormItem>
<FormItem label="功能">
<Button type="primary" icon="md-trending-up" @click="toTopZIndex">置顶</Button>
<Button type="error" class="margin-left-10" icon="md-trash" @click="deleteNode">删除</Button>
</FormItem>
</Form>
</div>
</div>
<div v-if="drawerType === 'edge'">
<div class="drawer_title">线条设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="80">
<FormItem label="线条文本">
<Input v-model="drawerEdge.EdgeText" @on-change="changeEdgeText"></Input>
</FormItem>
<FormItem label="线条宽度">
<Slider v-model="drawerEdge.edgeWidth" :min="1" :max="10" @on-change="changeEdgeWidth"></Slider>
</FormItem>
<FormItem label="线条颜色">
<ColorPicker v-model="drawerEdge.edgeColor" @on-change="changeEdgeColor"/>
</FormItem>
<FormItem label="功能">
<Button type="primary" icon="md-trending-up" @click="toTopZIndex">置顶</Button>
<Button type="error" class="margin-left-10" icon="md-trash" @click="deleteNode">删除</Button>
</FormItem>
</Form>
</div>
</div>
</div>
</template>
<script>
export default {
name:'RightDrawer',
data() {
return {
gridTypeList:[
{
label:'四边网格',
value:'mesh'
},
{
label:'点状网格',
value:'dot'
}
],
showGrid:true,
drawerNode:{
fill:'',
nodeText:'',
fontSize:null,
fontFill:'',
strokeWidth:null,
stroke:''
},
drawerEdge:{
EdgeText:'',
edgeWidth:null,
edgeColor:''
},
};
},
props:{
drawerType: {
type: String
},
selectCell:{
type: String | Object
},
graph:{
type: String | Object
},
grid:{
type: Object
}
},
created() {
},
mounted() {
},
watch:{
selectCell:{
handler(val) {
if(val){
if(val.isNode()){ //节点
this.drawerNode.fill = val.store.data.attrs.body.fill
this.drawerNode.nodeText = val.store.data.attrs.label.text
this.drawerNode.fontFill = val.store.data.attrs.label.fill
this.drawerNode.fontSize = Number(val.store.data.attrs.label.fontSize)
this.drawerNode.strokeWidth = Number(val.store.data.attrs.body.strokeWidth)
this.drawerNode.stroke = val.store.data.attrs.body.stroke
}else{ //边
this.drawerEdge.EdgeText = val.store.data.labels?val.store.data.labels[0].text:''
this.drawerEdge.edgeWidth = Number(val.store.data.attrs.line.strokeWidth)
this.drawerEdge.edgeColor = val.store.data.attrs.line.stroke
}
}
},
immediate: true,
deep: false
},
},
methods: {
// 网格设置
changeGrid(){
this.showGrid?this.graph.showGrid():this.graph.hideGrid()
},
changeGridType(e){
this.grid.type = e
this.changeGrid()
},
changeGrid(){
this.graph.drawGrid({
...this.grid
})
},
// 节点设置
changeStrokeWidth(val){
this.selectCell.attr('body/strokeWidth', val)
},
changefontSize(val){
this.selectCell.attr('label/fontSize',val)
},
changeNodeText(){
this.selectCell.attr('label/text', this.drawerNode.nodeText)
},
changeStroke(val){
this.drawerNode.stroke = val
this.selectCell.attr('body/stroke', this.drawerNode.stroke)
},
changeFontFill(val){
this.drawerNode.fontFill = val
this.selectCell.attr('label/fill', this.drawerNode.fontFill)
},
changeFill(val){
this.drawerNode.fill = val
this.selectCell.attr('body/fill', val)
},
// 边设置
changeEdgeText(){
console.log(this.drawerEdge.EdgeText);
this.selectCell.setLabels(
[{attrs:{label:{text:this.drawerEdge.EdgeText}}}]
)
},
changeEdgeWidth(val){
this.drawerEdge.edgeWidth = val
this.selectCell.attr('line/strokeWidth', this.drawerEdge.edgeWidth)
},
changeEdgeColor(val){
this.drawerEdge.stroke = val
this.selectCell.attr('line/stroke', this.drawerEdge.stroke)
},
// 置顶
toTopZIndex(){
this.selectCell.toFront()
},
// 删除
deleteNode(){
this.$emit('deleteNode')
},
},
};
</script>
<style lang="less" scoped>
.drawer_container {
max-width: 300px;
min-width: 300px;
.drawer_title {
border-bottom: 1px solid #e8eaec;
box-sizing: border-box;
padding: 14px 16px;
color: #333;
font-size: 16px;
}
.drawer_wrap {
box-sizing: border-box;
padding: 20px 10px 20px 20px;
}
}
</style>

浙公网安备 33010602011771号