基於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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM