最近公司要求做一个拓扑流程图,在网上搜寻了一些可行性方案之后,发现好一点的可视化拓扑图都是要收费的,于是决定自己在阿里的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'), 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- 300px; min- 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>