用vue3编写一个节点管理
1、初始化状态就一个节点
2、点击节点的白色圆圈,就给该节点生成一个子节点
3、节点和子节点之间用线连接
4、双击该节点,则收起该节点的所有子节点
5、再次双击该节点,则打开该节点的所有子节点
Node.vue :
<template>
<div class="node" :id="nodeObj.id">
<div class="circle" @click="addNode"></div>
<div class="content" @dblclick="toggleChildren">{
{ nodeObj.name }}{
{ nodeObj.showChildren ? '<' : ">" }}</div>
<div v-if="nodeObj.children.length && nodeObj.showChildren" :style="{ left: childrenLeft }"
class="children">
<div v-for="child in nodeObj.children" :key="child.id" class="child">
<div class="line"></div>
<Node :node="child"></Node>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Node',
data() {
return {
nodeObj: null
}
},
props: {
node: {
type: Object,
required: true
}
},
created() {
this.nodeObj = this.node
},
computed: {
childrenLeft() {
return (this.nodeObj.storey + 1) * 140 + 'px'
}
},
methods: {
addNode() {
if (!this.nodeObj.showChildren) return
const id = this.nodeObj.storey + 1 + '-' + this.nodeObj.children.length
const newNode = {
id,
name: '子节点',
children: [],
showChildren: true,
storey: this.nodeObj.storey + 1,
};
this.nodeObj.children.push(newNode);
this.$nextTick(() => {
})
},
toggleChildren() {
this.nodeObj.showChildren = !this.nodeObj.showChildren;
}
},
updated() {
const par = document.getElementById(this.nodeObj.id)
this.nodeObj.children.forEach((item) => {
const sub = document.getElementById(item.id)
if (!sub) return
const parLeft = par.getBoundingClientRect().left + 80
const parTop = par.getBoundingClientRect().top + 25
const subLeft = sub.getBoundingClientRect().left
const subTop = sub.getBoundingClientRect().top + 25
const dt = Math.abs(subTop - parTop)
const dl = Math.abs(subLeft - parLeft)
const lineDom = sub.previousElementSibling
// 子节点在父节点的下方
if (subTop > parTop) {
const angle = Math.atan(dt / dl) / Math.PI * 180
lineDom.style.transform = `rotate(${angle}deg)`
// 子节点在父节点的上方
} else if (subTop < parTop) {
const angle = Math.atan(dt / dl) / Math.PI * 180
lineDom.style.transform = `rotate(-${angle}deg)`
} else {
lineDom.style.transform = `rotate(0deg)`
}
const lineWidth = Math.sqrt(Math.pow(dt, 2) + Math.pow(dl, 2)) - 8
lineDom.style.left = `-${lineWidth}px`
lineDom.style.width = `${lineWidth}px`
lineDom.style.transformOrigin = `${lineWidth}px center`
})
}
};
</script>
<style scoped>
.node {
width: 80px;
height: 50px;
position: relative;
display: inline-block;
}
.circle {
width: 20px;
height: 20px;
border-radius: 50%;
position: absolute;
background-color: white;
box-sizing: border-box;
border: 1px solid black;
cursor: pointer;
left: calc(100% - 10px);
top: calc(50% - 10px);
}
.content {
width: 80px;
height: 50px;
font-size: 18px;
line-height: 50px;
text-align: left;
text-indent: 3px;
background-color: #d8e8e5;
box-sizing: border-box;
border: 1px solid black;
border-radius: 10px;
}
.children {
position: fixed;
height: 100vh;
display: flex;
top: 0;
width: 100px;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
}
.child {
width: 80px;
height: 50px;
position: relative;
}
.line {
position: absolute;
top: calc(50% - 1px);
left: -45px;
width: 45px;
height: 2px;
background-color: black;
transform-origin: 45px center;
}
</style>
App.vue :
<template>
<Node :node="rootNode"></Node>
</template>
<script>
import Node from './components/Node.vue';
export default {
name: 'App',
components: {
Node
},
data() {
return {
rootNode: {
id: '0-0',
storey: 0,
name: '根节点',
children: [],
showChildren: true
}
};
}
};
</script>
<style>
* {
user-select: none;
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-left: 20px;
}
#app {
width: 100%;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: flex-start;
align-items: center;
}
</style>