el-tree check-strictly但父子关联+虚拟滚动

1. 设置check-strictly可选择各层级且父子数关联

<!-- child.vue -->
<template>
  <div class="select-tree-container">
    <el-popover
      placement="bottom-start"
      popper-class="select-tree-popper"
      v-model="isShowSelect"
      @hide="closePopver"
    >
      <el-tree
        ref="tree"
        :data="treeData"
        node-key="id"
        default-expand-all
        :expand-on-click-node="false"
        check-strictly
        check-on-click-node
        show-checkbox
        :default-expanded-keys="defaultKey"
        @check="nodeClick"
      ></el-tree>

      <el-select
        slot="reference"
        ref="select"
        v-model="selectVal"
        multiple
        @remove-tag="removeTag"
      >
        <el-option
          v-for="item in options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        ></el-option>
      </el-select>
    </el-popover>
  </div>
</template>
 
<script>
export default {
  name: 'SelectTree',
  props: {
    // 树结构数据
    treeData: {
      type: Array,
      default() {
        return [];
      },
    },
    // 默认选中的节点key
    defaultKey: {
      type: Array,
      default() {
        return [];
      },
    }
  },
  data() {
    return {
      isShowSelect: false, // 是否显示树状选择器
      options: [],
      returnDatas: [], // 返回给父组件数组对象
      selectVal: []
    }
  },
  created () {
  },
  watch: {
    // 隐藏select自带的下拉框
    isShowSelect() {
      this.$refs.select.blur();
    }
  },
  methods: {
    // 节点被点击
    nodeClick(v) {
      const anode = this.$refs.tree.getNode(v)
      if (anode.checked) {
        this.setParentChecked(anode.parent)
        this.setChildChecked(anode.childNodes)
      } else {
        this.deleteParentChecked(anode.parent)
        this.deleteChildChecked(anode.childNodes)
      }

      var checkedKeys = this.$refs.tree.getCheckedKeys() // 所有被选中的节点的 key 所组成的数组数据

      this.options = checkedKeys.map(item => {
        const node = this.$refs.tree.getNode(item) // 所有被选中的节点对应的node

        if (node.level === 1) { // 第一层
          node.hide = false
        } else {
          node.hide = !node.parent.indeterminate &&  node.parent.checked
        }

        // 设置option选项
        return {
          ...node,
          label: node.label,
          value: node.key
        }
      })

      this.returnDatas = this.selectVal = this.options.filter(o => !o.indeterminate && !o.hide).map(item => item.value)
      this.closePopver()
    },
    // 如果不是全选中为父级添加半选状态,如果子集全选后,父级也要全选
    setParentChecked(parent) {
      const fnode = this.$refs.tree.getNode(parent)
      // 子集是否是全选
      const isAllChecked = fnode.childNodes.every(k => k.checked && k.indeterminate === false)
      if (!fnode.isLeaf) {
        fnode.indeterminate = !isAllChecked // 子集是否是全选,如果子集全选,则半选状态为假
        fnode.checked = true
      }
      if (fnode.parent) {
        this.setParentChecked(fnode.parent)
      }
    },
    // 将子节点全选中
    setChildChecked(childNodes) {
      if (childNodes && childNodes.length > 0) {
        childNodes.map(k => {
          k.checked = true
          this.setChildChecked(this.$refs.tree.getNode(k).childNodes)
        })
      }
    },
    // 如果取消子节点的选中,设置父级节点选中状态
    deleteParentChecked(parent, d = false) {
      const fnode = this.$refs.tree.getNode(parent)
      const isAllChecked = fnode.childNodes.some(k => d ? (k.checked || k.indeterminate) : k.checked) // 子集是否是全选
      if (!fnode.isLeaf) {
        fnode.indeterminate = isAllChecked // 子集是否是全选,如果子集全选,则半选状态为假
        fnode.checked = isAllChecked
        if (fnode.parent) { // 如果有父节点,则需要去判断父节点是否选中
          this.deleteParentChecked(fnode.parent, true)
        }
      }
    },
    // 删除子节点的勾选状态
    deleteChildChecked(childNodes) {
      if (childNodes && childNodes.length > 0) {
        childNodes.map(k => {
          k.indeterminate = false
          k.checked = false
          this.deleteChildChecked(this.$refs.tree.getNode(k).childNodes)
        })
      }
    },
    // 删除任一 select 选项
    removeTag(val) {
      this.$refs.tree.setChecked(val, false); // 设置节点的勾选状态为未选中
      this.nodeClick(val)
    },
    // 清空所有勾选
    clearSelectedNodes() {
      this.selectVal = this.returnDatas = []
      this.$refs.tree.setCheckedKeys([])
    },
    // 下拉框关闭
    closePopver() {
      this.$emit("get:value", this.selectVal, this.returnDatas);
    }
  }
};
</script>
 
<style lang="scss">
  .select-tree-container {
    .el-select {
      width: 280px;
    }
  }

  .select-tree-popper {
    box-sizing: border-box;
    width: 280px;
  }
</style>
<template>
  <div class="app-container">
    <select-tree
      :tree-data="treeData"
      :defaultKey="defaultCheckedKeys"
      @get:value="getTreeVal"
    />
  </div>
</template>

<script>
import SelectTree from '@/components/SelectTree.vue';

export default {
  components: { SelectTree },
  name: "App",
   data() {
    return {
      treeData: [
        {
          id: 1,
          label: "水果",
          children: [
            { 
              id: 4, 
              label: '香蕉',
              children: [
                { id: 41, label: '小香蕉' },
                { id: 42, label: '大香蕉' },
              ]
            },
            { 
              id: 5, 
              label: '圣女果',
              children: [
                { id: 51, label: '小圣果' },
                { id: 52, label: '大圣果' },
              ] 
            }
          ]
        },
        {
          id: 2,
          label: "食物",
          children: [
            { id: 6, label: '龙虾' },
            { id: 7, label: '螃蟹' }
          ]
        }
      ],
      defaultCheckedKeys: []
    }
  },
  methods: {
    getTreeVal(val, data) {
      // console.log(val, data);
    }
  }
};
</script>

2. 增加虚拟滚动

  1. 安装vue-virtual-scroll-list
$ yarn add vue-virtual-scroll-list
  1. tree-virtual-node.vue中增加以下方法,否则点击收起箭头无效果

  2. el-tree改为virtual-tree,其余参数都一致

<!-- child.vue -->
<template>
  <div class="select-tree-container">
    ...
    <virtual-tree
      ref="tree"
      :data="treeData"
      node-key="id"
      default-expand-all
      :expand-on-click-node="false"
      check-strictly
      check-on-click-node
      show-checkbox
      :default-expanded-keys="defaultKey"
      @check="nodeClick"
    ></virtual-tree>
    ...
  </div>
</template>
 
<script>
import VirtualTree from './virtualNodeTree/tree'

export default {
  name: 'SelectTree',
  components: { VirtualTree }
}
</script>

3. 效果图

4. 参考文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容