关于前端:vue项目使用G6集成思维导图

64次阅读

共计 5445 个字符,预计需要花费 14 分钟才能阅读完成。

实现如下效果图: 蕴含, 新增, 编辑, 删除性能.

1. 首先装置 G6
2. 创立 vue 页面,
3. 在页面中引入 G6

import G6 from '@antv/g6'
const {Util} = G6

4. 创立页面容器
<div id="myMap" />
5. 在 mounted 中获取容器宽高

this.mindStyle.width = this.$refs.continer.offsetWidth
this.mindStyle.height = this.$refs.continer.offsetHeight

6. 初始化 mindmap

const that = this
const borderStyle = 'height: 42, stroke: #70A2E1, radius: 6, fill: #DCE6F2, lineWidth: 2'
const labelStyle = 'fontSize: 16, marginTop: 13, marginLeft: 16, marginRight:16'
const tipStyle = 'marginTop: -15, stroke: #409EFF, fill: #409EFF, cursor: pointer'
const position = [[0, 0.5], [1, 0.5]]
G6.registerNode(
    'dice-mind-map-root', {jsx: (cfg) => {const width = Util.getTextSize(cfg.label, 16)[0] + 50
            return `
                  <group>
                    <rect draggable="true" style={{width: ${width}, ${borderStyle}}} keyshape>
                      <text style={{${labelStyle} }}>${cfg.label}</text>
                      ${type === 'add' ? '': `<text style={{ marginLeft: ${width - 16}, ${tipStyle}, opacity: ${cfg.hover ? 0.75 : 0} }} action="add">+</text>`}
                    </rect>
                  </group>`
          },
        getAnchorPoints() {return position}
      },
      'single-node')
      G6.registerNode(
        'dice-mind-map-sub', {jsx: (cfg) => {const width = Util.getTextSize(cfg.label, 16)[0] + 50
            return `
                <group>
                  <rect draggable="true" style={{width: ${width},${borderStyle}}} keyshape>
                    <text draggable="true" style={{${labelStyle} }}>${cfg.label}</text>
                    ${type === 'add' ? '': `<text style={{ marginLeft: ${width - 30}, ${tipStyle}, opacity: ${cfg.hover ? 0.75 : 0}, next:'inline'}} action="add">+</text>`}
                    ${type === 'add' ? '': `<text style={{ marginLeft: ${width - 20}, ${tipStyle}, opacity: ${cfg.hover ? 0.75 : 0}, next:'inline'}} action="delete">-</text>`}
                  </rect>
                </group>`
          },
          getAnchorPoints() {return position}
        },
        'single-node'
      )
      G6.registerBehavior('dice-mindmap', {getEvents() {if (type !== 'add') {
            return {
              'node:click': 'clickNode',
              'node:dblclick': 'editNode',
              'node:mouseenter': 'hoverNode',
              'node:mouseleave': 'hoverNodeOut'
            }
          }
        },
        clickNode(evt) {const model = evt.item.get('model')
          const name = evt.target.get('action')
          switch (name) {
            case 'add':
              that.nodeAdd(model, evt)
              break
            case 'delete':
              this.nodeDelete(model, evt)
              break
            case 'edit':
              break
            default:
              return
          }
        },
        editNode(evt) {that.nodeEdit(evt)
        },
        hoverNode(evt) {that.nodeHover(evt, true)
        },
        hoverNodeOut(evt) {that.nodeHover(evt, false)
        }
      })

      this.mindmap = new G6.TreeGraph({
        container: 'myMap',
        width: this.mindStyle.width,
        height: this.mindStyle.height,
        fitView: 'autoSize',
        fitViewPadding: [10, 20],
        layout: {
          type: 'mindmap',
          direction: 'H',
          getHeight: () => {return 40},
          getWidth: (node) => {
            return node.level === 0
              ? Util.getTextSize(node.label, 16)[0] + 20
              : Util.getTextSize(node.label, 12)[0]
          },
          getVGap: () => {return 10},
          getHGap: () => {return 60},
          getSide: (node) => {return node.data.direction}
        },
        // 边的款式
        defaultEdge: {
          type: 'cubic-horizontal',
          style: {
            lineWidth: 2,
            stroke: '#C3D8F2'
          }
        },
        // 文字款式
        defaultNode: {
          labelCfg: {
            style: {
              fill: '#333',
              fontSize: 20
            }
          },
          style: {
            stroke: '#72CC4A',
            width: 150
          }
        },
        minZoom: 0.5,
        modes: {default: ['drag-canvas', 'zoom-canvas', 'dice-mindmap']
        }
      })
      this.mindmap.data(that.dataFormat(this.treeNode))
      this.mindmap.render()

7. 鼠标移入事件, 显示增加删除按钮

nodeHover(evt, status) {
   evt.currentTarget.updateItem(evt.item, {hover: status})
},

8. 节点编辑事件, 双击能够编辑文本内容

nodeEdit(evt) {
      const item = evt.item
      const model = item.get('model')
      const {x, y} = item.calculateBBox()
      const graph = evt.currentTarget
      const realPosition = evt.currentTarget.getClientByPoint(x, y)
      const el = document.createElement('div')
      const fontSizeMap = {
        'dice-mind-map-root': 24,
        'dice-mind-map-sub': 18
      }
      el.style.fontSize = fontSizeMap[model.type] + 'px'
      el.style.position = 'fixed'
      el.style.top = realPosition.y + 'px'
      el.style.left = realPosition.x + 'px'
      el.style.paddingLeft = '12px'
      el.style.transformOrigin = 'top left'
      el.style.transform = `scale(${evt.currentTarget.getZoom()})`
      const input = document.createElement('input')
      input.style.border = 'none'
      input.value = model.label
      input.style.width = Util.getTextSize(model.label, fontSizeMap[model.type])[0] + 15 + 'px'
      input.className = 'dice-input'
      el.className = 'dice-input'
      el.appendChild(input)
      document.body.appendChild(el)
      const destroyEl = () => {document.body.removeChild(el)
      }
      const clickEvt = (event) => {if (!(event.target && event.target.className && event.target.className.includes('dice-input'))) {window.removeEventListener('mousedown', clickEvt)
          window.removeEventListener('scroll', clickEvt)
          graph.updateItem(item, {label: input.value})
          graph.layout(false)
          graph.off('wheelZoom', clickEvt)
          destroyEl()}
      }
      graph.on('wheelZoom', clickEvt)
      window.addEventListener('mousedown', clickEvt)
      window.addEventListener('scroll', clickEvt)
      input.addEventListener('keyup', (event) => {if (event.key === 'Enter') {
          clickEvt({target: {}
          })
        }
      })
    },

9. 节点增加事件

nodeAdd(model, evt) {
      const newId =
          model.id + '-' +
          (((model.children || []).reduce((a, b) => {const num = Number(b.id.split('-').pop())
            return a < num ? num : a
          }, 0) || 0) + 1)
      evt.currentTarget.updateItem(evt.item, {children: (model.children || []).concat([{
          id: newId,
          direction: newId.charCodeAt(newId.length - 1) % 2 === 0 ? 'right' : 'left',
          label: '子节点',
          type: 'dice-mind-map-sub',
          color: model.color
        }])
      })
      evt.currentTarget.layout(false)
    },

10. 节点删除事件

nodeDelete(model, evt) {const parent = evt.item.get('parent')
      evt.currentTarget.updateItem(parent, {children: (parent.get('model').children || []).filter((e) => e.id !== model.id)
      })
      evt.currentTarget.layout(false)
    },

11. 数据格式化

dataFormat(data) {const changeData = (d, level = 0) => {
        const data = {...d}
        switch (level) {
          case 0:
            data.type = 'dice-mind-map-root'
            break
          case 1:
            data.type = 'dice-mind-map-sub'
            break
          default:
            data.type = 'dice-mind-map-sub'
            break
        }
        data.hover = false
        if (level === 1 && !d.direction) {if (!d.direction) {data.direction = d.id.charCodeAt(d.id.length - 1) % 2 === 0 ? 'right' : 'left'
          }
        }

        if (d.children) {data.children = d.children.map((child) => changeData(child, level + 1, data.color))
        }
        return data
      }
      return changeData(data)
    },

12. 切换显示时, 先销毁
this.mindmap.destroy()

13. 向后盾提交数据
this.mindmap.getNodes()
查看所有节点信息
this.mindmap.getNodes()[0]._cfg.model
以后思维导图的树形构造
.

正文完
 0