AntV G6 组织图使用(后端渲染数据)

一、业务场景:
点击按钮,跳转页面并显示该数据的组织架构图(类似于粒子效果)

二、问题描述:
初始写死的数据能显示,但是从接口请求到的数据赋上值 渲染不了

三、具体实现步骤:
(1)下载

  npm install --save @antv/g6

(2)在需要使用的文件里面单独引入

  import G6 from '@antv/g6'

(3)所有逻辑处理写在初始化函数里面

 initComponent (arrList,edgesList) {
		// arrList是后台返回的数组数据,拿到后调用初始化函数把值传过来
		// edgesList 是用粒子连接 arrList数组里面数据的(以arrList数组里的第一项为中心)
        this.data = {
          nodes: arrList,
          edges:edgesList
        }
        /**
         * 遍历节点数据,给节点添加图片
         */
        for (let i = 0, len = this.data.nodes.length; i < len; i++) {
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].id !== 'node1') { // 'offlineAnomaly'
            this.data.nodes[i].img = '/labelshow.jpg'
          }
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].status == 1) { // 'onlineAuth'
            // this.data.nodes[i].img = 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000'
          }
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].status == 2) { // 'onlineAuth'
            // this.data.nodes[i].img = 'https://img0.baidu.com/it/u=3927459320,2138990686&fm=26&fmt=auto'
          }
        }
        // todo 设置鼠标悬停显示详情操作
        const tooltip = new G6.Tooltip({
          offsetX: 70,
          offsetY: 20,
          getContent (e) {
            const outDiv = document.createElement('div')
            outDiv.style.width = '180px'
            // e.item._cfg.id== 'node1'?<li>人员ID: ${e.item.getModel().id}li>: <li>标签ID: ${e.item.getModel().id}li>
            if(e.item._cfg.id== 'node1'){
              outDiv.innerHTML = `
            <ul id="nodeDetails">
              <li>人员名称: ${e.item.getModel().label}li>



            ul>`
              return outDiv
            }else {
              outDiv.innerHTML = `
            <ul id="nodeDetails">
              <li>标签名称: ${e.item.getModel().label}li>
               <li>标签ID: ${e.item.getModel().id}li>
            ul>`
              return outDiv
            }

          },
          itemTypes: ['node']
        })
        // todo 初始化画布宽高为div parentContent 的宽度和高度
        this.canvasWidth = this.$refs.parentContent.clientWidth
        this.canvasHeight = this.$refs.parentContent.clientHeight
        this.graph = new G6.Graph({
          container: 'container',
          width: this.canvasWidth,
          height: this.canvasHeight,
          linkCenter: true,
          plugins: [tooltip], // 配置 Tooltip 插件
          modes: {
            default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'activate-relations'] // 允许拖拽画布、放缩画布、拖拽节点、设置高亮
          },
          layout: {
            type: 'force',
            preventOverlap: true, // 防止节点重叠
            // 防碰撞必须设置nodeSize或size,否则不生效,由于节点的size设置了40,虽然节点不碰撞了,但是节点之间的距离很近,label几乎都挤在一起,所以又重新设置了大一点的nodeSize,这样效果会好很多
            nodeSize: 120,
            linkDistance: 90 // 指定边距离为150
          },
          defaultNode: { // 节点样式修改
            type: 'image', // 设置节点为图片
            size: [40, 40], // 节点大小
            labelCfg: { // 修改节点label样式
              style: {
                fill: '#5B8FF9', // 字体颜色
                fontSize: 14 // 字体大小
              }
            }
          },
          defaultEdge: {
            style: {
              lineWidth: 2, // 线宽
              stroke: '#778899', // 线的颜色
              endArrow: { // 设置终点箭头
                path: G6.Arrow.vee(5, 20, 15), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应)
                d: 15
              }
            }
          }
          // 设置节点高亮样式
          // edgeStateStyles: {
          //   highlight: {
          //     stroke: '#32dadd'
          //   }
          // }
        })

        /**
         * 鼠标移出时,删除节点连线高亮
         * */
        // this.graph.on('node:mouseleave', this.clearAllStats)

        this.graph.on('node:click', (e) => {
          const nodeItem = e.item // 获取被点击的节点元素对象
          console.log(nodeItem)
          console.log('单击', nodeItem._cfg)
          // 如果点击的是人员就开始添加
          if(nodeItem._cfg.id== 'node1'){
            this.visible = true
          }
        if(nodeItem._cfg.id !== 'node1'){
          this.remove(nodeItem._cfg.id)
        }
        })
        // 接收数据并渲染
        this.graph.data(this.data)
        this.graph.render()
        
        const el = this.graph.findById(this.data.nodes[0]?.id)
        console.log('model', el)
         el._cfg.model.label = '张三'
        el._cfg.model.img = 'https://img2.baidu.com/it/u=617857580,3197175121&fm=253&fmt=auto&app=138&f=JPEG?w=260&h=260'
        this.graph.refreshItem(el)

        // 双击节点
        this.graph.on('node:dblclick', (e) => {
          // 先将所有当前是 click 状态的节点置为非 click 状态
          const clickNodes = this.graph.findAllByState('node', 'dblclick')
          clickNodes.forEach((cn) => {
            this.graph.setItemState(cn, 'dblclick', false)
          })
          const nodeItem = e.item // 获取被点击的节点元素对象
          console.log('双击', nodeItem._cfg)
          this.graph.setItemState(nodeItem, 'dblclick', true) // 设置当前节点的 click 状态为 true
        })
      },

(4)在任何地方要重新渲染的话,都需要调用初始化函数,两个值都必须传,必须确保是最新的

         this.initComponent(this.data.nodes,this.data.edges)

四、完整代码

<template>
  
  <div class="parentContent" ref="parentContent">
    <div id="container" ref="container">div>

    
    <a-modal
      :title="labelTitle==0?'新增标签':'编辑标签'"
      :visible="visible"
      width="600px"
      @cancel="handleCancel"
    >
      <template slot="footer">
        <a-button @click="handleCancel">取消a-button>
        <a-button type="primary" @click="handleOk">确定a-button>
      template>
      <a-form-model ref="ruleForm" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
        <a-form-model-item label="标签名称" prop="labelname">
          <a-input v-model="form.labelname" placeholder="请输入"/>
        a-form-model-item>

        <a-form-model-item label="标签颜色" prop="color">
          <a-input placeholder="请选择" type="color" v-model="form.color"/>
        a-form-model-item>

        <a-form-item label="标签图标" prop="tb">
          <a-input
            v-model="form.tb"
            ref="iconInput"
            @click="iconselect()"
            enterButton="选择图标"
            placeholder="选择图标"
          >
            <a-icon slot="prefix" :type="icon"/>
            <a-icon slot="suffix" type="close-circle" @click="emitEmpty"/>
          a-input>
        a-form-item>

        <a-form-model-item label="代码" prop="code" v-if="form.labelshape=='0'" v-hasPermi="['label:ifSystemLabel']">
          <a-input v-model="form.code" placeholder="请输入"/>
        a-form-model-item>
      a-form-model>
      <iconSelector-modal ref="modal" @ok="setIcon" :icon="icon"/>
    a-modal>
  div>
template>
<script>
  import G6 from '@antv/g6'
  import { getLabelLists, delUserLabel, addLabels,addUserLabel, editLabels,showUserLabel } from '@/api/system/label'
  import IconSelectorModal from '@/views/system/modules/IconSelectorModal'
  export default {
    name: 'BqtuDiago',
    components: {
      IconSelectorModal
    },
   async mounted () {
      this.ryId = this.$route.query.ryId
      this.ryname = this.$route.query.name
      console.log(this.ryname)
     this.initSize()
     await this.getUserLabel()
    },
    watch:{
      'this.data.nodes'(val, oldVal) {
        console.log(val,oldVal)
        if(val) {
          const that = this
          val.forEach(function(value, index, array) {
            const el = that.graph.findById(value.id)
            console.log('model', el._cfg.model)
            console.log('value', value)
            el._cfg.model.id = value.id
            el._cfg.model.label = value.label
            el._cfg.model.ip= value.ip
            el._cfg.model.status= value.status
            if (value.status == 1) {
              el._cfg.model.img = '/labelshow.jpg'
            }
            if (value.status == 0) {
              el._cfg.model.img = '.....'
            }
            that.graph.refreshItem(el)
          })
        }
      }
    },
    data () {
      return {
        ryId:'',
        ryname:'',
        labelTitle: 0,
        icon: 'smile',
        visible: false,
        pagination: {
          current: 1,
          pageSize: 10,
          total: 0
        },
        form: {
          labelname: '',
          labeltype: '',
          labelshape: '',
          labelid: '',
          code: '',
          tb: '',
          color: ''
        },
        rules: {
          labelname: [
            { required: true, message: '请输入', trigger: 'blur' }
            // { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
          ],
          labelshape: [
            { required: true, message: '请选择', trigger: 'blur' }
            // { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
          ],
          labeltype: [
            { required: true, message: '请选择', trigger: 'blur' }
            // { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
          ],
          tb: [
            { required: true, message: '请选择', trigger: 'blur' }
            // { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
          ],
          code: [
            { required: true, message: '请输入', trigger: 'blur' }
            // { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
          ]

        },

        data: {
          nodes:[],
          edges:[],
        }, // 拓扑图数据
        graph: undefined, // new G6
        canvasWidth: 0, // 画布宽度
        canvasHeight: 0 // 画布高度
      }
    },
    methods: {

      getUserLabel() {
        console.log(this.ryId)
        showUserLabel({
          ryId: this.ryId,
          pageNum: this.pagination.current,
          pageSize: this.pagination.pageSize
        })
          .then(res => {
            console.log(res)
            const { data,code, msg } = res || {}
            this.data.nodes = data?.sysBqRequests
            console.log(this.data.nodes)

            this.data.nodes.forEach((item)=>{
              this.data.edges.push({
                source: 'node1',
                target: item.id
              })
            })
            console.log(this.data.edges)
            this.data.nodes.unshift({
                  id: 'node1',
                  label: '人员',
                  img:'/portrait.png',
                  status:'0'
            })
            console.log(this.data.nodes)
            this.initComponent(this.data.nodes,this.data.edges)
          }).catch((err) => {
          console.log(err)
        })
      },

// 删除
      remove(id) {
        // e.preventDefault();//阻止默认行为
        console.log(id)
        // this.selectedRowKeys.push(id)
        // console.log(this.selectedRowKeys)
        this.$confirm({
          content: '您确定要删除此标签吗?',
          okText: '确定',
          centered: true,//页面居中位置
          okType: 'danger',
          cancelText: '取消',
          onOk: () => {
            delUserLabel({
              bqId: id
            }).then(res => {
              console.log(res)
              let { msg } = res
              if (msg == 'success') {
                this.$message.success('操作成功')
                // 销毁画布在新增上去
                this.graph.destroy();
                this.getUserLabel()
                // 删除以后清空要删除的数组
                // this.selectedRowKeys = []
              } else {
                // this.$message.error(errorMessage)
              }
            }).catch(err => {
              console.log(err)
            })
          },
          onCancel: () => {
            console.log('Cancel')
          }
        })
      },
      iconselect() {
        this.$refs.modal.show()
      },
      setIcon(icon) {
        console.log(icon)
        this.icon = icon
        this.form.tb = icon
        // this.form.setFieldsValue({ icon: icon })
      },
      emitEmpty() {
        this.$refs.iconInput.focus()
        // this.form.setFieldsValue({ icon: '' })
        this.form.tb = ''
      },
      handleCancel() {
        if(this.labelTitle ==0){
          this.form = {}
        }
        this.visible = false
      },
      // 新增和编辑标签
      handleOk() {
        this.$refs.ruleForm.validate(valid => {
          if (valid) {
            if (this.labelTitle == 0) {
              console.log(this.ryId)
              addUserLabel({
                mc: this.form.labelname,
                // code:this.form.code,
                dm: this.form.code,
                // sfxt: this.form.labelshape,
                ys: this.form.color,
                lx: '1',
                ryId:this.ryId,
                tb: this.form.tb
              })
                .then(res => {
                  console.log(res)
                  let { msg } = res
                  if (msg == 'success') {
                    this.visible = false
                    this.$message.success('操作成功')
                    // 销毁画布在新增上去
                    this.graph.destroy();
                    this.getUserLabel()
                    // this.form = {}
                  } else {
                    this.$message.error(msg)
                  }
                }).catch((err) => {
                console.log(err)
              })
            } else {
              editLabels({
                mc: this.form.labelname,
                dm: this.form.code,
                ys: this.form.color,
                sfxt: this.form.labelshape,
                lx: this.form.labeltype,
                tb: this.form.tb,
                id: this.form.labelid
              })
                .then(res => {
                  console.log(res)
                  let { msg } = res
                  if (msg == 'success') {
                    this.visible = false
                    this.$message.success('操作成功')

                  }
                }).catch((err) => {
                console.log(err)
              })
            }
          }
        })
      },




      /**
       * 设置画布大小自适应
       */
      initSize () {
        const self = this // 因为箭头函数会改变this指向,指向windows。所以先把this保存
        setTimeout(() => {
          // todo 浏览器窗口发生变化时
          window.onresize = function () {
            // todo 获取div parentContent 的宽度和高度
            this.canvasWidth = self.$refs?.parentContent?.clientWidth
            this.canvasHeight = self.$refs?.parentContent?.clientHeight
            // todo 修改画布的大小
            self.graph.changeSize(this.canvasWidth, this.canvasHeight)
            // todo 将图移动到画布中心位置
            self.graph.fitCenter()
          }
        }, 20)
      },
      /**
       * 创建G6,并对G6的一些设置
       * */

      initComponent (arrList,edgesList) {
        console.log(arrList)
        this.data = {
          nodes: arrList,
          //   nodes:[
          //   {
          //     id: 'node1',
          //     label: '采集服务器',
          //     // ip: '192.168.1.1',
          //     // status: 0
          //     // 此处的key值一定不要出现type,如果出现type,图片修改无效
          //   },
          //   {
          //     id: 'node2',
          //     label: '标签1',
          //   },
          //   {
          //     id: 'node3',
          //     label: '标签2',
          //   },
          //   {
          //     id: 'node4',
          //     label: '标签3',
          //   },
          //   {
          //     id: 'node5',
          //     label: '标签4',
          //   },
          //   {
          //     id: 'node6',
          //     label: '标签5',
          //   },
          // ],
          edges:edgesList

            // edges[
            // {
            //   source: 'node1',
            //   target: '188'
            // },
            // {
            //   source: 'node1',
            //   target: '189'
            // },
            // {
            //   source: 'node1',
            //   target: '201'
            // },

          // ]
        }
        /**
         * 遍历节点数据,给节点添加图片
         */
        console.log(this.data.nodes)
        for (let i = 0, len = this.data.nodes.length; i < len; i++) {
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].id !== 'node1') { // 'offlineAnomaly'
            this.data.nodes[i].img = '/labelshow.jpg'
          }
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].status == 1) { // 'onlineAuth'
            // this.data.nodes[i].img = 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000'
          }
          // eslint-disable-next-line eqeqeq
          if (this.data.nodes[i].status == 2) { // 'onlineAuth'
            // this.data.nodes[i].img = 'https://img0.baidu.com/it/u=3927459320,2138990686&fm=26&fmt=auto'
          }
        }
        // todo 设置鼠标悬停显示详情操作
        const tooltip = new G6.Tooltip({
          offsetX: 70,
          offsetY: 20,
          getContent (e) {
            const outDiv = document.createElement('div')
            outDiv.style.width = '180px'
            // e.item._cfg.id== 'node1'?
  • 人员ID: ${e.item.getModel().id}
  • :
  • 标签ID: ${e.item.getModel().id}
  • if(e.item._cfg.id== 'node1'){ outDiv.innerHTML = `
    • 人员名称: ${e.item.getModel().label}
    `
    return outDiv }else { outDiv.innerHTML = `
    • 标签名称: ${e.item.getModel().label}
    • 标签ID: ${e.item.getModel().id}
    `
    return outDiv } }, itemTypes: ['node'] }) // todo 初始化画布宽高为div parentContent 的宽度和高度 this.canvasWidth = this.$refs.parentContent.clientWidth this.canvasHeight = this.$refs.parentContent.clientHeight this.graph = new G6.Graph({ container: 'container', width: this.canvasWidth, height: this.canvasHeight, linkCenter: true, plugins: [tooltip], // 配置 Tooltip 插件 modes: { default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'activate-relations'] // 允许拖拽画布、放缩画布、拖拽节点、设置高亮 }, layout: { type: 'force', preventOverlap: true, // 防止节点重叠 // 防碰撞必须设置nodeSize或size,否则不生效,由于节点的size设置了40,虽然节点不碰撞了,但是节点之间的距离很近,label几乎都挤在一起,所以又重新设置了大一点的nodeSize,这样效果会好很多 nodeSize: 120, linkDistance: 90 // 指定边距离为150 }, defaultNode: { // 节点样式修改 type: 'image', // 设置节点为图片 size: [40, 40], // 节点大小 labelCfg: { // 修改节点label样式 style: { fill: '#5B8FF9', // 字体颜色 fontSize: 14 // 字体大小 } } }, defaultEdge: { style: { lineWidth: 2, // 线宽 stroke: '#778899', // 线的颜色 endArrow: { // 设置终点箭头 path: G6.Arrow.vee(5, 20, 15), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) d: 15 } } } // 设置节点高亮样式 // edgeStateStyles: { // highlight: { // stroke: '#32dadd' // } // } }) /** * 鼠标移入时,设置节点连线高亮 * */ // const that = this // 改变this指向 // this.graph.on('node:mouseenter', function (e) { // let item = e.item // that.graph.setAutoPaint(false) // that.graph.getNodes().forEach(function (node) { // that.graph.clearItemStates(node) // that.graph.setItemState(node, 'dark', true) // }) // that.graph.setItemState(item, 'dark', false) // that.graph.setItemState(item, 'highlight', true) // that.graph.getEdges().forEach(function (edge) { // if (edge.getSource() === item) { // that.graph.setItemState(edge.getTarget(), 'dark', false) // that.graph.setItemState(edge.getTarget(), 'highlight', true) // that.graph.setItemState(edge, 'highlight', true) // edge.toFront() // } else if (edge.getTarget() === item) { // that.graph.setItemState(edge.getSource(), 'dark', false) // that.graph.setItemState(edge.getSource(), 'highlight', true) // that.graph.setItemState(edge, 'highlight', true) // edge.toFront() // } else { // that.graph.setItemState(edge, 'highlight', false) // } // }) // that.graph.paint() // that.graph.setAutoPaint(true) // }) /** * 鼠标移出时,删除节点连线高亮 * */ // this.graph.on('node:mouseleave', this.clearAllStats) this.graph.on('node:click', (e) => { const nodeItem = e.item // 获取被点击的节点元素对象 console.log(nodeItem) console.log('单击', nodeItem._cfg) // 如果点击的是人员就开始添加 if(nodeItem._cfg.id== 'node1'){ this.visible = true } if(nodeItem._cfg.id !== 'node1'){ this.remove(nodeItem._cfg.id) } }) // 接收数据并渲染 this.graph.data(this.data) this.graph.render() console.log(this.data.nodes) console.log(this.data.nodes[0]) const el = this.graph.findById(this.data.nodes[0]?.id) console.log('model', el) // // el._cfg.model.label = '张三' el._cfg.model.label = this.ryname // el._cfg.model.ip = '192.168.....' // el._cfg.model.status = 4 el._cfg.model.img = '/portrait.png' // el._cfg.model.img = 'https://img2.baidu.com/it/u=617857580,3197175121&fm=253&fmt=auto&app=138&f=JPEG?w=260&h=260' this.graph.refreshItem(el) // 双击节点 this.graph.on('node:dblclick', (e) => { // 先将所有当前是 click 状态的节点置为非 click 状态 const clickNodes = this.graph.findAllByState('node', 'dblclick') clickNodes.forEach((cn) => { this.graph.setItemState(cn, 'dblclick', false) }) const nodeItem = e.item // 获取被点击的节点元素对象 console.log('双击', nodeItem._cfg) this.graph.setItemState(nodeItem, 'dblclick', true) // 设置当前节点的 click 状态为 true }) }, /** * 清除节点连线高亮状态 */ clearAllStats () { const that = this this.graph.setAutoPaint(false) this.graph.getNodes().forEach(function (node) { that.graph.clearItemStates(node) }) this.graph.getEdges().forEach(function (edge) { that.graph.clearItemStates(edge) }) this.graph.paint() this.graph.setAutoPaint(true) } } }
    script> <style> .parentContent { width: 100%; height: 100%; position: absolute; left: 0; top: 0; right: 0; bottom: 0; } #nodeDetails { list-style: none; } #nodeDetails>li { padding:5px 0; text-align: left; } style>

    五、效果展示:
    AntV G6 组织图使用(后端渲染数据)_第1张图片
    AntV G6 组织图使用(后端渲染数据)_第2张图片
    AntV G6 组织图使用(后端渲染数据)_第3张图片

    你已经成功了,撒花。
    今天的分享到此结束,欢迎小伙伴们一起交流

    你可能感兴趣的:(canavas,AntV,G6,使用,前端)