rpa3U5sLx

23朵毒蘑菇

vue的一个简单的树形结构展示组件。

原文章迁移

vue|树形组件

2021-09-14 11:00:10 已有版本 1 个 show:0.56kTYPE: blog

效果图

一篇文字

源码

<template>
<div
    class="cp_container tree-container"
    :style="{
        flexDirection:direction=='level'?'row':'column',
        '--my-gap':gap || '15px',
    }">
    <div
        v-if="!isStart && parentNode.child && parentNode.child.length>1" 
        :class="{
            location_el:true,
            'start_location_el-vertical':direction=='vertical',
            'start_location_el-level':direction=='level',
        }">
    </div>
    <div
        v-if="!isEnd && parentNode.child && parentNode.child.length>1" 
        :class="{
            location_el:true,
            'end_location_el-vertical':direction=='vertical',
            'end_location_el-level':direction=='level',
        }">
    </div>
    <div 
        class="name tree-name-container"
        @click="onItem(nodeData,parentNode,zIndex)">
        <svg-icon :icon-class="nodeData.icon" v-if="nodeData.icon"></svg-icon>
        {{nodeData.name}}
        <div class="bt_list">
            <i 
                class="el-icon-plus"
                @click.stop="onAdd(nodeData,parentNode,zIndex)">
            </i>
            <i 
                class="el-icon-minus"
                @click.stop="onDelete(nodeData,parentNode,zIndex)">
            </i>
            <i 
                class="el-icon-edit-outline"
                @click.stop="onUpdate(nodeData,parentNode,zIndex)">
            </i>
        </div>
        <div
            v-if="parentNode.name" 
            :class="{
                center_el:true,
                'start_center_el-vertical':direction=='vertical',
                'start_center_el-level':direction=='level',
            }">
            <span></span>
        </div>
        <div
            v-if="nodeData.child && nodeData.child.length>0" 
            :class="{
                center_el:true,
                'end_center_el-vertical':direction=='vertical',
                'end_center_el-level':direction=='level',
            }">
            <span></span>
        </div>
    </div>
    <div
        class="container tree-childs-container"
        v-if="nodeData.child && nodeData.child.length>0"
        :style="{
            gridTemplateColumns:direction!='level'?`repeat(${nodeData.child.length},max-content)`:'',
            gridTemplateRows:direction=='level'?`repeat(${nodeData.child.length},max-content)`:'',
            paddingTop:direction!='level'?'calc(var(--my-gap) * 2)':'',
            paddingLeft:direction=='level'?'calc(var(--my-gap) * 2)':'',
        }">
        <MyTree
            v-for="(item,index) in nodeData.child"
            :key="index"
            :zIndex="zIndex+1"
            :direction="direction"
            :parentNode="nodeData"
            :nodeData='item'
            :isStart='index == 0 && nodeData.child.length>1'
            :isEnd='index == (nodeData.child.length - 1) && nodeData.child.length>1'
            @onClick="onItem($event.nodeData,$event.parentNode,$event.zIndex)"
            @onAdd="onAdd($event.nodeData,$event.parentNode,$event.zIndex)"
            @onUpdate="onUpdate($event.nodeData,$event.parentNode,$event.zIndex)"
            @onDelete="onDelete($event.nodeData,$event.parentNode,$event.zIndex)">
        </MyTree>
    </div>
</div>
</template>
<script>
//部门树结构组件  提供css让外部修改
export default {
    name:'MyTree',
    props:{
        zIndex:{  //结构层级
            type:Number,
            default:0,
        },
        direction:{  //结构方向
            type:String,
            default:'vertical',
        },
        nodeData:{  //节点数据对象
            type:Object,
            default:()=>{
                return {};
            },
        },
        parentNode:{  //父节点
            type:Object,
            default:()=>{
                return {};
            },
        },
        isStart:{  //是左侧
            type:Boolean,
            default:false,
        },
        isEnd:{  //是右侧
            type:Boolean,
            default:false,
        },
        gap:{  //间隔
            type:String,
            default:'15px',
        },
    },
    data() {
        return {
        };
    },
    methods:{
        onItem(nodeData,parentNode,zIndex){  //点击事件
            this.$emit('onClick',{
                parentNode,
                nodeData,
                zIndex,
            });
        },
        onAdd(nodeData,parentNode,zIndex){
            this.$emit('onAdd',{
                parentNode,
                nodeData,
                zIndex,
            });
        },
        onUpdate(nodeData,parentNode,zIndex){
            this.$emit('onUpdate',{
                parentNode,
                nodeData,
                zIndex,
            });
        },
        onDelete(nodeData,parentNode,zIndex){
            this.$emit('onDelete',{
                nodeData,
                parentNode,
                zIndex,
            });
        },
    },
};
</script>
<style scoped lang="scss">
.cp_container{
    width: fit-content;
    height: fit-content;
    display: flex;
    align-items: center;
    position: relative;
    --border-color: #ccc;  //边框颜色
    >.location_el{
        position: absolute;
    }
    >.start_location_el-vertical{
        height: var(--my-gap);
        width: calc(50% + var(--my-gap));
        left: calc(0px - var(--my-gap));
        top: calc(0px - var(--my-gap));
        border-top: 1px solid var(--border-color);
    }
    >.start_location_el-level{
        width: var(--my-gap);
        height: calc(50% + var(--my-gap));
        left: calc(0px - var(--my-gap));
        top: calc(0px - var(--my-gap));
        border-left: 1px solid var(--border-color);
    }
    >.end_location_el-vertical{
        height: var(--my-gap);
        width: calc(50% + var(--my-gap));
        right: calc(0px - var(--my-gap));
        top: calc(0px - var(--my-gap));
        border-top: 1px solid var(--border-color);
    }
    >.end_location_el-level{
        width: var(--my-gap);
        height: calc(50% + var(--my-gap));
        left: calc(0px - var(--my-gap));
        bottom: calc(0px - var(--my-gap));
        border-left: 1px solid var(--border-color);
    }
    >.name{
        max-width: 400px;
        padding:15px;
        border-radius: 3px;
        font-size: 20px;
        box-sizing: border-box;
        background-color: rgb(255, 255, 255);
        cursor: pointer;
        display: flex;
        justify-content: center;
        align-items: center;
        position: relative;
        box-shadow: 0 1px 4px #00152936;
        color: #3e4761;
        >svg{
            margin-right: 5px;
            min-width: 20px;
            width: 20px;
            height: 20px;
        }
        &:hover{
            .bt_list{
                opacity: 1;
            }
        }
        >.bt_list{
            opacity: 0;
            display: grid;
            position: absolute;
            height: 30px;
            padding: 0 5px;
            border-radius: 2px;
            background-color: rgb(255, 255, 255);
            bottom: -35px;
            border: 1px solid #dadada;
            box-sizing: border-box;
            z-index: 999;
            grid-gap: 5px;
            grid-template-columns: 1fr 1fr 1fr;
            align-items: center;
            transition: all 0.2s;
            box-shadow: 0 1px 4px #00152936;
            &:hover{
                opacity: 1;
            }
        }
        //center 居中的四个边框
        >.center_el{
            position: absolute;
            display: flex;
            justify-content: center;
            align-items: center;
            pointer-events: none;
            >span{
                background-color: var(--border-color);
            }
        }
        >.start_center_el-vertical{
            width: 100%;
            height: var(--my-gap);
            right: 0;
            top: calc(0px - var(--my-gap));
            >span{
                width: 1px;
                height: 100%;
            }
        }
        >.start_center_el-level{
            width: var(--my-gap);
            height: 100%;
            left: calc(0px - var(--my-gap));
            top: 0;
            >span{
                height: 1px;
                width: 100%;
            }
        }
        >.end_center_el-vertical{
            width: 100%;
            height: var(--my-gap);
            right: 0;
            bottom: calc(0px - var(--my-gap));
            >span{
                width: 1px;
                height: 100%;
            }
        }
        >.end_center_el-level{
            width: var(--my-gap);
            height: 100%;
            right: calc(0px - var(--my-gap));
            top: 0;
            >span{
                height: 1px;
                width: 100%;
            }
        }
    }
    >.container{
        display: grid;
        grid-gap: var(--my-gap);
        justify-content: center;
        width: fit-content;
    }
}
</style>

使用方法

<MyTree
        :nodeData="treeData"
        direction="level"
        @onAdd="onAdd"
        @onUpdate="onUpdate"
        @onDelete="onDelete">
    </MyTree>
    
    //数据
    treeData:{
        name:'客户申报',
        child:[],
    },

默认横向排列,非常简单。
其中 svg-icon为我自定义的全局组件,不用直接删除就是了。