//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;
}

京公网安备 11010502036488号