封装组件,支持单选,多选,搜索,根据节点 id 默认选中对应的节点,勾选数据事件触发回调

效果图 (会不断更新 和修复一些 BUG 记得回来看看哦)

认真看组件配置属性,特别是传值的时候 defaultProps 配置的展示 key,因为最后有模拟 JSON 数据所以文章有点长

多选效果图:单选效果图:
imgimg

# 一、子组件

<template>
  <div>
    <el-select
      ref="searchSelect"
      v-model="mineStatus"
      :placeholder="placeholder"
      :multiple="!single"
      :size="size"
      :disabled="disabled"
      collapse-tags
      :loading="loading"
      @change="selectChange"
    >
      <div style="padding: 10px;">
        <el-input
          v-if="filterable"
          v-model="singleSearch"
          placeholder="请输入搜索内容"
          :size="size"
          clearable
        />
      </div>
      <el-option
        :value="mineStatusValue"
        style="position: relative;width: 100%;"
        :style="{ height: height + 'px', 'overflow-y': overflow }"
        @click="doThis($event)"
      >
        <div style="height: 100%;" @click="doThis($event)">
          <div style="padding-right: 10px;" @click="doThis($event)">
            <el-tree
              v-if="treeData.length"
              ref="tree"
              :data="treeData"
              :show-checkbox="!single"
              style="font-weight: 500;"
              highlight-current
              :props="defaultProps"
              :default-expand-all="defaultExpandAll"
              :default-checked-keys="defaultCheckedKeys"
              :check-strictly="single"
              :filter-node-method="filterNode"
              node-key="id"
              @check-change="handleCheckChange"
              @node-click="clickNode"
            />
          </div>
          <div
            v-if="!treeData.length"
            style="width: 100%;height: 100%;background-color: #FFFFFF;text-align: center;"
          >
            暂无数据
          </div>
        </div>
        <div
          v-show="load"
          id="load"
          style="position: absolute;left: 0;top: 0;height: 200px;width: 100%;"
        />
      </el-option>
    </el-select>
  </div>
</template>
<!-- /**
 * 组件说明
 * 属性:
 * 参数                     说明                       类型                    默认值
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * placeholder              输入框占位文本                String                ' 请选择 '
 * defaultProps             需要使用的展示字段值          Object                {children: 'children',label: 'label'}
 * data                     tree 的数据源                 Array                 []
 * filterable               是否开启搜索功能              Boolean                false
 * single                   tree 下拉是否单选              Boolean                false
 * defaultExpandAll         tree 是否展开全部节点          Boolean                 false
 * defaultCheckedKeys          默认勾选节点               Array                   []
 * disabled                   是否禁止操作                Boolean                Array
 * size                    el-option 大小尺寸选择          String                 medium
 *
 * 事件:
 * selectTerrEvent  获取选中对象 返回数组
 */ -->
<script>
const deepFind = (arr, condition, children) => {
  const main = []
  try {
    (function poll(arr, level) {
      if (!Array.isArray(arr)) return
      for (let i = 0; i < arr.length; i++) {
        const item = arr[i]
        main[level] = item
        const isFind = (condition && condition(item, i, level)) || false
        if (isFind) {
          throw Error
        } else if (children && item[children] && item[children].length) {
          poll(item[children], level + 1)
        } else if (i === arr.length - 1) {
          main.length = main.length - 1
        }
      }
    })(arr, 0)
  } catch (err) {}
  return main
}
export default {
  props: {
    placeholder: {
      type: String,
      required: false,
      default: '请选择'
    },
    defaultProps: {
      // 需要使用的展示字段值
      type: Object,
      default: () => ({
        children: 'children',
        label: 'label'
      })
    },
    filterable: {
      type: Boolean, // 是否开启搜索
      default: false
    },
    single: {
      type: Boolean, // 是否单选
      default: false
    },
    data: {
      type: Array, // 数据
      default: () => []
    },
    defaultExpandAll: {
      type: Boolean, // 是否展开节点
      default: false
    },
    defaultCheckedKeys: {
      type: Array,
      default: () => []
    },
    size: {
      type: String,
      default: 'medium'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      overflow: 'hidden',
      height: 40,
      load: false,
      mineStatus: '',
      mineStatusValue: [],
      loading: false,
      deferTimer: null, // 多选复选框高频查找的数据使用到的延时器变量
      loadingTips: null,
      SearchData: [], // 搜索的数据
      treeData: [], // 渲染树的变量
      callbackDefault: null, //  更新数据 高频回调事件使用到的 延时器变量
      //timeID: null, // 搜索防抖
      singleSearch: '', // 单选搜索
      firstTime: false // 初次加载 状态
    }
  },
  watch: {
    singleSearch(newValue, oldValue) {
      this.$refs.tree.filter(newValue)
    },
    // 更新数据 / 清空输入框
    data(n) {
      if (this.firstTime) {
        if (Array.isArray(n)) {
          if (n.length) {
            this.height = 200
            this.overflow = 'auto'
          } else {
            this.height = 40
            this.overflow = 'hidden'
          }
          this.mineStatus = ''
          this.mineStatusValue = []
          this.treeData = n
          this.defaultCheckEvent(true)
        } else {
          console.error('data 属性必须是一个Array')
        }
      }
    },
    // 更新勾选数据
    defaultCheckedKeys(newValue, oldValue) {
      if (this.firstTime) {
        clearTimeout(this.callbackDefault)
        this.callbackDefault = setTimeout(() => {
          this.defaultCheckEvent(true)
        }, 300)
      }
    }
  },
  created() {
    const that = this
    let dataType = true
    // 等待 接口树 数据获取完成
    function dataTerr() {
      if (!that.treeData.length && dataType) {
        that.mineStatus = '获取数据中...'
        setTimeout(() => {
          that.treeData = that.data
          dataTerr()
        }, 400)
      } else if (that.treeData.length) {
        dataType = false
        that.height = 200
        that.overflow = 'auto'
        that.mineStatus = ''
        // 是否开启默认勾选
        if (that.defaultCheckedKeys.length) {
          that.defaultCheckEvent()
        }
        // 初次加载 完成
        that.firstTime = true
      }
    }
    dataTerr()
    // 2.5s 后不管有没要获取到数据 都停止
    setTimeout(() => {
      dataType = false
      that.firstTime = true
    }, 2500)
  },
  methods: {
    // 过滤数据 返回搜索结果
    filterNode(value, data) {
      if (!value) return true
      return data[this.defaultProps.label].indexOf(value) !== -1
    },
    // 阻止事件冒泡 解决点击空白区域事件获取错误的数据 bug
    doThis(event) {
      this.$refs.searchSelect.blur()
      event.stopPropagation()
    },
    //select 框值改变时候触发的事件
    selectChange(e) {
      if (this.single || !this.treeData.length) {
        return false
      }
      const arrNew = []
      const dataLength = this.mineStatusValue.length
      const eleng = e.length
      for (let i = 0; i < dataLength; i++) {
        for (let j = 0; j < eleng; j++) {
          if (e[j] === this.mineStatusValue[i][this.defaultProps.label]) {
            arrNew.push(this.mineStatusValue[i])
            break
          }
        }
      }
      this.$refs.tree.setCheckedNodes(arrNew) // 设置勾选的值
    },
    // 默认勾选
    defaultCheckEvent(lock) {
      // 避免 多个监听数据更新时同时并发多次
      if (lock) {
        this.firstTime = false
      }
      // 筛选出数据存放
      const defaultData = []
      // 根据树 id 递归树 筛选出对应的数据
      if (this.single) {
        // 单选
        const myarr = deepFind(
          this.treeData,
          (item, index, level) => item.id === this.defaultCheckedKeys[0],
          this.defaultProps.children
        )
        myarr.forEach((v, l) => {
          if (v.id === this.defaultCheckedKeys[0]) {
            defaultData.push(v)
          }
        })
      } else {
        // 多选
        this.defaultCheckedKeys.forEach((id, k) => {
          const myarr = deepFind(
            this.treeData,
            (item, index, level) => item.id === id,
            this.defaultProps.children
          )
          myarr.forEach((v, l) => {
            if (v.id === id) {
              defaultData.push(v)
            }
          })
        })
      }
      // 更新输入框内的默认勾选值
      const arrLabel = []
      const arr = []
      defaultData.forEach((item) => {
        arrLabel.push(item[this.defaultProps.label])
        arr.push(item)
      })
      this.mineStatusValue = arr
      if (this.single) {
        this.mineStatus = arrLabel[0]
      } else {
        this.mineStatus = arrLabel
      }
      // 解除 状态
      if (lock) {
        this.firstTime = true
      }
      this.$emit('selectTerrEvent', defaultData)
      if (!this.mineStatus.length) {
        this.$refs.tree && this.$refs.tree.setCheckedNodes([])
      }
    },
    // 搜索 监听
    search() {
      this.loading = true
      const val = this.$refs.searchSelect.$data.query
      this.SearchData = []
      this.treeData = this.data
      setTimeout(() => {
        this.loading = false
        this.$refs.tree.filter(val)
      }, 500)
    },
    // 单选点击 复选框事件 @check="handleCheck"
    handleCheck(data) {
      if (!this.single) {
        return
      }
      this.$refs.tree.setCheckedKeys([]) // 删除所有选中节点
      this.$refs.tree.setCheckedNodes([data]) // 选中已选中节点
    },
    // 单选模式事件
    clickNode(data, node, obj) {
      if (!this.single) {
        // 多选不执行
        const index = this.mineStatusValue.findIndex((d) => d.id === data.id)
        if (index > -1) {
          this.$refs.tree.setChecked(data, false)
        } else {
          this.$refs.tree.setChecked(data, true)
        }
        return
      }
      const arrLabel = []
      const arr = [];
      [data].forEach((item) => {
        arrLabel.push(item[this.defaultProps.label])
        arr.push(item)
      })
      this.mineStatusValue = arr
      this.mineStatus = arrLabel[0]
      this.$refs.searchSelect.blur() // 失去焦点 关闭下拉框
      // 传递数据给父
      this.$emit('selectTerrEvent', [data])
    },
    // 获取当前复选框 选中的值 赋值到输入框里
    handleCheckChange() {
      if (this.deferTimer == null) {
        this.load = true
        this.loadingTips = this.$loading({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(255, 255, 255, 0.5)',
          target: document.getElementById('load')
        })
      }
      const res = this.$refs.tree.getCheckedNodes(true, true) // 这里两个 true,1. 是否只是叶子节点 2. 是否包含半选节点(就是使得选择的时候不包含父节点)
      const arrLabel = []
      const arr = []
      res.forEach((item) => {
        arrLabel.push(item[this.defaultProps.label])
        arr.push(item)
      })
      clearTimeout(this.deferTimer)
      this.deferTimer = setTimeout(() => {
        this.mineStatusValue = arr
        this.mineStatus = arrLabel
        this.load = false
        this.loadingTips.close()
        this.deferTimer = null
        this.$emit('selectTerrEvent', res)
      }, 200)
    }
  }
}
</script>
<style scoped>
.el-tooltip.item {
  width: max-content;
  display: inline-block;
  border: none;
  outline: none;
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover {
  background-color: #ffffff;
}
.el-select-dropdown__item {
  padding: 0;
}
.treedownwidth {
  width: 13px !important;
}
.show {
  display: block;
}
.comtreedown {
  height: 100%;
}
.comtreedown .treedown {
  width: 220px;
  height: 100%;
  display: flex;
  flex-direction: column;
  color: #ffffff;
  position: relative;
}
.comtreedown .treedown .title {
  height: 35px;
  line-height: 35px;
  font-size: 16px;
  text-indent: 14px;
  border-bottom: 1px solid #fff;
  overflow: hidden;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
  cursor: pointer;
}
.comtreedown .treedown .left {
  position: absolute;
  z-index: 8;
  top: 50%;
  right: 0px;
  width: 13px;
  height: 72px;
  margin-top: -36px;
  cursor: pointer;
}
.comtreedown .treedown .left img {
  display: none;
}
/* 修改默认样式 */
.el-tree-node__expand-icon {
  color: #ffffff;
}
.comtreedown .el-tree {
  width: 100%;
  height: 100%;
  padding: 10px;
  color: #ffffff;
  overflow: auto;
  border-bottom-left-radius: 10px;
}
.el-tree-node__content {
  width: max-content;
  min-width: 100%;
  color: #fff;
}
.el-tree-node,
.el-tree-node__children {
  min-width: 100%;
  width: max-content;
}
.el-tree-node__label {
  width: auto;
  overflow: hidden;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}
.el-tree-node__children .el-tree-node__label {
  width: auto;
  overflow: hidden;
  -webkit-overflow: hidden;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}
span.el-icon-caret-right:before {
  content: "";
}
span.el-icon-caret-right:after {
  content: "\E60E";
}
</style>

# 二、父页面

<template>
  <div>
    <el-form :inline="true" :model="pageParams.params" class="demo-form-inline">
      <el-form-item label="下拉树">
        <select-tree
          size="mini"
          :data="getData"
          :filterable="true"
          :default-expand-all="true"
          :default-props="defaultProps"
          @selectTerrEvent="selectTerrEvetn"
        />
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
import SelectTree from './select-tree.vue'
export default {
  components: {
    SelectTree
  },
  data() {
    const _this = this
    return {
      pageParams: {},
      defaultProps: {
        children: 'children',
        label: 'name'
      },
      getData: []
    }
  },
  computed: {},
  watch: {},
  created() {
    this.getData = [
      {
        id: '-1',
        name: '前置库系统',
        pid: null,
        children: [
          {
            id: '1',
            name: 'Mysql',
            pid: '-1',
            children: [
              {
                id: '021d75b2eea64d579effd98fcf173c1c',
                name: 'mysql连接19',
                pid: '1',
                children: null
              },
              {
                id: 'dcde763b6fbf4c24af3eb8c419405c7f',
                name: 'xp_制作映射表数据',
                pid: '1',
                children: null
              },
              {
                id: 'c5868ea98e574c10a6396197d8ea0be8',
                name: 'SOURCEMySql',
                pid: '1',
                children: null
              },
              {
                id: 'c7778794bd1f4c24a87bd8384846945d',
                name: 'fd',
                pid: '1',
                children: null
              },
              {
                id: '5206c716b9e34ebcb6d18135a29012c7',
                name: 'cx',
                pid: '1',
                children: null
              },
              {
                id: 'ec358fba11d44d15a96276da2d8c92b6',
                name: 'local12345',
                pid: '1',
                children: null
              },
              {
                id: '08a1d89a54e64c98ba3dfc4c56de70d8',
                name: 'fd2',
                pid: '1',
                children: null
              },
              {
                id: '85a3bace64bf49c49997c89e976c1797',
                name: 'fd1',
                pid: '1',
                children: null
              },
              {
                id: '044bc4a61dc945079592ef6b119f5611',
                name: 'xwscon',
                pid: '1',
                children: null
              },
              {
                id: '2b2add5ab7624561a89e023f1526df4e',
                name: 'order',
                pid: '1',
                children: null
              },
              {
                id: 'cb200d034acf4ea6b729b0b17820b5ee',
                name: '206fd',
                pid: '1',
                children: null
              },
              {
                id: '98ecb1222d9e4c1f9a5db30c44875b8e',
                name: 'CJY_CS',
                pid: '1',
                children: null
              },
              {
                id: '5edfc7e8638845128e44f2a4da7de7eb',
                name: '测试数值类型',
                pid: '1',
                children: null
              }
            ]
          },
          {
            id: '2',
            name: 'Oracle',
            pid: '-1',
            children: [
              {
                id: 'a3b7f6eec7134de8b85bbd9dc57d07c5',
                name: 'local',
                pid: '2',
                children: null
              },
              {
                id: '4b80404a54d54812bb3409ddf21442ac',
                name: 'bzk',
                pid: '2',
                children: null
              },
              {
                id: '5610963be596416a9deb83ec32a16f19',
                name: 'by',
                pid: '2',
                children: null
              },
              {
                id: 'ead9bd5004b44e8ba791bcb2511d0b11',
                name: 'gw测试',
                pid: '2',
                children: null
              },
              {
                id: '9d717d68fbe141baa8c8380fa52bc51d',
                name: 'ORA123',
                pid: '2',
                children: null
              },
              {
                id: '739a330fdbba4d7ea9378ec169c30e91',
                name: '206ORA',
                pid: '2',
                children: null
              },
              {
                id: 'fb48a2e3177e4c4a910a7a0c81c3e9ac',
                name: 'xp_采集流程',
                pid: '2',
                children: null
              },
              {
                id: '197030e0295c4546ad3087da9c312da2',
                name: '测试大小写',
                pid: '2',
                children: null
              },
              {
                id: 'a9fad7c393b74103ba3beea7719c90e2',
                name: 'xp_数据源配置',
                pid: '2',
                children: null
              },
              {
                id: '34ed62284e624bb0a4ae756fef82c9de',
                name: 'oracle连接',
                pid: '2',
                children: null
              },
              {
                id: '65f4ea98c9de4f6daa7693d2fd0ab43c',
                name: '206123',
                pid: '2',
                children: null
              },
              {
                id: '7d72b8e291464d129c95592752f839ce',
                name: 'MT18',
                pid: '2',
                children: null
              },
              {
                id: '0c6acdf12d554d6ea39d986c596bc4d2',
                name: '监控测试',
                pid: '2',
                children: null
              },
              {
                id: '9724796eb3aa45baa225c936e549bd81',
                name: 'SOURCE2',
                pid: '2',
                children: null
              },
              {
                id: '174987a5a60b45b5beedd088dd42a02a',
                name: 'SunOrcl',
                pid: '2',
                children: null
              },
              {
                id: '9899e04067554df69c67cf5b196e98b7',
                name: 'by1',
                pid: '2',
                children: null
              },
              {
                id: 'd314fbad49d14e05a4279fc4ccb4ce66',
                name: 'MT',
                pid: '2',
                children: null
              },
              {
                id: 'c8cbeff921e547e483596074a9e324fb',
                name: 'gw_bzk',
                pid: '2',
                children: null
              },
              {
                id: '45e29dc086b544678e9125a1c2f0e89b',
                name: 'YGH',
                pid: '2',
                children: null
              },
              {
                id: '5f21d117c256418a801eae9ee27d8cc3',
                name: 'CHATESTU',
                pid: '2',
                children: null
              },
              {
                id: 'e29a448c8c4c4e37a75f64ad87479cfd',
                name: 'MT041201',
                pid: '2',
                children: null
              },
              {
                id: '79802451f0bb4a0f9918d24cbb3df478',
                name: 'MT0415',
                pid: '2',
                children: null
              },
              {
                id: '4066e261f0bd4aab94880080594f2f42',
                name: 'LOCS',
                pid: '2',
                children: null
              },
              {
                id: '63c531c5ec30436bb83c3f153bf4ea04',
                name: 'bzk2',
                pid: '2',
                children: null
              },
              {
                id: '5269f15244b84074b60789a54d839ebe',
                name: 'GB',
                pid: '2',
                children: null
              },
              {
                id: 'f76d9389e35d40e7ba4b9d5604f9821e',
                name: 'GW测试',
                pid: '2',
                children: null
              },
              {
                id: '87c3ccb1d9224972901e1cea2fee88a8',
                name: 'orcl202',
                pid: '2',
                children: null
              },
              {
                id: 'a9cb35c1e9564ac88a88b2aef38aeafb',
                name: '测试1',
                pid: '2',
                children: null
              },
              {
                id: '30b36c6022834b4c8fbba6b1400f7e85',
                name: 'TEST_SUB',
                pid: '2',
                children: null
              },
              {
                id: '4f8f187bc8e046ccb9c0afaa2e30fa6b',
                name: 'XP_DRI',
                pid: '2',
                children: null
              },
              {
                id: '47b79c6e096248e18a51568d763e9ac5',
                name: 'ORCLCHA',
                pid: '2',
                children: null
              },
              {
                id: '27af4fdba1df477295f5978ca6f0704c',
                name: 'XP_订阅分发1',
                pid: '2',
                children: null
              },
              {
                id: '2224cfbd964e444d96acddc404622491',
                name: 'XP_订阅分发2',
                pid: '2',
                children: null
              }
            ]
          },
          {
            id: '10',
            name: 'DM',
            pid: '-1',
            children: [
              {
                id: '362eede69f654260b891d1a9ac61cc68',
                name: '达梦连接',
                pid: '10',
                children: null
              },
              {
                id: '8331b61efdca4c82a183c69da90d5099',
                name: '达梦AAA',
                pid: '10',
                children: null
              },
              {
                id: '65eb1c3012584d9e865d2f0447fd5eb0',
                name: 'zb',
                pid: '10',
                children: null
              },
              {
                id: '37562043808646709b003badbdeab8e5',
                name: 'bzk1',
                pid: '10',
                children: null
              },
              {
                id: 'b39484a1f6654c6ab5c2bac8763e652e',
                name: '达梦GW',
                pid: '10',
                children: null
              },
              {
                id: '0d905fdf73a94948a91406bc508b607d',
                name: 'DM数值类型',
                pid: '10',
                children: null
              },
              {
                id: '10132ff9686145929a24de9b5c9c6eb4',
                name: '测试dm转Oracle',
                pid: '10',
                children: null
              }
            ]
          },
          {
            id: '12',
            name: 'Kingbase8',
            pid: '-1',
            children: [
              {
                id: 'c933e63315ee4c64b70a5fa348c83059',
                name: 'xp_1111_test1',
                pid: '12',
                children: null
              },
              {
                id: 'a5b761ecb97a4930a8ecae16219c6fd9',
                name: 'xp_add_1',
                pid: '12',
                children: null
              },
              {
                id: '12c96463ed464a7da6c0696cf272b6e0',
                name: 'xp_test_kingbase8_22',
                pid: '12',
                children: null
              },
              {
                id: '14f306c4ccc94ddfb4b0cafcb79c3982',
                name: 'XP_TEST_1',
                pid: '12',
                children: null
              },
              {
                id: '9a68cbf199534e28a6c8779036977edb',
                name: '金仓C',
                pid: '12',
                children: null
              },
              {
                id: '6097a77761d64e83a3a481fa9b4e000f',
                name: '金仓m',
                pid: '12',
                children: null
              },
              {
                id: '6941881032414bd2858a64efcf36db5e',
                name: 'sunKing',
                pid: '12',
                children: null
              },
              {
                id: '91b67d1b3d9049c597544302f4e0053a',
                name: 'qztable',
                pid: '12',
                children: null
              },
              {
                id: '645aadeb3d5542dfb71801c5600ef857',
                name: 'king8',
                pid: '12',
                children: null
              }
            ]
          }
        ]
      }
    ]
  },
  beforeDestroy() {},
  methods: {
    selectTerrEvetn(data) {
      console.log(data, '勾选了')
    }
  }
}
</script>
<style scoped></style>