用 vue3 编写一个节点管理

用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>

猜你喜欢

转载自blog.csdn.net/m0_65121454/article/details/132740344
今日推荐