vue3 antvX6的使用原始碼

一尘子!發表於2024-10-14
npm install --save @antv/x6
<template>
  <div class="dashboard-container">
    <p>選擇節點</p>
    <button @click="save">儲存</button>
    <div class="antvBox">
      <div class="menu-list">
        <div
          v-for="item in moduleList"
          :key="item.id"
          draggable="true"
          @dragend="handleDragEnd($event, item)"
        >
          <img :src="item.image" alt="" />
          <p>{{ item.name }}</p>
        </div>
      </div>
      <div class="canvas-card">
        <div id="container" />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { Graph } from '@antv/x6'

import t1 from '../../assets/images/1.png'
import t2 from '../../assets/images/2.png'
import t3 from '../../assets/images/3.png'
import t4 from '../../assets/images/4.png'
import t5 from '../../assets/images/5.png'

const moduleList = ref([
  {
    id: 1,
    name: '節點1',
    image: t1
  },
  {
    id: 8,
    name: '節點2',
    image: t2
  },
  {
    id: 2,
    name: '節點3',
    image: t3
  },
  {
    id: 3,
    name: '節點4',
    image: t4
  },
  {
    id: 4,
    name: '節點5',
    image: t5
  }
])

const curSelectNode = ref(null)

/**
 * 拖拽左側
 * @param e
 * @param item
 */
const handleDragEnd = (e, item) => {
  addHandleNode(e.pageX - 300, e.pageY - 200, new Date().getTime(), item.image, item.name)
}

const graph = ref<Graph | null>(null)

const initGraph = () => {
  const container = document.getElementById('container')
  if (container === null) {
    throw new Error('Container element not found')
  }
  graph.value = new Graph({
    container: container, // 畫布容器
    width: container.offsetWidth, // 畫布寬
    height: container.offsetHeight, // 畫布高
    background: false, // 背景(透明)
    snapline: true, // 對齊線
    // 配置連線規則
    connecting: {
      snap: true, // 自動吸附
      allowBlank: false, // 是否允許連線到畫布空白位置的點
      allowMulti: true, // 是否允許在相同的起始節點和終止之間建立多條邊
      allowLoop: true, // 是否允許建立迴圈連線,即邊的起始節點和終止節點為同一節點
      highlight: true, // 拖動邊時,是否高亮顯示所有可用的節點
      highlighting: {
        magnetAdsorbed: {
          name: 'stroke',
          args: {
            attrs: {
              fill: '#5F95FF',
              stroke: '#5F95FF'
            }
          }
        }
      },
      router: {
        // 對路徑新增額外的點
        name: 'orth'
      },
      connector: {
        // 邊渲染到畫布後的樣式
        name: 'rounded',
        args: {
          radius: 8
        }
      }
    },
    panning: {
      enabled: false
    },
    mousewheel: {
      enabled: true, // 支援滾動放大縮小
      zoomAtMousePosition: true,
      modifiers: 'ctrl',
      minScale: 0.5,
      maxScale: 3
    },
    grid: {
      type: 'dot',
      size: 20, // 網格大小 10px
      visible: true, // 渲染網格背景
      args: {
        color: '#a0a0a0', // 網格線/點顏色
        thickness: 2 // 網格線寬度/網格點大小
      }
    }
  })
  nodeAddEvent()
}
/**
 * 新增畫布到節點
 * x座標、y座標、id節點唯一標識、image圖片、name節點名稱
 */
//新增節點到畫布
const addHandleNode = (x, y, id, image, name) => {
  graph.value.addNode({
    id: id,
    shape: 'image', // 指定使用何種圖形,預設值為 'rect'
    x: x,
    y: y,
    width: 60,
    height: 60,
    imageUrl: image,
    attrs: {
      body: {
        stroke: '#ffa940',
        fill: '#ffd591'
      },
      label: {
        textWrap: {
          width: 90,
          text: name
        },
        fill: 'black',
        fontSize: 12,
        refX: 0.5,
        refY: '100%',
        refY2: 4,
        textAnchor: 'middle',
        textVerticalAnchor: 'top'
      }
    },
    ports: {
      groups: {
        group1: {
          position: [30, 30]
        }
      },
      items: [
        {
          group: 'group1',
          id: 'port1',
          attrs: {
            circle: {
              r: 6,
              magnet: true,
              stroke: '#ffffff',
              strokeWidth: 2,
              fill: '#5F95FF'
            }
          }
        }
      ]
    },
    zIndex: 10
  })
}
/**
 * 滑鼠移入節點再顯示連結樁
 */
const nodeAddEvent = () => {
  const container = document.getElementById('container')
  if (container === null) {
    throw new Error('Container element not found')
  }
  const changePortsVisible = visible => {
    const ports = container.querySelectorAll('.x6-port-body')
    for (let i = 0, len = ports.length; i < len; i = i + 1) {
      ports[i].style.visibility = visible ? 'visible' : 'hidden'
    }
  }
  graph.value.on('node:mouseenter', () => {
    changePortsVisible(true)
  })
  graph.value.on('node:mouseleave', () => {
    changePortsVisible(false)
  })

  // 節點繫結點選事件 刪除節點
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  graph.value.on('node:click', ({ e, x, y, node, view }) => {
    console.log('點選!!!', node)
    // 判斷是否有選中過節點
    if (curSelectNode.value) {
      // 移除選中狀態
      curSelectNode.value.removeTools()
      // 判斷兩次選中節點是否相同
      if (curSelectNode.value !== node) {
        node.addTools([
          {
            name: 'boundary',
            args: {
              attrs: {
                fill: '#16B8AA',
                stroke: '#2F80EB',
                strokeWidth: 1,
                fillOpacity: 0.1
              }
            }
          },
          {
            name: 'button-remove',
            args: {
              x: '100%',
              y: 0,
              offset: {
                x: 0,
                y: 0
              }
            }
          }
        ])
        curSelectNode.value = node
      } else {
        curSelectNode.value = null
      }
    } else {
      curSelectNode.value = node
      node.addTools([
        {
          name: 'boundary',
          args: {
            attrs: {
              fill: '#16B8AA',
              stroke: '#2F80EB',
              strokeWidth: 1,
              fillOpacity: 0.1
            }
          }
        },
        {
          name: 'button-remove',
          args: {
            x: '100%',
            y: 0,
            offset: {
              x: 0,
              y: 0
            }
          }
        }
      ])
    }
  })

  // 刪除連結節點的線
  // 連線繫結懸浮事件
  graph.value.on('cell:mouseenter', ({ cell }) => {
    if (cell.shape == 'edge') {
      cell.addTools([
        {
          name: 'button-remove',
          args: {
            x: '100%',
            y: 0,
            offset: {
              x: 0,
              y: 0
            }
          }
        }
      ])
      cell.setAttrs({
        line: {
          stroke: '#409EFF'
        }
      })
      cell.zIndex = 99 // 保證當前懸停的線在最上層,不會被遮擋
    }
  })
  graph.value.on('cell:mouseleave', ({ cell }) => {
    if (cell.shape === 'edge') {
      cell.removeTools()
      cell.setAttrs({
        line: {
          stroke: 'black'
        }
      })
      cell.zIndex = 1 // 保證未懸停的線在下層,不會遮擋懸停的線
    }
  })
}

//儲存畫布,並提交
const save = () => {
  console.log(graph.value.toJSON(), 'graph')
  console.log(graph.value.getNodes(), 'node')
}
onMounted(() => {
  initGraph()
})
</script>
<style lang="scss" scoped>
/* @use ''; 引入css類 */
.dashboard-container {
  .antvBox {
    display: flex;
    width: 100%;
    height: 100%;
    color: black;
    padding-top: 20px;
    .menu-list {
      height: 100%;
      width: 300px;
      padding: 0 10px;
      box-sizing: border-box;
      display: flex;
      justify-content: space-between;
      align-content: flex-start;
      flex-wrap: wrap;
      > div {
        margin-bottom: 10px;
        border-radius: 5px;
        padding: 0 10px;
        box-sizing: border-box;
        cursor: pointer;
        color: black;
        width: 105px;
        display: flex;
        flex-wrap: wrap;

        justify-content: center;
        img {
          height: 50px;
          width: 50px;
        }
        P {
          width: 90px;
          text-align: center;
        }
      }
    }
    .canvas-card {
      width: 1700px;
      height: 750px;
      box-sizing: border-box;
      > div {
        width: 1400px;
        height: 750px;
        border: 2px dashed #2149ce;
      }
    }
  }
}
</style>

借鑑https://blog.csdn.net/wzy_PROTEIN/article/details/136305034

相關文章