最近公司要求做一個拓撲流程圖,在網上搜尋了一些可行性方案之后,發現好一點的可視化拓撲圖都是要收費的,於是決定自己在阿里的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>
