递归树+添加,删除,修改功能

递归树形组件 ,带添加修改删除功能,并且当前选中树颜色高亮

展示出来样例

在这里插入图片描述

子组件 封装树形组件
<template>
    <table>
        <tr>
            <td :colspan="hasChild ? model.children.length * 2 : 1" :class="{extend: hasChild && model.extend}">
                <div :class="{card:true,select:nodeParamsId===model.resId}">
                    <div class="title">{{model.resName}}</div>
                    <div class="body">
                        {{model.message}}
                    </div>
                    <div class="footer">
                        <a href="javascript:;" @click="$emit('on-add', model)">添加</a>
                        <a href="javascript:;" @click="$emit('on-update', model)" v-if="model.type!=='root'">修改</a>
                        <a href="javascript:;" @click="$emit('on-remove', model)" v-if="model.type!=='root'">删除</a>
                    </div>
                </div>
                <div class="extend_handle" v-if="hasChild" @click="toggleExtend()">{{model.extend?'展开': '隐藏'}}</div>
            </td>
        </tr>
        <tr v-if="hasChild && model.extend">
            <td v-for="(item, index) in model.children" :key="index" colspan="2" class="child">
                <TreeChart :nodeParamsId="nodeParamsId" :model="item" @on-add="$emit('on-add', $event)" @on-update="$emit('on-update', $event)" @on-remove="$emit('on-remove', $event)"   />
            </td>
        </tr>
    </table>
</template>

<script>
export default {
    name: 'TreeChart',
    props: ['model','nodeParamsId'],
    computed: {
        hasChild(){
            return this.model.children && this.model.children.length
        }
    },
    methods: {
        toggleExtend() {
            this.model.extend = !this.model.extend
        }
    }
}
</script>

<style lang="less">
.select{
    background: gold !important;
}
.card{
    background: rgb(227, 236, 247);
    border: 2px solid #ffffff;
    border-radius: 5px;
    margin: 0 auto;
    width: 200px;
    .title{
        height: 18px;
        padding: 10px 0;
        font-size: 12px;
    }

    .body{
        height: 100px;
        background: #ffffff;
        width: auto;
        margin: 0 15px;
    }

    .footer{
        display: flex;
        margin: 5px 15px;
        justify-content: space-around;
        a{
            font-size: 12px;
            color: rgb(20, 126, 192);
            text-decoration: none;
        }
    }
}
table{
    border-collapse: separate!important;
    border-spacing: 0!important;
    td{
        position: relative; 
        vertical-align: top;
        padding:0 0 50px 0;
        text-align: center;
        &.extend{
            &::after{
                content: "";
                position: absolute;
                left: 50%;
                bottom: 18px;
                height: 30px;
                border-left: 2px dashed rgb(159, 186, 202);
                transform: translate3d(-1px, 0, 0);
            }
        }
        &.child{
            &::before{
                content: "";
                position: absolute;
                left:50%;
                bottom:100%;
                height:15px;
                border-left:2px dashed rgb(159, 186, 202);
                transform: translate3d(-1px,0,0)
            }
            // 横线
            &::after{
                content: "";
                position: absolute;
                left:0;
                right:0;
                top:-15px;
                border-top:2px dashed rgb(159, 186, 202);
            }
            &:first-child:before,
            &:last-child:before{
                display: none;
            }
            &:first-child:after{
                left:50%;
                height:15px;
                border:2px dashed;
                border-color:rgb(159, 186, 202) transparent transparent rgb(159, 186, 202);
                border-radius: 6px 0 0 0;
                transform: translate3d(1px,0,0)
            }
            &:last-child:after{
                right:50%;
                height:15px; 
                border:2px dashed;
                border-color:rgb(159, 186, 202) rgb(159, 186, 202) transparent transparent;
                border-radius: 0 6px 0 0;
                transform: translate3d(-1px,0,0)
            }
            &:first-child.child:last-child::after{
                left:auto;
                border-radius: 0;
                border-color:transparent rgb(159, 186, 202) transparent transparent;
                transform: translate3d(1px,0,0)
            }
        }
    }

}
</style>
父组件引用
<template>
  <div id="app">
       <el-button type="text" @click="btn">添加message</el-button>
      <el-dialog
        title="提示"
        :visible.sync="dialogVisible"
        width="30%"
      >
          <el-form  :model="form" class="demo-form-inline">
        <el-form-item label="审批人" label-width="80px">
          <el-input v-model="form.name" placeholder="审批人"></el-input>
        </el-form-item>
        <el-form-item label="活动区域" label-width="80px">
          <el-select v-model="form.message" placeholder="活动区域">
            <el-option label="区域一" value="shanghai"></el-option>
            <el-option label="区域二" value="beijing"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="confirm(form)">确 定</el-button>
        </span>
      </el-dialog>
    <TreeChart :model="tree" @on-add="add" @on-update="update" @on-remove="remove" :nodeParamsId="nodeParams.resId"/>
    </div>
</template>

<script>
import TreeChart from '../components/TreeChart'
export default {
  components: {
    TreeChart
  },
  data(){
      return {
          tree:{
              resId:1,
              type:'root',
              hasChild:['22'],
              children:[],
              resName:'全量客户',
              message:'客户数',
              extend:true,
          },
          id:2,
          dialogVisible:false,
          form:{
            name:'',
            message:""
          },
          isNullNode:false,//是否是空白节点
          type:null,//1是新增  2是修改  3是删除
          // 当前节点的id和父id
          nodeParams:{
            resId:null,
            parentId:null,
          },//当前node
          activeArr:[]
      }
  },
  watch:{
    type(newval){
      console.log('type',newval)
    }
  },
  methods:{
    // 新增节点
    add(node){
      if(this.isNullNode){
        this.$message.warning('是空白节点,请先编辑')
        return 
      }
      // 新增为1
      this.type=1
      // 新节点
      const newNode = {
        resName: '',
        resId: this.id++,//当前节点id
        message: '',
        extend: true,
        children: [],//有children才可以添加,没有的话,添加不进去
        parentId:node.resId,//父节点ID
        type:'normal'//节点类型
      }
      // 把新节点push到当前的children里
      node.children.push(newNode)
      // 新增为空白节点
      this.isNullNode=true,
      // 每次新增push进一个当前新增的数组
      this.activeArr.push(newNode.resId)
      // 设置当前可编辑节点的id
      this.nodeParams={
        resId:newNode.resId,
        parentId:newNode.parentId
      }
    },
    // 更新节点
    update(node){
      if(this.isNullNode){
        this.$message.warning('请先完成节点编辑')
        return 
      }
      this.nodeParams={
        resId:node.resId,
        parentId:node.parentId
      }
      this.type=2
      this.dialogVisible=true
    },
    // 移除节点
    remove(node){
      if (node.type == 'root'){
        this.$message.warning('根节点不能删除')
        return 
      }
      // node下面有子节点,不能删除
      if(node.children.length>0){
        this.$message.warning('该节点下有子节点,不可删除')
        return
      }
      /* 重要一步 */
      let ids=''
      this.activeArr.splice(this.activeArr.indexOf(node.resId),1)//删除当前选中的对象
      ids=this.activeArr[this.activeArr.length -1]//最后一个对象
      const deepSearch = (tree) => {
        for (let i = tree.length - 1; i >= 0; i--) {
          if(tree[i].resId == ids){//删除的是数组的最后一个对象(已经删除了当前的)
            this.nodeParams={
              resId:tree[i].resId,
              parentId:tree[i].parentId
            }
          }
          if (tree[i].resId == node.resId){
            if(tree[i].message==''){
              this.type=null
              this.isNullNode=false
            }
            tree.splice(i, 1)//删除id一致的节点
          } else if(tree[i].children){
            deepSearch(tree[i].children)
          }
        }
      }
      deepSearch(this.tree.children)
      // 对应删除树的节点
    },
    // 确认弹框
    confirm(form){
      if(this.type==null){
        this.$message.warning('请在右侧添加节点')
        return 
      }
      const deepSearch = (tree) => {
          //树id 和当前可修改id一致  进行节点修改
          if (tree.resId == this.nodeParams.resId){
            tree.message=form.message
            tree.resName=form.name
          } else if(tree.children&&tree.children.length){
            // childer里面继续找
            tree.children.forEach(el => {
              deepSearch(el)
            });
        }
      }
      deepSearch(this.tree)
      this.dialogVisible=false
      this.isNullNode=false
      this.type=2
    },
    // 点击弹框
    btn(){
      if(this.tree.children.length==0){
        this.$message.warning('请添加节点')
        return 
      }
      if(this.type==null){
        this.$message.warning('请添加节点')
        return
      }
      this.dialogVisible=true
    }
  },
}
</script>

<style lang="less">

</style>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值