原生多选树组件封装

<template>

  <div class="MultipleTree">

    <div v-show="isShowSelect" class="mask" @blur="popoverHide" />

    <el-popover

      ref="dcPopover"

      v-clickoutside="closePopover"

      placement="bottom-start"

      :width="popoverWidth"

      trigger="click"

      :popper-options="{ boundariesElement: 'viewport', removeOnDestroy: true }"

      @hide="popoverHide"

    >

      <el-tree

        ref="tree"

        class="common-tree"

        :width="width"

        :data="treeData"

        :props="obj"

        :show-checkbox="multiple"

        :node-key="obj.id"

        :check-strictly="checkStrictly"

        :default-expanded-keys="multiple ? defaultKey : []"

        :default-checked-keys="multiple ? defaultKey : []"

        :expand-on-click-node="multiple && expandClickNode"

        :check-on-click-node="checkClickNode"

        :highlight-current="true"

        @check-change="nodeClick"

        @node-click="nodeClick"

        @check="checkClick"

        @node-expand="nodeExpand"

        @node-collapse="nodeExpand"

      />

      <el-select

        slot="reference"

        ref="select"

        v-model="returnDataKeys"

        :size="size"

        :multiple="multiple"

        :clearable="clearable"

        :collapse-tags="collapseTags"

        :placeholder="placeholder"

        class="tree-select"

        @click.native="selectClick"

        @remove-tag="removeTag"

        @clear="clean"

      >

        <el-option

          v-for="(item, index) in options"

          :key="index"

          :label="item && item.label"

          :value="item && item.value"

        />

      </el-select>

    </el-popover>

  </div>

</template>

<script>

export default {

  name: 'MultipleTree',

  props: {

    // 树结构数据

    data: {

      type: Array,

      default() {

        return []

      }

    },

    // 树属性

    obj: {

      type: Object,

      required: false,

      default: () => {

        return {

          id: 'id', // ID

          label: 'name', // 显示名称

          children: 'children', // 子级字段名

          path: 'path', // 路径

          content: 'content', // 描述

          pid: 'pid' // 父id

        }

      }

    },

    // 配置是否可多选

    multiple: {

      type: Boolean,

      default() {

        return false

      }

    },

    // 配置是否可清空选择

    clearable: {

      type: Boolean,

      default() {

        return false

      }

    },

    // 配置多选时是否将选中值按文字的形式展示

    collapseTags: {

      type: Boolean,

      default() {

        return true

      }

    },

    // 显示复选框情况下,是否严格遵循父子不互相关联

    checkStrictly: {

      type: Boolean,

      default() {

        return false

      }

    },

    // 多选是设置点击节点是否可以选中

    checkClickNode: {

      type: Boolean,

      default() {

        return false

      }

    },

    // 多选时:点击节点展开还是点三角标

    expandClickNode: {

      type: Boolean,

      default() {

        return true

      }

    },

    // 默认选中的节点key

    defaultKey: {

      type: [Number, String, Array, Object],

      default() {

        return []

      }

    },

    // 只获取叶子节点

    leafOnly: {

      type: Boolean,

      default() {

        return true

      }

    },

    size: {

      type: String,

      default() {

        return 'mini'

      }

    },

    width: {

      type: String,

      default() {

        return '100%'

      }

    },

    height: {

      type: String,

      default() {

        return '300px'

      }

    },

    placeholder: {

      type: String,

      default() {

        return '请选择'

      }

    }

  },

  // 上面是父组件可传入参数

  data() {

    return {

      popoverWidth: '0px', // 下拉框大小

      isShowSelect: false, // 是否显示树状选择器

      options: [], // select option选项

      returnDatas: [], // 返回给父组件数组对象

      returnDataKeys: this.defaultKey // 返回父组件数组主键值

    }

  },

  computed: {

    treeData() {

      // 若非树状结构,则转化为树状结构数据

      return JSON.stringify(this.data).indexOf(this.obj.children) !== -1

        ? this.data

        : this.switchTree()

    }

  },

  watch: {

    treeData: {

      handler() {

        this.$nextTick(() => {

          this.init()

        })

      },

      immediate: true,

      deep: true

    },

    defaultKey: {

      handler(val) {

        this.returnDataKeys = val

      },

      immediate: true,

      deep: true

    }

  },

  methods: {

    init() {

      if (this.defaultKey !== undefined && this.defaultKey.length > 0) {

        if (this.multiple) {

          // 多选

          if (Object.prototype.toString.call(this.defaultKey).indexOf('Array') != -1) {

            if (Object.prototype.toString.call(this.defaultKey[0]).indexOf('Object') != -1) {

              // 对象

              this.setDatas(this.defaultKey)

            } else if (

              Object.prototype.toString.call(this.defaultKey[0]).indexOf('Number') != -1 ||

              Object.prototype.toString.call(this.defaultKey[0]).indexOf('String') != -1

            ) {

              this.setKeys(this.defaultKey)

            } else {

              return

            }

          } else {

            return

          }

        } else {

          // 单选

          if (

            Object.prototype.toString.call(this.defaultKey).indexOf('Number') != -1 ||

            Object.prototype.toString.call(this.defaultKey).indexOf('String') != -1 ||

            Object.prototype.toString.call(this.defaultKey).indexOf('Object') != -1

          ) {

            this.setKey(this.defaultKey)

          } else {

            return

          }

        }

      }

    },

    // 下拉框select点击[入口]

    selectClick() {

      var element = document.querySelector('body > div.el-select-dropdown.el-popper.is-multiple')

      element.style.display = 'none'

      this.$nextTick(function() {

        // 设置下拉框自适应宽度

        this.popoverWidth = this.$refs.select.$el.clientWidth - 26

      })

      // 显示下拉框

      return (this.isShowSelect = !this.isShowSelect)

    },

    nodeExpand(data, node) {

      this.$refs.dcPopover.updatePopper()

    },

    checkClick(data, node) {

      // 文中,已严格遵循了父子不关联,这里需要判断,如果是父节点勾选,则将判断check节点中有无子节点,如果没有也进行勾选

      if (!this.multiple) {

        // 单选

        this.isShowSelect = false

        this.setKey(node.key)

      } else {

        const node1 = this.$refs.tree.getNode(data.id)

        if (!node) {

          return

        }

        if (!node1) {

          return

        }

        if (!node1.checked) {

          // 如果有子级,则同步清空

          if (data.children) {

            let checks = []

            const checkedKeys = data.id

            checks.push(checkedKeys)

            const arr = this.getChildrenKeys(data)

            checks = checks.concat(arr)

            this.returnDataKeys = this.returnDataKeys.filter(

              (x) => !checks.some((item) => x === item)

            )

          } else {

            const index = this.returnDataKeys.findIndex((e) => e === data.id)

            this.returnDataKeys.splice(index, 1)

          }

        } else {

          let checksArr = [...this.returnDataKeys]

          const checkedKeys = data.id

          checksArr.push(checkedKeys)

          const arr = this.getChildrenKeys(data)

          checksArr = checksArr.concat(arr)

          this.returnDataKeys = [...new Set(checksArr)]

        }

        this.$refs.tree.setCheckedKeys(this.returnDataKeys)

        const t = []

        this.options = this.returnDataKeys.map((item) => {

          // 设置option选项

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

          if (!node) {

            return

          } else {

            t.push(node.data)

            return { label: node.label, value: node.key }

          }

        })

        this.returnDatas = t

      }

    },

    // 获取子级及孙子级value

    getChildrenKeys(node) {

      let ids = []

      if (node.children) {

        node.children.forEach((e) => {

          ids.push(e.id)

          if (e.children) {

            ids = ids.concat(this.getChildrenKeys(e))

          }

        })

      }

      return ids

    },

    // 树点击方法

    nodeClick(data, node) {

      if (!this.multiple) {

        // 单选

        this.isShowSelect = false

        this.setKey(node.key)

      } else {

        // 多选

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

        const t = []

        this.options = checkedKeys.map((item) => {

          // 设置option选项

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

          if (!node) {

            return

          } else {

            t.push(node.data)

            return { label: node.label, value: node.key }

          }

        })

        this.returnDataKeys = this.options.map((item) => {

          return item.value

        })

        this.returnDatas = t

        this.$emit('getValue', this.returnDataKeys, this.returnDatas)

      }

    },

    // 单选:清空选中

    clean() {

      // this.$refs.tree.setCurrentKey(null) //清除树选中key

      this.$refs.tree.setCurrentKey([]) // 清除树选中key

      this.returnDatas = []

      this.returnDataKeys = []

      this.clearSelectedNodes()

      this.popoverHide()

    },

    // 单选:设置、初始化值 key

    setKey(thisKey) {

      this.$refs.tree.setCurrentKey(thisKey)

      const node = this.$refs.tree.getNode(thisKey)

      this.setData(node.data)

    },

    // 单选:设置、初始化对象

    setData(data) {

      this.options = []

      this.options.push({

        label: data[this.obj.label],

        value: data[this.obj.id]

      })

      this.returnDatas = data

      this.returnDataKeys = data[this.obj.id]

    },

    // 多选:设置、初始化值 keys

    setKeys(thisKeys) {

      this.$refs.tree.setCheckedKeys(thisKeys)

      this.returnDataKeys = thisKeys

      const t = []

      this.options = thisKeys.map((item) => {

        // 设置option选项

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

        if (!node) {

          return

        } else {

          t.push(node.data)

          return { label: node.label, value: node.key }

        }

      })

      this.returnDatas = t

      this.popoverHide()

    },

    // 多选:设置、初始化对象

    setDatas(data) {

      this.$refs.tree.setCheckedNodes(data)

      this.returnDatas = data

      const t = []

      data.map((item) => {

        // 设置option选项

        t.push(item[this.obj.id])

      })

      this.returnDataKeys = t

      this.popoverHide()

    },

    // 多选,删除任一select选项的回调

    removeTag(val) {

      this.$refs.tree.setChecked(val, false) // 设置为未选中

      const node = this.$refs.tree.getNode(val) // 获取节点

      if (!this.checkStrictly && node.childNodes.length > 0) {

        this.treeToList(node).map((item) => {

          if (item.childNodes.length <= 0) {

            this.$refs.tree.setChecked(item, false)

          }

        })

      }

      this.nodeClick()

      this.popoverHide()

    },

    // 点击别处时关闭下拉框

    closePopover() {

      // if (this.isShowSelect) {

      this.isShowSelect = false

      if (this.returnDataKeys) {

        this.$emit('getValue', this.returnDataKeys, this.returnDatas)

      }

      // }

    },

    // 下拉框关闭执行

    popoverHide() {

      this.$emit('getValue', this.returnDataKeys, this.returnDatas)

    },

    // 多选,清空所有勾选

    clearSelectedNodes() {

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

      for (let i = 0; i < checkedKeys.length; i++) {

        this.$refs.tree.setChecked(checkedKeys[i], false)

      }

    },

    // 树形转为集合

    treeToList(tree) {

      let queen = []

      const out = []

      queen = queen.concat(tree)

      while (queen.length) {

        const first = queen.shift()

        if (first.childNodes) {

          queen = queen.concat(first.childNodes)

        }

        out.push(first)

      }

      return out

    },

    switchTree() {

      return this.buildTree(this.data, this.defaultValue)

    },

    // 将一维的扁平数组转换为多层级对象

    buildTree(data, id) {

      const fa = (id) => {

        const temp = []

        for (let i = 0; i < data.length; i++) {

          const n = data[i]

          if (n[this.obj.pid] === id) {

            n[this.obj.children] = fa(n[this.obj.id])

            temp.push(n)

          }

        }

        return temp

      }

      return fa(id)

    }

  }

}

</script>

<style lang="scss">

.MultipleTree {

  .el-tooltip {

    overflow: initial;

  }

  .popoverTag {

    text-align: center;

    max-height: 200px;

    overflow-y: scroll;

    .el-tag {

      margin-right: 10px;

      margin-bottom: 10px;

      cursor: pointer;

    }

  }

}

</style>

 效果图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值