//App.js
import React, { Component } from 'react';
import './App.css'
class App extends Component {
  constructor(props) {
    super(props)
    this.getNode = this.getNode.bind(this)
    this.drawLine = this.drawLine.bind(this)
    this.addLine = this.addLine.bind(this)
    this.state = {
      //节点树 存储节点的关系
      node: {
        node: {
          text: '根节点',
          level: 0,
          id: 0,
          children: []
        }
      },
      //当前节点
      curNode: null,
      //要绘制边的节点id
      nodeId: [],
      //边的信息
      line: [],
      //父节点与子节点的关系
      parentAndChild: [
        {
          id: 0,
          childrenId: []
        }
      ]
    }
  }
  drawLine() {
    return this.state.line.map(item => {
      let path = `M${item.curX} ${item.curY} L${item.parentX} ${item.parentY} Z`
      return (
        <path d={path} style={{ fill: 'white', stroke: 'red', strokeWidth: 2 }} />
      )
    })
  }


  getNode(root) {
    let node = root.node
    let margin = 10
    let className, x
    if (!node) {
      return
    }
    switch (node.level) {
      case 0:
        className = 'node-0'
        break;
      case 1:
        className = 'node-1'
        x = 80
        break;
      default:
        className = 'node-2'
        x = 100
        break;
    }
    return (
      <div id={node.id} style={{ left: x }} key={node.id} className="flex" >
        <div onClick={this.chooseNode.bind(this, node)} style={{ margin }} suppressContentEditableWarning contentEditable='true' className={className}>{node.text}</div>
        <div>
          {
            node.children ? node.children.map(item => this.getNode(item)) : ''
          }
        </div>
      </div>
    )
  }
  chooseNode(node) {
    this.setState({
      curNode: node
    })
  }
  blur() {
    this.setState({
      curNode: null
    })
  }
  addParentAndChild(arr, parentId, childId) {
    let has = false
    for (let i = 0; i < arr.length; i++) {
      if (arr[i].id == parentId) {
        arr[i].childrenId.push(childId)
      }
      if (arr[i].id == childId) {
        has = true
      }
    }
    if (!has) {
      let obj = {}
      obj.childrenId = []
      obj.id = childId
      arr.push(obj)
    }
  }
  addNode() {
    let curNode = this.state.curNode
    let nodeId = this.state.nodeId
    let parentAndChild = this.state.parentAndChild
    if (!curNode) {
      alert('请选中一个节点')
      return
    }
    let obj = {}
    let level
    if (curNode.level == 2) {
      level = 2
    } else {
      level = curNode.level + 1
    }
    obj.level = level
    obj.text = '新节点'
    obj.id = +new Date()
    obj.children = []
    curNode.children.push({ node: obj })
    nodeId.push(obj.id)
    this.addParentAndChild(parentAndChild, curNode.id, obj.id)
    let state = Object.assign({}, this.state.node, curNode, nodeId, parentAndChild)
    this.setState({
      state
    })
    setTimeout(() => {
      let newLine = []
      this.state.nodeId.forEach(item => this.addLine(item, newLine))
      let line = this.state.line
      this.setState({
        line: newLine
      })
    }, 0);
  }
  addLine(id, newLine) {
    let e = document.getElementById(id)
    if (!e) {
      return
    }
    let parent = e.parentNode.parentNode.children[0]
    let pType = Number(parent.className.split('-')[1])
    let eType = Number(parent.className.split('-')[1])
    let pLeftOffset = 0, pTopOffset = 0, eLeftOffset = 10, eTopOffset = 0
    switch (pType) {
      case 0:
        pLeftOffset = 150
        pTopOffset = 25
        break;
      case 1:
        pLeftOffset = 100
        pTopOffset = 20
      default:
        pLeftOffset = 70
        pTopOffset = 15
        break;
    }
    let obj = {}
    obj.parentX = (parent.getBoundingClientRect().left + pLeftOffset)
    obj.parentY = (parent.getBoundingClientRect().top + pTopOffset)
    obj.curX = e.getBoundingClientRect().left + eLeftOffset //加上margin大小
    eTopOffset = Number(window.getComputedStyle(e).height.split('p')[0])
    obj.curY = (e.getBoundingClientRect().top + eTopOffset / 2) //半个子节点高度
    newLine.push(obj)
  }

  getNodeAndChildren() {
    let curId = this.state.curNode.id
    let parentAndChild = this.state.parentAndChild
    let shouldDeleteNodeId = []
    let nodeId = this.state.nodeId
    for (let i = 0; i < parentAndChild.length; i++) {
      if (parentAndChild[i].id === curId) {
        shouldDeleteNodeId.push(parentAndChild[i].id)
        if (parentAndChild[i].childrenId && parentAndChild[i].childrenId !== 0) {
          parentAndChild[i].childrenId.forEach(item => {
            shouldDeleteNodeId.push(item)
          })
        }
      }
    }
    nodeId = nodeId.filter(key => !shouldDeleteNodeId.includes(key))
    this.setState({
      line: []
    })
    setTimeout(() => {
      let newLine = []
      nodeId.forEach(item => this.addLine(item, newLine))
      let line = this.state.line
      this.setState({
        line: newLine,
        nodeId
      })
    }, 0);
  }

  deleteNode() {
    let curNode = this.state.curNode
    let nodeId = this.state.nodeId
    if (curNode.id == 0) {
      alert('无法删除根节点')
      return
    }
    let node = this.state.node
    //删除边
    this.getNodeAndChildren()
    //删除节点
    let curId = curNode.id
    let jsonStr = JSON.stringify(node, (key, val) => {
      if (key == 'node' && val.id == curId) {
        return undefined
      } else {
        return val
      }
    })
    let n = JSON.parse(jsonStr)
    this.setState({
      node: n
    })
  }
  render() {
    return (
      <>
        <button style={{ position: 'absolute' }} onClick={this.addNode.bind(this)}>增加节点</button>
        <button style={{ position: 'absolute', left: 100 }} onClick={this.deleteNode.bind(this)}>删除节点</button>
        <svg width="100vw" height="100vh" version="1.1">
          {this.drawLine()}
        </svg>
        <div className="container">
          {this.getNode(this.state.node)}
        </div>
      </>
    )
  }
}

export default App;
//App.css
body,
html {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

.container {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.node-0 {
    position: relative;
    width: 150px;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: skyblue;
    color: #fff;
    border-radius: 10px;
}

.node-1 {
    position: relative;
    width: 100px;
    height: 40px;
    text-align: center;
    background-color: violet;
    border-radius: 10px;
    line-height: 40px;
    color: #fff;
}

.node-2 {
    position: relative;
    width: 70px;
    height: 30px;
    border-radius: 8px;
    line-height: 30px;
    text-align: center;
    color: #fff;
    background-color: sandybrown
}

.flex {
    display: flex;
    position: relative;
    align-items: center;
}

.line {
    position: relative;
}

.link {
    fill: none;
    stroke: red;
    stroke-width: 6px;
    cursor: default;
}

图片说明