关于AntV-X6在Vue里面是使用【AntV 1.X,Vue2的版本】

写在前面 【注意这个是1.X版本Vue2的】

2.X+Vue3的版本见下面

【2.X+Vue3版本】关于AntV-X6,2.X版本在vue3里面的应用【AntV 2.X,Vue3的版本】。 - 简书 (jianshu.com)

因为工作需要,写了一个拖拽流程图生成组件。用的是AntV-X6。以前用的jsplumb,后面发现了这个蛮好用的,记录一下。

组件功能

  • 可以拖拽添加节点,修改编辑节点样式(默认四个链接桩)
  • 可以添加连线,修改连线的样式。给线条添加动态(蚂蚁线)。可以手动调整线条(添加编辑工具)
  • 可以添加图片节点。自定义设置图片。
  • 导出与反显
项目地址

antv-x6: AntV-X6 图编辑引擎在vue2里面的使用。 (gitee.com)

npm install
npm run serve
项目界面展示
1232.png

项目过程

1,引入

npm install @antv/x6@1.34.6 --save

2,建立一个初始化默认设置的antvSetting.js,方便调用。

// 画布基本设置(这些例子上面都有)
export const configSetting = (Shape) => {
  return {
    grid: true,
    autoResize: true,
    translating: { restrict: true },
    mousewheel: {
      enabled: true,
      zoomAtMousePosition: true,
      modifiers: 'ctrl',
      minScale: 0.5,
      maxScale: 3,
    },
    connecting: {
      router: {
        name: 'manhattan',
        args: {
          padding: 1,
        },
      },
      connector: {
        name: 'rounded',
        args: {
          radius: 8,
        },
      },
      anchor: 'center',
      connectionPoint: 'anchor',
      allowBlank: false,
      snap: {
        radius: 20,
      },
      createEdge() {
        return new Shape.Edge({
          attrs: {
            line: {
              stroke: '#A2B1C3',
              strokeWidth: 2,
              targetMarker: {
                name: 'block',
                width: 12,
                height: 8
              },
            },
          },
          zIndex: 0,
        })
      },
      validateConnection({ targetMagnet }) {
        return !!targetMagnet
      },
    },
    onToolItemCreated({ tool }) {
      const handle = tool
      const options = handle.options
      if (options && options.index % 2 === 1) {
        tool.setAttrs({ fill: 'red' })
      }
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#5F95FF',
            stroke: '#5F95FF',
          },
        },
      },
    },
    resizing: true,
    rotating: true,
    selecting: {
      enabled: true,
      rubberband: true,
      showNodeSelectionBox: true,
    },
    snapline: true,
    keyboard: true,
    clipboard: true
  }
}

/**
* 节点预设类型 
* 0椭圆形: defaultOval, 
* 1方形: defaultSquare, 
* 2圆角矩形: defaultYSquare,
* 3菱形: defaultRhombus, 
* 4平行四边形: defaultRhomboid, 
* 5圆形: defaultCircle, 
* 6图片: otherImage
* 到时候通过传入的type===通过匹配 data里面设置的type获取到相应的节点设置内容
* 编辑的时候也可以通过节点里面的data.type 获取到到底是什么节点进行响应设设置
*/
export const configNodeShape = (type) => {
  const nodeShapeList =  [{
      label: '椭圆形',
      /**
       * 
       *  加入data里面的标识type是为了方便编辑的时候找到相对应的类型进行不同的编辑处理
       *  另外获取初始对应的设置
      */
      data: {
        type: 'defaultOval'
      },
      shape: 'rect',
      width: 100,
      height: 50,
      attrs: {
        body: {
          rx: 20,
          ry: 26,
          fill: '#fff',
          stroke: '#333'
        },
        label: {
          text: '椭圆形',
          fontSize: 16,
          fill: '#333'
        }
      }
    },
    {
      label: '方形',
      data: {
        type: 'defaultSquare',
      },
      shape: 'rect',
      width: 100,
      height: 50,
      attrs: {
        label: {
          text: '方形',
          fontSize: 16,
          fill: '#333'
        },
        body: {
          fill: '#fff',
          stroke: '#333'
        }
      },
    },
    {
      label: '圆角矩形',
      data: {
        type: 'defaultYSquare'
      },
      shape: 'rect',
      width: 100,
      height: 50,
      attrs: {
        body: {
          rx: 6,
          ry: 6,
          fill: '#fff',
          stroke: '#333'
        },
        label: {
          text: '圆角矩形',
          fontSize: 16,
          fill: '#333'
        }
      },
    },
    {
      label: '菱形',
      data: {
        type: 'defaultRhombus'
      },
      shape: 'polygon',
      width: 120,
      height: 50,
      attrs: {
        body: {
          refPoints: '0,10 10,0 20,10 10,20',
          fill: '#fff',
          stroke: '#333'
        },
        label: {
          text: '菱形',
          fontSize: 16,
          fill: '#333'
        }
      },
    },
    {
      label: '平行四边形',
      data: {
        type: 'defaultRhomboid'
      },
      shape: 'polygon',
      width: 120,
      height: 50,
      attrs: {
        body: {
          refPoints: '10,0 40,0 30,20 0,20',
          fill: '#fff',
          stroke: '#333'
        },
        label: {
          text: '平行四边形',
          fontSize: 16,
          fill: '#333'
        }
      }
    },
    {
      label: '圆形',
      data: {
        type: 'defaultCircle'
      },
      shape: 'circle',
      width: 80,
      height: 80,
      attrs: {
        label: {
          text: '圆形',
          fontSize: 16,
          fill: '#333'
        },
        body: {
          fill: '#fff',
          stroke: '#333'
        }
      }
    },
    {
      label: "图片",
      data: {
        type: 'otherImage'
      },
      shape: 'rect',
      width: 80,
      height: 80,
      markup: [
        {
          tagName: 'rect',
          selector: 'body',
        },
        {
          tagName: 'image',
        },
        {
          tagName: 'text',
          selector: 'label',
        },
      ],
      attrs: {
        body: {
          stroke: '#5F95FF',
          fill: '#5F95FF',
        },
        image: {
          width: 80,
          height: 80,
          refX: 0,
          refY: 0,
          xlinkHref: 'https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg',
        },
        label: {
          fontSize: 14,
          fill: '#fff',
          text: '图片'
        },
      },
    }
  ]
  if(type) {
    const obj = nodeShapeList.find(item => {return item.data.type === type})
    return obj || nodeShapeList
  }
  return nodeShapeList
}

/**
* 节点连接桩设置
* 这里设置了上下左右四个
* 并且给style设置了 visibility: 'hidden',默认是隐藏的。
* 如果设置了隐藏记得在画布里面设置鼠标经过显示。
* graph.on('node:mouseenter', () => {
        const container = document.getElementById('wrapper')
        const ports = container.querySelectorAll('.x6-port-body')
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
          ports[i].style.visibility = val ? 'visible' : 'hidden'
        }
    })
* 如果需要常显去掉每个链接桩里面的
  style: {
        visibility: 'hidden',
     },
* 就可以了
*/
export const configNodePorts = () => {
  return {
    groups: {
      top: {
        position: 'top',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      bottom: {
        position: 'bottom',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
    },
    items: [
      {
        group: 'top',
      },
      {
        group: 'right',
      },
      {
        group: 'bottom',
      },
      {
        group: 'left',
      },
    ]
  }
}

// 连线 label 设置
export const configEdgeLabel = (labelText, fontColor, fill, stroke) => {
  if(!labelText) return { attrs: { labelText: { text: '' }, labelBody: { fill: '', stroke: '' } } }
  return {
      markup: [
        {
          tagName: 'rect',
          selector: 'labelBody',
        },
        {
          tagName: 'text',
          selector: 'labelText',
        },
      ],
      attrs: {
        labelText: {
          text: labelText || '',
          fill: fontColor || '#333',
          textAnchor: 'middle',
          textVerticalAnchor: 'middle',
        },
        labelBody: {
          ref: 'labelText',
          refX: -8,
          refY: -5,
          refWidth: '100%',
          refHeight: '100%',
          refWidth2: 16,
          refHeight2: 10,
          stroke: stroke || '#555',
          fill: fill || '#fff',
          strokeWidth: 2,
          rx: 5,
          ry: 5,
        },
      }
    }
}

// 键盘事件
export const graphBindKey = (graph) => {
    graph.bindKey(['meta+c', 'ctrl+c'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.copy(cells)
      }
      return false
    })
    graph.bindKey(['meta+x', 'ctrl+x'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.cut(cells)
      }
      return false
    })
    graph.bindKey(['meta+v', 'ctrl+v'], () => {
      if (!graph.isClipboardEmpty()) {
        const cells = graph.paste({ offset: 32 })
        graph.cleanSelection()
        graph.select(cells)
      }
      return false
    })
    //undo redo
    graph.bindKey(['meta+z', 'ctrl+z'], () => {
      if (graph.history.canUndo()) {
        graph.history.undo()
      }
      return false
    })
    graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      if (graph.history.canRedo()) {
        graph.history.redo()
      }
      return false
    })
    // select all
    graph.bindKey(['meta+a', 'ctrl+a'], () => {
      const nodes = graph.getNodes()
      if (nodes) {
        graph.select(nodes)
      }
    })
    //delete
    /*
    graph.bindKey('delete', () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.removeCells(cells)
      }
    })
    */
    // zoom
    graph.bindKey(['ctrl+1', 'meta+1'], () => {
      const zoom = graph.zoom()
      if (zoom < 1.5) {
        graph.zoom(0.1)
      }
    })
    graph.bindKey(['ctrl+2', 'meta+2'], () => {
      const zoom = graph.zoom()
      if (zoom > 0.5) {
        graph.zoom(-0.1)
      }
    })
    return graph
}

3,页面上的使用

<template>
  <div class="all">
    <div class="antv-content">
      <div class="antv-menu">
        <h3> 基础图形列表 </h3>
        <ul class="menu-list">
          <li draggable="true" @drag="menuDrag('defaultOval')"> <i class="icon-oval"></i> <strong>椭圆形</strong></li>
          <li draggable="true" @drag="menuDrag('defaultSquare')"><i class="icon-square"></i><strong>矩形</strong></li>
          <li draggable="true" @drag="menuDrag('defaultYSquare')"><i class="icon-ysquare"></i><strong>圆角矩形</strong></li>
          <li draggable="true" @drag="menuDrag('defaultRhombus')"><i class="icon-rhombus"></i><strong>菱形</strong></li>
          <li draggable="true" @drag="menuDrag('defaultRhomboid')"><i class="icon-rhomboid"></i><strong>平行四边形</strong></li>
          <li draggable="true" @drag="menuDrag('defaultCircle')"><i class="icon-circle"></i><strong>圆形</strong></li>
          <li draggable="true" @drag="menuDrag('otherImage')"><i class="el-icon-picture"></i><strong>图片</strong></li>
        </ul>
        <div class="wrapper-btn" v-if="isChange">
          <el-button type="success" @click="handlerSend">保存当前方案</el-button>
        </div>
      </div>
      <div class="antv-wrapper">
        <div class="wrapper-canvas" :style="{height: height}" id="wrapper" @drop="drop($event)" @dragover.prevent></div>
        <div class="wrapper-tips">
          <div class="wrapper-tips-item">
            <el-switch v-model="isPortsShow" @change="changePortsShow"></el-switch>
            <span>链接桩常显</span>
          </div>
        </div>
      </div>
      <div v-if="editDrawer" class="edit-main">
        <div class="edit-main-title">
          <h3>{{editTitle}} </h3>
          <i class="el-icon-close" @click="closeEditForm"></i>
        </div>
        <div v-if="editTitle === '编辑节点'" class="form-main">
          <el-form ref="nodeForm" :model="form" label-width="80px">
            <el-form-item label="节点文本">
              <el-input v-model="form.labelText" size="small" @input="changeNode('labelText', form.labelText)"></el-input>
            </el-form-item>
            <el-form-item label="字体大小">
              <el-input v-model="form.fontSize" size="small" @input="changeNode('fontSize', form.fontSize)"></el-input>
            </el-form-item>
            <el-form-item label="字体颜色">
              <el-color-picker v-model="form.fontFill" @change="changeNode('fontFill', form.fontFill)"></el-color-picker>
            </el-form-item>
            <el-form-item label="节点背景">
              <el-color-picker v-model="form.fill" @change="changeNode('fill', form.fill)"></el-color-picker>
            </el-form-item>
            <el-form-item label="边框颜色">
              <el-color-picker v-model="form.stroke" @change="changeNode('stroke', form.stroke)"></el-color-picker>
            </el-form-item>
             <div class="see-box">
                <h5>预览</h5>
                <div class="see-item" :style="{ 'background': form.fill, 'color': form.fontFill, 'border-color': form.stroke, 'font-size': form.fontSize + 'px' }">{{form.labelText}}</div>
              </div>
          </el-form>
        </div>
        <div v-if="editTitle === '编辑图片节点'" class="form-main">
          <el-form ref="imageForm" :model="form" label-width="80px">
            <el-form-item label="节点文本">
              <el-input v-model="form.labelText" size="small" @input="changeImageNode('labelText', form.labelText)"></el-input>
            </el-form-item>
            <el-form-item label="字体颜色">
              <el-color-picker v-model="form.labelFill" @change="changeImageNode('labelFill', form.labelFill)"></el-color-picker>
            </el-form-item>
            <el-form-item label="节点背景">
              <el-color-picker v-model="form.fill" @change="changeImageNode('fill', form.fill)"></el-color-picker>
            </el-form-item>
            <el-form-item label="图片地址">
              <el-input v-model="form.xlinkHref" size="small" placeholder="图片地址" @input="changeImageNode('xlinkHref', form.xlinkHref)"></el-input>
              <el-image :src="form.xlinkHref" style="width: 80px; height: 80px; background: #f2f2f2" fit="fill"></el-image>
            </el-form-item>
            <el-form-item label="图片尺寸">
              <span style="font-size: 14px; padding-right: 5px; color: #888;">宽</span><el-input-number v-model="form.width" :min="0" label="宽" size="mini" @change="changeImageNode('width', form.width)"></el-input-number>
              <span style="font-size: 14px; padding-right: 5px; color: #888;">高</span><el-input-number v-model="form.height" :min="0" label="高" size="mini" @change="changeImageNode('height', form.height)"></el-input-number>
            </el-form-item>
          </el-form>
        </div>
        <div v-if="editTitle === '编辑连线'" class="form-main">
          <el-form ref="edgeForm" :model="form" label-width="80px">
            <el-form-item label="标签内容">
              <el-input v-model="form.label" size="small" placeholder="标签文字,空则没有" @input="changeEdgeLabel(form.label, labelForm.fontColor, labelForm.fill, labelForm.stroke)"></el-input>
              <div v-if="form.label" class="label-style">
                <p>字体颜色:<el-color-picker v-model="labelForm.fontColor" size="mini" @change="changeEdgeLabel(form.label, labelForm.fontColor, labelForm.fill, labelForm.stroke)"></el-color-picker></p>
                <p>背景颜色:<el-color-picker v-model="labelForm.fill" size="mini" @change="changeEdgeLabel(form.label, labelForm.fontColor, labelForm.fill, labelForm.stroke)"></el-color-picker></p>
                <p>描边颜色:<el-color-picker v-model="labelForm.stroke" size="mini" @change="changeEdgeLabel(form.label, labelForm.fontColor, labelForm.fill, labelForm.stroke)"></el-color-picker></p>
              </div>
            </el-form-item>
            <el-form-item label="线条颜色">
              <el-color-picker v-model="form.stroke" size="small" @change="changeEdgeStroke"></el-color-picker>
            </el-form-item>
            <el-form-item label="线条样式">
              <el-select v-model="form.connector" size="small" placeholder="请选择" @change="changeEdgeConnector">
                <el-option label="直角" value="normal"></el-option>
                <el-option label="圆角" value="rounded"></el-option>
                <el-option label="平滑" value="smooth"></el-option>
                <el-option label="跳线(两线交叉)" value="jumpover"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="线条宽度">
              <el-input-number v-model="form.strokeWidth" size="small" @change="changeEdgeStrokeWidth" :min="2" :step="2" :max="6" label="线条宽度"></el-input-number>
            </el-form-item>
            <el-form-item label="双向箭头">
              <el-switch v-model="form.isArrows" @change="changeEdgeArrows"></el-switch>
            </el-form-item>
            <el-form-item label="流动线条">
              <el-switch v-model="form.isAnit" @change="changeEdgeAnit"></el-switch>
            </el-form-item>
            <el-form-item label="调整线条">
              <el-switch v-model="form.isTools" @change="changeEdgeTools"></el-switch>
            </el-form-item>
          </el-form>
        </div>
        <div class="edit-btn">
          <el-button type="danger" @click="handlerDel" style="width:100%">删除此{{editTitle === '编辑节点' ? '节点' : '连线'}}</el-button>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  import { Graph, Shape } from '@antv/x6'
  import { configSetting, configNodeShape, configNodePorts, configEdgeLabel, graphBindKey } from '@/utils/antvSetting'
  export default {
    name: "AntV6X",
    /**
     * 这个是作为子组件分别接受了两个数据一个是高度height,一个是反显图表数据tempGroupJson
     * 作为子组件例子 <AntVXSix v-model="tempGroupJson" height="720px" />
     * 
     */ 
    props: {
      height: {
        type: String,
        default: '100vh' //'720px'
      },
      value: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        graph: null,
        isChange: false,
        isPortsShow: false,
        menuItem: '',
        selectCell: '',
        editDrawer: false,
        editTitle: '',
        form:{},
        labelForm: {
          fontColor: '#333',
          fill: '#FFF',
          stroke: '#555'
        }
      }
    },
    created() {
    },
    watch:{
      value:{
        handler: function(){
          if(this.graph){
            this.isChange = false
            this.isPortsShow = false
            this.menuItem = ''
            this.selectCell = ''
            this.editDrawer = false
            this.graph.dispose()
            this.initGraph()
          }
        },
        deep: true,
        immediate: true
      }
    },
    mounted() {
      this.initGraph()
    },
    beforeDestroy() {
      this.graph.dispose()
    },
    methods: {
      // 链接桩的显示与隐藏,主要是照顾菱形
      changePortsShow(val){
        const container = document.getElementById('wrapper')
        const ports = container.querySelectorAll('.x6-port-body')
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
          ports[i].style.visibility = val ? 'visible' : 'hidden'
        }
      },
      // 初始化渲染画布
      initGraph(){
        const graph = new Graph({
          container: document.getElementById('wrapper'),
          ...configSetting(Shape)
        })
        // 画布事件
        graph.on('node:mouseenter', () => {
          this.changePortsShow(true)
        })
        graph.on('node:mouseleave', () => {
          if(this.isPortsShow) return
          this.changePortsShow(false)
        })
        // 点击编辑
        graph.on('cell:click', ({ cell }) => {
          this.editForm(cell)
        })
        // 画布键盘事件
        graphBindKey(graph)
        // 删除
        graph.bindKey(['delete','backspace'], () => {
          this.handlerDel()
        })
        // 赋值
        this.graph = graph
        // 返现方法
        if(this.value && JSON.parse(this.value).length){
          const resArr = JSON.parse(this.value)
          // 导出的时候删除了链接桩设置加回来
          const portsGroups = configNodePorts().groups
          if(resArr.length){
            const jsonTemp = resArr.map(item=>{
              if(item.ports) item.ports.groups = portsGroups 
              return item
            })
            graph.fromJSON(jsonTemp)
          }
        }
        // 画布有变化
        graph.on('cell:changed', () => { 
          this.isChangeValue()
        })
      },
      // 画布是否有变动
      isChangeValue(){
        if(!this.isChange) {
          this.isChange = true
          this.$emit('cellChanged', true)
        }
      },
      menuDrag(type){
        // 根据type获取到不同节点的预设参数
        this.menuItem = configNodeShape(type)
      },
      drop(event){
         // 节点预设 ,添加位置信息和链接桩信息组合成完整的节点
        const nodeItem = {
          ...this.menuItem,
          x: event.offsetX - (this.menuItem.width / 2),
          y: event.offsetY - (this.menuItem.height / 2),
          ports: configNodePorts()
        }
        // 创建节点
        this.graph.addNode(nodeItem)
        this.isChangeValue()
      },
       // 点击编辑更具不同的内容获取编辑数据
      editForm(cell){
        if(this.selectCell) this.selectCell.removeTools() // 删除修改线的工具
        this.selectCell = cell
        // 编辑node节点
        if(cell.isNode() && cell.data.type && cell.data.type.includes('default')){
          this.editTitle = '编辑节点'
          const body = cell.attrs.body || cell.attrs.rect || cell.attrs.polygon || cell.attrs.circle
          this.form = {
            labelText: cell.attrs.label.text || '',
            fontSize: cell.attrs.label.fontSize || 14,
            fontFill: cell.attrs.label.fill || '',
            fill: body.fill|| '',
            stroke: body.stroke || ''
          }
          return this.editDrawer = true
        }
        // 编辑图片节点
        if(cell.isNode() && cell.data.type && cell.data.type === 'otherImage'){
          this.editTitle = '编辑图片节点'
          const attrs = cell.attrs || { body:{fill: ''}, label: {text: '', fill: ''}, image:{xlinkHref: '', height:  80, width: 80} }
          this.form = {
            fill: attrs.body.fill,
            labelText: attrs.label.text,
            labelFill: attrs.label.fill,
            height: (attrs.image && attrs.image.height) || 80,
            width: (attrs.image && attrs.image.width) || 80,
            xlinkHref: (attrs.xlinkHref && attrs.image.xlinkHref) || 'https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg'
          }
          return this.editDrawer = true
        }
        // 编辑连线
        if(!cell.isNode() && cell.shape === 'edge'){
          this.editTitle = '编辑连线'
          this.form = {
            label: (cell.labels && cell.labels[0]) ? cell.labels[0].attrs.labelText.text : '',
            stroke: cell.attrs.line.stroke || '',
            connector: 'rounded',
            strokeWidth: cell.attrs.line.strokeWidth || '',
            isArrows: cell.attrs.line.sourceMarker ? true : false,
            isAnit: cell.attrs.line.strokeDasharray ? true : false,
            isTools: false
          }
          // 看连线上是否有label
          const edgeCellLabel = cell.labels && cell.labels[0] && cell.labels[0].attrs || false
          if(this.form.label && edgeCellLabel){
            this.labelForm = {
              fontColor: edgeCellLabel.labelText.fill || '#333',
              fill: edgeCellLabel.labelBody.fill || '#fff',
              stroke: edgeCellLabel.labelBody.stroke || '#555'
            }
          } else {
            this.labelForm = { fontColor: '#333', fill: '#FFF', stroke: '#555' }
          }
          return this.editDrawer = true
        }
      },
      closeEditForm(){
        this.editDrawer = false
        if(this.selectCell) this.selectCell.removeTools()
      },
      // 修改一般节点
      changeNode(type, value){
        switch (type) {
          case 'labelText':
            this.selectCell.attr('label/text', value)
            break;
          case 'fontSize':
            this.selectCell.attr('label/fontSize', value)
            break;
          case 'fontFill':
            this.selectCell.attr('label/fill', value)
            break;
          case 'fill':
            this.selectCell.attr('body/fill', value)
            break;
          case 'stroke':
            this.selectCell.attr('body/stroke', value)
            break;
        }
      },
      // 修改图片节点
      changeImageNode(type, value){
        switch (type) {
          case 'labelText':
            this.selectCell.attr('label/text', value)
            break;
          case 'labelFill':
            this.selectCell.attr('label/fill', value)
            break;
          case 'fill':
            this.selectCell.attr('body/fill', value)
            break;
          case 'xlinkHref':
            this.selectCell.attr('image/xlinkHref', value)
            break;
          case 'height':
            this.selectCell.attr('image/height', value)
            break;
          case 'width':
            this.selectCell.attr('image/width', value)
            break;
        }
      },
      // 修改边label属性
      changeEdgeLabel(label, fontColor, fill, stroke){
        this.selectCell.setLabels([configEdgeLabel(label, fontColor, fill, stroke)])
        if(!label) this.labelForm = { fontColor: '#333', fill: '#FFF', stroke: '#555' }
      },
      // 修改边的颜色
      changeEdgeStroke(val){
        this.selectCell.attr('line/stroke', val)
      },
      // 边的样式
      changeEdgeConnector(val){
        switch (val) {
          case 'normal':
            this.selectCell.setConnector(val)
          break;
          case 'smooth':
            this.selectCell.setConnector(val)
          break;
            case 'rounded':
            this.selectCell.setConnector(val, { radius: 20 })
          break;
            case 'jumpover':
            this.selectCell.setConnector(val, { radius: 20 })
          break;
        }
      },
      // 边的宽度
      changeEdgeStrokeWidth(val){
        if(this.form.isArrows){
          this.selectCell.attr({
            line: {
              strokeWidth: val,
              sourceMarker: {
                width: 12 * (val / 2) || 12,
                height: 8 * (val / 2) || 8
              },
              targetMarker: {
                width: 12 * (val / 2) || 12,
                height: 8 * (val / 2) || 8
              }
            }
          })

        } else {
          this.selectCell.attr({
            line: {
              strokeWidth: val,
              targetMarker: {
                width: 12 * (val / 2) || 12,
                height: 8 * (val / 2) || 8
              }
            }
          })
        }
        
      },
      // 边的箭头
      changeEdgeArrows(val){
        if(val){
          this.selectCell.attr({
            line: {
              sourceMarker: {
                name: 'block',
                width: 12 * (this.form.strokeWidth / 2) || 12,
                height: 8 * (this.form.strokeWidth / 2) || 8
              },
              targetMarker: {
                name: 'block',
                width: 12 * (this.form.strokeWidth / 2) || 12,
                height: 8 * (this.form.strokeWidth / 2) || 8
              },
            }
          })
        } else {
          this.selectCell.attr({
            line: {
              sourceMarker: '',
              targetMarker: {
                name: 'block',
                size: 10 * (this.form.strokeWidth / 2) || 10
              },
            }
          })
        }
      },
      // 边的添加蚂蚁线
      changeEdgeAnit(val){
        if(val){
          this.selectCell.attr({
            line: {
              strokeDasharray: 5,
              style: {
                animation: 'ant-line 30s infinite linear',
              }
            }
          })
        } else {
          this.selectCell.attr({
            line: {
              strokeDasharray: 0,
              style: {
                animation: '',
              }
            }
          })
        }
      },
      // 给线添加调节工具
      changeEdgeTools(val){
        if(val) this.selectCell.addTools(['vertices', 'segments'])
        else this.selectCell.removeTools()
      },
      // 删除节点
      handlerDel(){
        this.$confirm(`此操作将永久删除此${this.editTitle === '编辑节点' ? '节点' : '连线'}, 是否继续?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          const cells = this.graph.getSelectedCells()
          if (cells.length) {
            this.graph.removeCells(cells)
            this.form = {}
            this.editDrawer = false
            this.$message({type: 'success',message: '删除成功!'})
          }
        }).catch(() => {})
      },
      // 导出
      handlerSend(){
        // 我在这里删除了链接桩的设置,和工具(为了减少数据),反显的时候要把删除的链接桩加回来
        const {cells: jsonArr} = this.graph.toJSON()
        const tempGroupJson = jsonArr.map(item=>{
          if(item.ports && item.ports.groups) delete item.ports.groups
          if(item.tools) delete item.tools
          return item
        })
        if(this.selectCell){
          this.selectCell.removeTools()
          this.selectCell = ''
        }
        this.$emit('finish', JSON.stringify(tempGroupJson))
        console.log(JSON.stringify(tempGroupJson))
      },
    }
  }
</script>
<style lang="scss">
@keyframes ant-line {
  to {
      stroke-dashoffset: -1000
  }
}
</style>
<style lang="scss" scoped="scoped">
.all{
  border-radius: 8px;
  overflow: hidden;
}
.antv-content{
  background: #fff;
  display: flex;
  overflow: hidden;
  position: relative;
  .antv-menu{
    width: 200px;
    border-right: 1px solid #d5d5d5;
    padding: 10px;
    h3{
      padding: 10px;
    };
    li{
      padding: 10px;
      border-radius: 8px;
      border: 1px solid #555;
      background: #fff;
      margin: 5px 10px;
      font-size: 12px;
      display: flex;
      align-items: center;
      cursor: pointer;
      transition: all 0.5s ease;
      &:hover{
        box-shadow: 0 0 5px rgba($color: #000000, $alpha: 0.3);
      }
      i{
        font-size: 18px;
        margin-right: 10px;
      }
      strong{
        flex: 1;
      }
    }
  }
  .antv-wrapper{
    flex: 1;
    position: relative;
    .wrapper-canvas{
      position: relative;
      height: 100vh;
      min-height: 720px;
    }
    .wrapper-tips{
      padding: 10px;
      display: flex;
      align-items: center;
      position: absolute;
      top: 0;
      left: 0;
      .wrapper-tips-item{
        span{
          padding-left: 10px;
          font-size: 12px;
        }
      }
    }
  }
}
i.icon-oval{
    display: inline-block;
    width: 16px;
    height: 10px;
    border-radius: 10px;
    border: 2px solid #555;
}
i.icon-square{
    display: inline-block;
    width: 16px;
    height: 10px;
    border: 2px solid #555;
}
i.icon-ysquare{
   display: inline-block;
    width: 16px;
    height: 10px;
    border-radius: 4px;
    border: 2px solid #555;
}
i.icon-rhombus{
   display: inline-block;
    width: 10px;
    height: 10px;
    border: 2px solid #555;
    transform: rotate(45deg);
}
i.icon-rhomboid{
   display: inline-block;
    width: 10px;
    height: 10px;
    border: 2px solid #555;
    transform: skew(-30deg);
}
i.icon-circle{
   display: inline-block;
    width: 16px;
    height: 16px;
    border-radius: 16px;
    border: 2px solid #555;
}
.edit-main{
  position: absolute;
  right: 0;
  top: 0;
  height: 100%;
  width: 280px;
  border-left: 1px solid #f2f2f2;
  box-shadow: 0 -10px 10px rgba($color: #000000, $alpha: 0.3);
  padding: 20px;
  background: #fff;
  box-sizing: border-box;
  .edit-main-title{
    display: flex;
    justify-content: space-between;
    align-items: center;
    h3{
      flex: 1;
    }
    i{
      cursor: pointer;
      font-size: 20px;
      opacity: 0.7;
      &:hover{
        opacity: 1;
      }
    }
  }
  
  .form-main{
    padding: 20px 0;
    .label-style{
      background: #f2f2f2;
      padding: 0 10px;
      p{
        display: flex;
        align-items: center;
        font-size: 12px;
      }
    }
  }
  .edit-btn{
  }
  .see-box{
  padding: 20px;
    background: #f2f2f2;
    h5{
      padding-bottom: 10px;
    }
    .see-item{
      padding: 10px 30px;
      border: 2px solid #333;
      text-align: center;
    }
  }
}
.wrapper-btn{
  text-align: center;
  padding: 20px;
  button{
    width: 100%;
  }
}
</style>

展示页面(反显页面)

<template>
  <div class="antv-wrapper">
    <div class="wrapper-canvas" id="wrapper"></div>
  </div>
</template>
<script>
  import { Graph, Shape } from '@antv/x6'
  import { configNodePorts } from '@/utils/antvSetting'
  // 反显数据
  const resData = [{"shape":"edge","attrs":{"line":{"stroke":"#05C13A","strokeWidth":4,"targetMarker":{"name":"block","width":24,"height":16},"strokeDasharray":5,"style":{"animation":"ant-line 30s infinite linear"}}},"id":"390b4bc1-4945-464c-a34d-eeb14acba1a1","zIndex":0,"source":{"cell":"c9ba8f28-9335-447a-a4c0-1dbe34afc815","port":"9daae234-935b-4634-aae4-62fe6e1a763a"},"target":{"cell":"e78740b8-f27f-4f3d-b1d4-11e6e810c76a","port":"122093c7-de48-435b-95aa-8ef9cf58a9ca"},"connector":{"name":"normal"},"vertices":[{"x":640,"y":134}]},{"shape":"edge","attrs":{"line":{"stroke":"#0074FF","targetMarker":{"name":"block","width":12,"height":8},"sourceMarker":{"name":"block","width":12,"height":8}}},"id":"4fa2ff4a-a78e-4f4c-a8fa-0b1c536cbe1b","zIndex":0,"source":{"cell":"e78740b8-f27f-4f3d-b1d4-11e6e810c76a","port":"7ae5d159-3823-4b7f-9e2b-994405071dc8"},"target":{"cell":"9e97d5af-ae22-47e0-bb9f-a0ebf8db0910","port":"40ce97eb-f258-4b8c-b3bc-b798b4662b77"},"vertices":[{"x":1240,"y":250},{"x":1240,"y":400}]},{"shape":"edge","attrs":{"line":{"stroke":"#E36600","strokeWidth":4,"targetMarker":{"name":"block","width":24,"height":16}}},"id":"c0874682-8817-4797-b6e9-5022fd6238b3","zIndex":0,"source":{"cell":"c9ba8f28-9335-447a-a4c0-1dbe34afc815","port":"4b5b6dd0-c31a-42bd-ae81-74b26f67b3eb"},"target":{"cell":"9e97d5af-ae22-47e0-bb9f-a0ebf8db0910","port":"ef42ef10-d01d-49b1-95da-2f19189adf29"},"connector":{"name":"smooth"},"vertices":[{"x":290,"y":290},{"x":380,"y":400}]},{"position":{"x":170,"y":130},"size":{"width":100,"height":50},"attrs":{"text":{"text":"椭圆形"},"body":{"rx":20,"ry":26,"fill":"#08D34F","stroke":"#028222"},"label":{"text":"椭圆形","fontSize":16,"fill":"#FFFFFF"}},"visible":true,"shape":"rect","id":"c9ba8f28-9335-447a-a4c0-1dbe34afc815","data":{"type":"defaultOval"},"ports":{"items":[{"group":"top","id":"e49da871-6793-4a8e-909d-becd630de7cc"},{"group":"right","id":"9daae234-935b-4634-aae4-62fe6e1a763a"},{"group":"bottom","id":"4b5b6dd0-c31a-42bd-ae81-74b26f67b3eb"},{"group":"left","id":"1a7be646-1507-4122-94e0-0ad9d0ab0554"}]},"zIndex":1},{"position":{"x":590,"y":350},"size":{"width":480,"height":100},"attrs":{"text":{"text":"平行四边形"},"body":{"refPoints":"10,0 40,0 30,20 0,20","fill":"#7A0289","stroke":"#49007A"},"label":{"text":"平行四边形","fontSize":"40","fill":"#FFFFFF"}},"visible":true,"shape":"polygon","id":"9e97d5af-ae22-47e0-bb9f-a0ebf8db0910","data":{"type":"defaultRhomboid"},"ports":{"items":[{"group":"top","id":"c6a92550-ceb3-411c-8c4c-7848aa064b76"},{"group":"right","id":"40ce97eb-f258-4b8c-b3bc-b798b4662b77"},{"group":"bottom","id":"a47a0d67-e30b-4149-91b7-527c47043f0a"},{"group":"left","id":"ef42ef10-d01d-49b1-95da-2f19189adf29"}]},"zIndex":2},{"position":{"x":940,"y":10},"size":{"width":80,"height":80},"attrs":{"text":{"text":"圆形"},"body":{"fill":"#CD0000","stroke":"#950000"},"label":{"text":"圆形","fontSize":16,"fill":"#FFFFFF"}},"visible":true,"shape":"circle","id":"e78740b8-f27f-4f3d-b1d4-11e6e810c76a","data":{"type":"defaultCircle"},"ports":{"items":[{"group":"top","id":"11a5b89d-2e19-44db-ad6b-2f03983ea097"},{"group":"right","id":"7ae5d159-3823-4b7f-9e2b-994405071dc8"},{"group":"bottom","id":"358110af-4dd6-4806-8b24-46b2b34b2f7c"},{"group":"left","id":"122093c7-de48-435b-95aa-8ef9cf58a9ca"}]},"zIndex":3}]
  export default {
    name: "jsplumb",
    data() {
      return {
        
      }
    },
    created() {
    },
    mounted () {
      this.initGraph()
    },
    methods: {
      // 初始化渲染画布
      initGraph(){
        const graph = new Graph({
          container: document.getElementById('wrapper'),
          grid: true,
          autoResize: true,
          interacting: false,
          connecting: {
            router: {
              name: 'manhattan',
              args: {
                padding: 1,
              },
            },
            connector: {
              name: 'rounded',
              args: {
                radius: 8,
              },
            },
            anchor: 'center',
            connectionPoint: 'anchor',
            allowBlank: false,
            snap: {
              radius: 20,
            },
            createEdge() {
              return new Shape.Edge({
                attrs: {
                  line: {
                    stroke: '#A2B1C3',
                    strokeWidth: 2,
                    targetMarker: {
                      name: 'block',
                      width: 12,
                      height: 8
                    },
                  },
                },
                zIndex: 0,
              })
            }
          },
        })
        // 返现方法
        const portsGroups = configNodePorts().groups
        if(resData.length){
          const jsonTemp = resData.map(item=>{
            if(item.ports) item.ports.groups = portsGroups 
            return item
          })
          graph.fromJSON(jsonTemp)
        }
        graph.centerContent()
      }
    }
  }
</script>
<style lang="scss">
@keyframes ant-line {
  to {
      stroke-dashoffset: -1000
  }
}
</style>
<style lang="scss" scoped="scoped">
  .antv-wrapper{
    position: relative;
    height: 100vh;
    flex: 1;
    .wrapper-canvas{
      height: 100%;
      width: 100%;
      position: relative;
    }
    .wrapper-tips{
      padding: 10px;
      display: flex;
      align-items: center;
      position: absolute;
      top: 0;
      left: 0;
      .wrapper-tips-item{
        span{
          padding-left: 10px;
          font-size: 12px;
        }
      }
    }
  }
</style>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容