vue开发带复选框的树组件

编写递归组件tree.vue

<template>
  <div>
    <div :style="{ marginLeft: depth +30+ 'px' }" v-for="(item,index) of list" :key="index">
      <div flex="main:justify cross:center">
        <div class="title">
          <div @click="show(!item.show,item)" style="cursor: pointer">
            <Icon v-if="item.children && item.children.length>0" type="ios-arrow-forward"
                  :class="item.show?'down':'right'" style="vertical-align:middle"/>
            <span v-else style="display: inline-block;width: 14px"></span>
            <img src="../../../assets/2.png" alt="" style="width: 14px;vertical-align:middle">
          </div>
          <div style="cursor: pointer" @click="checkedOpen(!item.checked,item)" :class="item.checked?'checked':''">
            <span>{{item.title}}</span>
          </div>
        </div>
        <div v-if="checkbox" class="selected" style="margin-right: 20px"
             @click="select(item.selected,item.halfSelected,item)"
             :class="item.halfSelected?'halfSelected':''">
          <p :class="item.selected?'allSelected':''"></p>
        </div>
      </div>
      <div v-show="item.show">
        <detail-list :checkbox="checkbox" v-if="item.children && item.children.length>0" :fatherIndex="index"
                     :list="item.children"
                     :depth="depth+1" @selectFatherNode="selectFatherNode"
                     @checkedFatherNode="checkedFatherNode"></detail-list>
      </div>
    </div>
  </div>
</template>

<script>
  import {Icon} from 'view-design'

  export default {
    name: 'DetailList',//递归组件是指组件自身调用自身(被递归组件名)
    props: {
      list: {
        type: Array,
        default() {
          return []
        }
      },
      depth: {
        type: Number,
        default: 0
      },
      fatherIndex: {
        type: Number,
        default: 0
      },
      checkbox: {
        type: Boolean,
        default: false
      }
    },
    computed: {
      // indent() {
      //   return { '': `translate(${this.depth +30}px)` }
      // }
    },
    mounted() {
      console.log("this.fatherIndex", this.fatherIndex, this.list)
    },
    data() {
      return {}
    },
    methods: {
      //扩展
      show(show, node) {
        node.show = show
      },
      //选择
      select(selected, halfSelected, node) {
        node.selected = !selected
        if (node.selected) {
          node.halfSelected = false
        }
        if (node.children && node.children.length > 0) {
          this.selectedChildren(node.children, node.selected, false)
        }
        this.$emit('selectFatherNode', this.fatherIndex, this.list.every((nodes) => {
          return nodes.selected === true
        }), this.list.some((nodes) => {
          return nodes.selected === true
        }) || this.list.some((nodes) => {
          return nodes.halfSelected === true
        }))
        //当前选中节点、当前父节点索引、全选与否、半选与否
        // console.log("this.list.every((node) => { return node.selected === node.selected })",this.list.every((node) => { return node.selected === node.selected }))
      },
      selectedChildren(node, selected, halfSelected) {
        node.forEach(item => {
          item.selected = selected
          item.halfSelected = halfSelected
          if (item.children && item.children.length > 0) {
            this.selectedChildren(item.children, selected, halfSelected)
          }
        })
      },
      // 作为父级节点检查是否需要修改选中状态(仅用于子节点调用)
      selectFatherNode(index, childrenState, halfSelected) {
        if (childrenState) {//父节点全选
          this.list[index].selected = true
          this.list[index].halfSelected = false
          this.$emit('selectFatherNode', this.fatherIndex, this.list.every((nodes) => {
            return nodes.selected === true
          }), this.list.some((nodes) => {
            return nodes.selected === true
          }) || this.list.some((nodes) => {
            return nodes.halfSelected === true
          }))
        } else if (!childrenState && halfSelected) {//父节点半选
          this.list[index].selected = false
          this.list[index].halfSelected = true
          this.$emit('selectFatherNode', this.fatherIndex, this.list.every((nodes) => {
            return nodes.selected === true
          }), this.list.some((nodes) => {
            return nodes.selected === true
          }) || this.list.some((nodes) => {
            return nodes.halfSelected === true
          }))
        } else if (!childrenState && !halfSelected) {//全不选
          this.list[index].selected = false
          this.list[index].halfSelected = false
          this.$emit('selectFatherNode', this.fatherIndex, this.list.every((nodes) => {
            return nodes.selected === true
          }), this.list.some((nodes) => {
            return nodes.selected === true
          }) || this.list.some((nodes) => {
            return nodes.halfSelected === true
          }))
        }
      },
      //选中样式
      checkedOpen(checked, item) {
        this.checkedList(this.list)
        this.$emit('checkedFatherNode')
        item.checked = checked
      },
      checkedList(list) {
        list.forEach(item => {
          item.checked = false
          if (item.children && item.children.length) {
            this.checkedList(item.children)
          }
        })
      },
      checkedFatherNode() {
        this.checkedList(this.list)
        this.$emit('checkedFatherNode')
      }
    },
    components: {
      Icon
    }
  }
</script>
<style scoped>
  .title > div {
    display: inline-block;
  }

  .down {
    transform: rotate(90deg);
    transition: all 0.4s ease;
  }

  .right {
    transition: all 0.4s ease;
  }

  .selected {
    cursor: pointer;
  }

  .checked {
    color: #2d8cf0;
    background: #CBDCF7;
  }

  p {
    display: inline-block;
    width: 16px;
    height: 16px;
    position: relative;
    top: 0;
    left: 0;
    border: 1px solid #dcdee2;
    border-radius: 2px;
    background-color: #fff;
    transition: border-color .2s ease-in-out, background-color .2s ease-in-out, box-shadow .2s ease-in-out;
  }

  p.allSelected {
    border-color: #2d8cf0;
    background-color: #2d8cf0;
  }

  p.allSelected:after {
    content: "";
    display: table;
    width: 4px;
    height: 8px;
    position: absolute;
    top: 2px;
    left: 5px;
    border: 2px solid #fff;
    border-top: 0;
    border-left: 0;
    -webkit-transform: rotate(45deg) scale(1);
    transform: rotate(45deg) scale(1);
    transition: all .2s ease-in-out;
  }

  .halfSelected > p {
    background-color: #2d8cf0;
    border-color: #2d8cf0;
  }

  .halfSelected > p:after {
    content: "";
    width: 10px;
    height: 1px;
    position: absolute;
    border: 2px solid #fff;
    border-top: 0;
    border-left: 0;
    -webkit-transform: scale(1);
    transform: scale(1);
    position: absolute;
    left: 2px;
    top: 6px;
  }
</style>


引用tree.vue

<template>
  <div>
    <div>
      <Input v-if="searchOpen" :maxlength="5" v-model="searchValue" size="large"/>
      <tree :list="list3" :depth="0" :checkbox="checkbox"></tree>
    </div>
  </div>
</template>

<script>
  import tree from './tree'
  import {
    Input
  } from 'view-design'

  export default {
    name: "treeIndex",
    props:{
      // listData:{
      //   type:Array,
      //   default(){
      //     return []
      //   }
      // },
      // checkbox:{
      //   type:Boolean,
      //   default:false
      // }
    },
    components: {
      tree,Input
    },
    data() {
      return {
        checkbox:true,//是否开启复选
        searchOpen:true,//是否开启搜索
        searchValue: '',
        list3: [],
        list2: [],
        list1:  [{//原始数据
          title: "根级菜单",
          show: true,
          children: [{
            title: "1级菜单",
            show: true,
            children: [
              {
                title: "1级菜单-1",
                // checked:true
                children: [
                  {
                    title: "1级菜单-1-1",
                    // checked:true
                  },
                  {
                    title: "1级菜单-1-2",
                    selected: true,
                  }
                ]
              },
              {
                title: "1级菜单-2",
                children: [
                  {
                    title: "1级菜单-2-1",
                    // checked:true
                  },
                  {
                    title: "1级菜单-2-2",
                    selected: true,
                  }
                ]
                // selected: true,
              }
            ]
          },
            {
              title: "3级菜单2级菜单",
              children: [
                {
                  title: "2级菜单-1",
                },
                {
                  title: "2级菜单-2",
                  // selected: true,
                }]
            },
            {
              title: "3级菜单",
              children: [
                {
                  title: "3级菜单-1",
                  // selected: true,
                },
                {
                  title: "3级菜单-2",
                }]
            },
            {
              title: "4级菜单",
            }
          ]
        }]
      }
    },
    watch: {

      searchValue() {
        this.debouncedGetTableByName()
      }
    },
    mounted() {
      // console.log(this.findJieDian(this.list1, data => data.title === '3级菜单-1'))

      this.debouncedGetTableByName = this.$Lodash.debounce(this.search, 500, false)
      this.diguiList(this.list2,this.list1)
      if(this.checkbox){
        this.diGuihalfSelect(this.list2)
      }
      this.list3=JSON.parse(JSON.stringify(this.list2))
    },
    methods: {
      findJieDian(list, fun, path = []) {
        for (let node of list) {
          path.push(node.title)
          if (fun(node)) {
            return path
          }
          if (node.children) {
            let fatherArray = this.findJieDian(node.children, fun, path)
            if (fatherArray.length > 0) {
              return fatherArray
            }
          }
          path.pop()
        }
        return []
      },
      //原始数据递归树
      diguiList(list2, list1) {
        list1.forEach((item, index) => {
          list2[index] = Object.assign({}, {
            show: true,
            selected: false,
            halfSelected: false,
            checked:false,
          }, item)
          if (item.children && item.children.length) {
            this.diguiList(list2[index].children, item.children)
          }
        })
      },
      //半选递归
      diGuihalfSelect(list2){
        list2.forEach(item=>{
          if(item.children && item.children.length){
            this.diGuihalfSelect(item.children)
            item.halfSelected=item.selected?false:(item.children.some((node)=>{return  node.selected==true}) || item.children.some((node)=>{return  node.halfSelected==true}))
          }
        })
      },
      //选中递归
      //搜索过滤树节点
      search() {
        this.list3=JSON.parse(JSON.stringify(this.list2))
        this.productFilter(this.list3, this.searchValue)
      },
      productFilter(list, name) {
        list.forEach(item=>{
          if (item.children && item.children.length) {
            this.productFilter(item.children, name);
            item.children = item.children.filter(item => {
              /**自己符合条件或者子级符合条件 */
              if (item.title.indexOf(name) != -1 || item.has) {
                return item;
              }
            });
            /**发现子级有满足条件的,给父级添加标识,再次循环时保留父级 has:为标识字段*/
            item.children.length && (item.has = true);
          }
        })
        // const listLength = list.length;
        // for (let index = 0; index < listLength; index++) {
        //   /**父级满足条件,子级全保留 */
        //   // if (list[index].title .indexOf(name)!=-1){
        //   //   continue;
        //   // }
        //   if (list[index].children && list[index].children.length) {
        //     this.productFilter(list[index].children, name);
        //     list[index].children = list[index].children.filter(item => {
        //       if (item.title.indexOf(name) != -1 || item.has) {
        //         return item;
        //       }
        //     })
              //list[index].has = true;
        //   }
        //
        // }
      }
    }
  }
</script>

<style scoped>

</style>

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值