el-tree 自定义连线及图标

 实现的效果如上图

  1. 前提:这个方法是通过格式化 tree 的数据并且通过修改 el-tree 的 CSS 样式来实现连线和自定义图标的效果。
  2. 格式化实在组件当中做的,不会影响原数据。
  3. 缺点很明显,就是通过CSS 实现,没有办法满足拖拽的需求,连线会混乱。这里有一个思路是当拖拽完成之后,可以请求后端接口(前端处理比较麻烦),改变 tree 的数据,重新渲染。
  4. 第三点可参考:https://blog.csdn.net/qq_27331631/article/details/107486812
  5. 这里如果不介意用 jQuery,可以使用 zTree 来实现

代码说明及运行(可以直接拉到本地运行之后再看)

  • vue2 结构的项目
  • 引入 elementUI

APP.vue 文件可以直接替换

在同级创建 components 文件夹引入 lineTree

代码如下



<template>
  <div id="app">
    <div class="ibox">
      <line-tree
        :treeData="masterTreeData"
        node-key="emapId"
        :expand-on-click-node="false"
        :default-expand-all="true"
        :indent="48"
        @node-click="handleNodeClick"
        :highlight-current="true"
        :props="defaultProps"
      >
      </line-tree>
    </div>
  </div>
</template>
    
<script>
import lineTree from "./components/lineTree.vue";
export default {
  name: "App",
  components: {
    lineTree,
  },
  data() {
    return {
      masterTreeData: [
        {
          level: 1,
          name: "地图一1111111111111111111111111111111111111",
          child: [
            {
              level: 2,
              name: "21321312111111111111111111111",
              child: [
                {
                  level: 3,
                  name: "213213121111111111111111111111111111",
                },
                {
                  level: 3,
                  name: "qweqweqe",
                  child: [
                    {
                      level: 4,
                      name: "第四层",
                    },
                    {
                      level: 4,
                      name: "第四层",
                      child: [
                        {
                          name: "第五层",
                        },
                      ],
                    },
                  ],
                },
              ],
            },
          ],
        },
        {
          level: 1,
          name: "地图一1111111111111111111111111111111111111",
          child: [
            {
              level: 2,
              name: "21321312111111111111111111111",
              child: [
                {
                  level: 3,
                  name: "213213121111111111111111111111111111",
                },
                {
                  level: 3,
                  name: "qweqweqe",
                  child: [
                    {
                      level: 4,
                      name: "第四层",
                    },
                    {
                      level: 4,
                      name: "第四层",
                      child: [
                        {
                          name: "第五层",
                        },
                      ],
                    },
                  ],
                },
              ],
            },
            {
              level: 2,
              name: "qweqweqe",
              child: [
                {
                  level: 3,
                  name: "21321312",
                },
                {
                  level: 3,
                  name: "qweqweqe",
                },
              ],
            },
          ],
        },
      ],
      defaultProps: {
        label: "name",
        children: "child",
      }, //树型列表的prop值
    };
  },
  methods: {
    handleNodeClick(data, node, ele) {
      console.log("点击节点", data, node, ele);
    },
  },
  mounted() {},
};
</script>
    
<style lang="less">
html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}

* {
  box-sizing: border-box;
}

#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* text-align: center; */
  color: #2c3e50;
  /* margin-top: 60px; */
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  position: relative;
  box-sizing: border-box;
}
.ibox {
  width: 500px;
  height: 600px;
  margin: 0 auto;
  padding-top: 10px;
  overflow-y: auto;
}
</style>
    

<template>
    <div class="box">
        <el-tree ref="masterMapTree"
            class="masterMap_tree tree"
            :data="masterTreeData"
            v-bind="$attrs"
            v-on="$listeners">
                <template slot-scope="{ node }">
                <span :title="node.label" 
                class="custom-node" 
                :class="[{ 'title-indent': node.level == maxLevel,'is-end':node.end}]"
                :style="calcwidth(node.level)" >
                    <span class="label-icon" :class="[
                        { 'line-one': node.level == 1  },
                        { 'line-two': node.level != 1 && node.level != maxLevel },
                        { 'line-three': node.level == maxLevel },
                        {'no-expand': !node.data.child || !node.expanded}
                    ]"></span>
                    <!-- <span v-for=" item in node.data.lineCount" :key=" item " class="line-style" :style=" node.level == 2 ? { left: '-38PX'} : {left:'-'+(48*(item-1) + 10)+'PX' }"></span> -->
                    <span v-for=" (item, index) in node.data.lineCount" :key=" item " class="line-style" :class="{ 'line-style-end': index == 0 && node.data.end}" :style=" node.level == maxLevel ? {left:'-'+(48*(item-1) + 10)+'PX' } : { left: '-'+(48*(item-1) + 38)+'PX'}"></span>
                    <span class="label-title" >{{ node.label }}</span> 
                </span>
            </template>
        </el-tree>
    </div>
</template>

<script>
export default {
   props:{
    treeData:{
        type:Array,
        default:() => []
    }
   },
   data() {
      return {
        masterTreeData:[],
        maxLevel:0,
      }
   },
   watch:{
        treeData:{
            deep:true,
            immediate:true,
            handler(newVal){
                let data = JSON.parse(JSON.stringify(newVal))
                this.masterTreeData =  this.formatTree({ data ,lastCount: [],lastEnd:true,level:1 })
            }
        }
   },
   created(){
   },
   computed:{
   },
   methods:{
        formatTree( {data, lastCount,lastEnd, level}){
            if( data && Array.isArray(data) && data.length >0 ){
                // 计算最大层级
                if(level > this.maxLevel) this.maxLevel = level
                for (let i = 0; i < data.length; i++) {
                    let it = data[i]
                    it.level = level // 层级
                    // lineCount: 维护 node 节点的侧边线数组,长度表示个数,数字用于计算侧边线离隙长度
                    it.lineCount =  (level == 1) ? [] :  ( (level == 2) ? [1] : lastCount)
                    if( level != 1 && level != 2 ){
                        it.lineCount = lastCount.map(ele => ele+1)
                        if( lastEnd ){
                            it.lineCount[0] = 1
                        }else{
                            it.lineCount.unshift(1)
                        }
                    }  
                    // 是否当前层级最后一个
                    it.end =  (i == data.length - 1) ? true :  false
                    // 递归处理
                    if(it.child && Array.isArray(it.child) && it.child.length > 0){ 
                        it.child = this.formatTree( { data:it.child, lastCount:it.lineCount, lastEnd: it.end, level: it.level + 1 } )
                        }
                }
                return data
            }else{
                return []
            }
        },
        // 根据容器宽度计算 node 标题宽度
        calcwidth(level){
            // let w =  parseInt(28 + 48 * ( level - 1)) 
            let w =  parseInt(28) 
            return { 'max-width': `calc( 100% - 28PX)` }
            if(level == 3){
                return { 'max-width': `calc( 100% - ${w}PX + 10PX)` }
            }
            return { 'max-width': `calc( 100% - ${w}PX - 10PX)` }
        },
   },
}
</script>
<style lang="less" scoped>
.box{
    width: 100%;
    height: 100%;
}
/deep/.masterMap_tree {
    //   height: 100%;
    width: 100%;
    // 侧边框线 left 值计算得出
    .line-style {
        position: absolute;
        height: 40PX;
        width: 0;
        border-left: 1px dashed #9a9a9a;
        top: calc( -20PX + 10PX );
    }
    .line-style-end {
        height: 20PX;
    }
    // 一类横纵线
    .line-one { 
        position: relative;
        &::before{
            content: '';
            height: calc(20PX - 10PX);
            width: 0;
            border-left: 1px dashed #9a9a9a;
            position: absolute;
            top: 100%;
            left: 50%;
        }
        &::after{
            content: '';
            height: 0;
            width: 8PX;
            border-top: 1px dashed #9a9a9a;
            position: absolute;
            top: 50%;
            left: -8PX;
        }
    }
    // 二类横纵线
    .line-two { 
        position: relative;
        &::before{
            content: '';
            height: calc(20PX - 10PX);
            width: 0;
            border-left: 1px dashed #9a9a9a;
            position: absolute;
            top: 100%;
            left: 50%;
        }
        &::after{
            content: '';
            height: 0;
            width: 38PX;
            border-top: 1px dashed #9a9a9a;
            position: absolute;
            top: 50%;
            left: -38PX;
        }
    }
    // 三类横纵线
    .line-three { 
        position: relative;
        &::after{
            content: '';
            height: 0;
            width: 8PX;
            border-top: 1px dashed #9a9a9a;
            position: absolute;
            top: 50%;
            left: -8PX;
        }
    }

    .no-expand{
        &::before{
            content: '';
            height: 0;
            width: 0;
        }
    }

    .custom-node {
        position: relative;
        top:0;
        left:0;
        margin-left: 8PX;
        overflow: visible!important;
        // white-space: nowrap;
        max-width: 100%;
        display: inline-block;
        // text-overflow: ellipsis;
        &.title-indent {
            margin-left: -20PX!important;
        }
    }
    .label-icon{
        display: inline-block;
        vertical-align: middle;
        width: 20PX;
        height: 20PX;
        background-color: black;
        background: url(../assets/icon.png) no-repeat;
        background-size: 100% 100%;
    }
    .label-title{
        display: inline-block;
        vertical-align: middle;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        max-width: calc( 100% - 20PX);
    }

    .treeAllCheck {
        height: 30PX;
        line-height: 30PX;

        .lo__checkbox {
            .el-checkbox__inner {
                width: 20PX;
                height: 20PX;
            }

            .el-checkbox__label {
                font-size: 16PX;
            }

            .el-checkbox__inner::after {
                top: 0;
            }
        }
    }

    &.el-tree {
        color: #333;
        // max-height: calc(100% - 0.2PX);
        // max-height: 100%;
        // overflow-y: scroll;
        padding-left: 10PX!important;
    }

    .el-tree-node__label {
        display: inline-block;
        width: calc(100% - 55px);
        font-size: 16PX;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .el-tree-node__content {
        height: 40PX;
        width:100%;
        overflow-x: hidden;
    }

    .el-tree-node:focus>.el-tree-node__content {
        background-color: #fff;
    }

    .el-tree-node__content:hover {
        background-color: #fff1e1 !important;
    }

    .el-tree-node__expand-icon {
        // position: relative;
        z-index: 1;
        // margin-right: 8px;
        padding: 0 !important;
        width: 20PX!important;
        height: 20PX;
        background: #ffe8d8;
        border-radius: 2PX;
        text-align: center;
        transform: none !important;
    }

    .el-icon-caret-right:before {
        content: "+";
        color: #ff954d;
        font-size: 18PX;
        font-weight: 900;
        line-height: 1.1;
    }

    .el-tree-node__expand-icon.expanded:before {
        content: "\2212";
        color: #ff954d;
        font-size: 18PX;
        font-weight: 900;
        line-height: 1.1;
    }

    .el-tree-node__expand-icon.is-leaf {
        background-color: transparent;
    }

    .el-tree-node__expand-icon.is-leaf:before {
        content: "";
    }
    .el-checkbox__input.is-indeterminate .el-checkbox__inner::before {
        -webkit-box-sizing: content-box;
        box-sizing: content-box;
        content: "";
        border: none;
        height: 12PX;
        left: 35%;
        margin-left: -1PX;
        position: absolute;
        top: 1PX;
        -webkit-transform: rotate(45deg) scaleY(1);
        transform: rotate(45deg) scaleY(1);
        width: 5PX;
        -webkit-transition: -webkit-transform 0.15s ease-in 0.05s;
        transition: -webkit-transform 0.15s ease-in 0.05s;
        transition: transform 0.15s ease-in 0.05s;
        transition: transform 0.15s ease-in 0.05s, -webkit-transform 0.15s ease-in 0.05s;
        -webkit-transform-origin: center;
        transform-origin: center;
    }

    // 虚线样式
    .el-tree-node {
        position: relative;
    }

    .el-tree-node__children .is-leaf {
        position: relative;
    }

    .el-tree-node.is-current>.el-tree-node__content {
        background-color: #fff1e1;
    }

    // .el-tree-node__content>.el-tree-node__expand-icon {
    //     margin-left: 0.2PX;
    // }
}
</style>

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值