使用el-select二次封装tree下拉多选框

<!--

    /**

     * 树形下拉选择组件,下拉框展示树形结构,提供选择某节点功能,方便其他模块调用

     * @author wz

     * @date 2020-06-09

     * 调用示例:

     * <tree-select :height="400" // 下拉框中树形高度

     *              :width="200" // 下拉框中树形宽度 不填写自适应el-select input框大小

     *              size="small"  // 输入框的尺寸: medium/small/mini

     *              :data="data" // 树结构的数据或者普通包含主键,父主键的普通集合

                    :obj="{}"    //可自定义字段,字段映射如下

     *              multiple   // 多选

                    //默认值:单选可传入数字,字符串,对象;多选传入【数字|字符|对象】数组,其他非法

                    :default-key="..."

     *              clearable   // 可清空选择

     *              collapseTags   // 多选时将选中值按文字的形式展示

                    expand-click-node   //点击节点自动展开。多选有效

                    check-click-node // 是否点击节点是选中 多选生效

     *              checkStrictly // 多选时,严格遵循父子不互相关联 效果参考elementui 对应属性效果

     *              @getValue="父组件获取值方法"> // 事件有两个参数:第一个是所有选中的节点ID,第二个是所有选中的节点数据

     *              </tree-select>

      <select-tree

     obj 字段映射如下,值填写你实际字段,可拓展字段。最终返回主键以及选择对象【全部字段】

                        id:'id',//可改成自己对应主键【改值】

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

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

                        path:'path',//路径

                        content:'content',//描述

                        pid:'pid',//父id

     */

-->

<template>

  <div id="treeSelect">

    <div class="mask"

         v-show="isShowSelect"></div>

    <el-popover placement="top-start"

                title=""

                trigger="manual"

                v-model="isShowSelect"

                @hide="popoverHide">

      <div class="mrb10">

        <el-input placeholder="输入关键字进行过滤"

                  v-model="filterText"

                  size="small">

        </el-input>

      </div>

      <el-tree class="common-tree"

               width="width"

               style="height:300px"

               ref="tree"

               :data="data"

               :props="obj"

               :show-checkbox="multiple"

               :node-key="obj.id"

               :check-strictly="checkStrictly"

               :default-expanded-keys="defaultKey"

               :default-checked-keys="checkKeys"

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

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

               :highlight-current="true"

               @check-change="nodeClick"

               :filter-node-method="filterNode"

               @node-click="nodeClick"></el-tree>

      <el-select slot="reference"

                 ref="select"

                 :size="size"

                 popper-class="operateDropOption"

                 :popper-append-to-body="true"

                 style="width: 300px"

                 v-model="returnDataKeys"

                 :multiple="multiple"

                 :clearable="clearable"

                 :collapse-tags="collapseTags"

                 @click.native="selectClick"

                 @remove-tag="removeTag"

                 @clear="clean"

                 class="tree-select">

        <el-option v-for="item in options"

                   :key="item.value"

                   :label="item.label"

                   :value="item.value"></el-option>

      </el-select>

      <el-row>

        <!-- <el-button v-if="multiple"

                   class="ok"

                   @click="isShowSelect=false"

                   size="mini"

                   type="text">确定</el-button> -->

      </el-row>

    </el-popover>

  </div>

</template>


<script>

export default {

  name: "tree-select",

  props: {

    // 树结构数据

    data: {

      type: Array,

      default() {

        return [];

      }

    },

    obj: {

      type: Object,

      required: false,

      default: () => {

        return {

          id: 'value',// ID

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

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

          path: 'path',//路径

          content: 'content',//描述

          pid: 'pid',//父id

        }

      }

    },

    // 配置是否可多选

    multiple: {

      type: Boolean,

      default() {

        return true;

      }

    },

    // 配置是否可清空选择

    clearable: {

      type: Boolean,

      default() {

        return false;

      }

    },

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

    collapseTags: {

      type: Boolean,

      default() {

        return false;

      }

    },

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

    checkStrictly: {

      type: Boolean,

      default() {

        return false;

      }

    },

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

    checkClickNode: {

      type: Boolean,

      default() {

        return false;

      }

    },

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

    expandClickNode: {

      type: Boolean,

      default() {

        return false;

      }

    },

    // 默认选中的节点key

    defaultKey: {

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

      default() {

        return [''];

      }

    },

    checkKeys: {

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

      default() {

        return [''];

      }

    },

    size: {

      type: String,

      default() {

        return 'small';

      }

    },

    width: {

      type: String,

      default() {

        return '100%';

      }

    },

    height: {

      type: String,

      default() {

        return '100%';

      }

    },

  },

  data() {

    return {

      filterText: '',

      popoverWidth: "300px",//下拉框大小

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

      options: [],//select option选项

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

      returnDataKeys: [],//返回父组件数组主键值

    };

  },

  computed: {

    // treeData() { // 若非树状结构,则转化为树状结构数据

    //   return JSON.stringify(this.data).indexOf(this.obj.children) !== -1 ? this.data : this.switchTree();

    // },

  },

  mounted() {

    this.nodeClick()

    addEventListener('click', (val) => {

      this.isShowSelect = false

    })

  },

  deactivated() {

    this.clean()

  },

  methods: {

    init() {

      // eslint-disable-next-line no-undef,no-debugger

      // debugger

      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 {

              // console.log("多选:传入参数类型不匹配");

              return;

            }

          } else {

            // console.log("多选:传入参数类型不匹配");

            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 {

            // console.log("单选:传入参数类型不匹配");

            return;

          }

        }

      }

    },

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

    selectClick() {

      this.$nextTick(function () {//设置下拉框自适应宽度

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

      })

      //显示下拉框

      return this.isShowSelect = !this.isShowSelect

    },

    //单选: 树点击方法

    nodeClick(data, node) {

      if (!this.multiple) {//单选

        this.isShowSelect = false;

        this.setKey(node.key);

      } else {//多选

        var checkedNodes = this.$refs.tree.getCheckedNodes(true, false); // 所有被选中的节点

        var t = [];

        // 数组去重

        const map = new Map()

        checkedNodes.forEach((item) => {

          if (!map.has(item['id'])) {

            map.set(item['id'], item)

          }

        })

        const newCheckedNodes = [...map.values()]

        var checkedKeys = newCheckedNodes.map(item => item.id) // 所有被选中的节点的 key 所组成的数组数据

        this.options = checkedKeys.map((item) => {//设置option选项

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

          t.push(node.data);

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

        });

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

          return item.value;

        });

        this.$emit('change', this.returnDataKeys)

        this.returnDatas = t;

      }

    },

    filterNode(value, data) {

      if (!value) return true;

      return data.label.indexOf(value) !== -1;

    },

    //单选:清空选中

    clean() {

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

      this.returnDatas = null; this.returnDataKeys = '';

      this.popoverHide();

      this.isShowSelect = false;

      this.$refs.tree.setCheckedKeys([]);

      this.$refs.tree.setChecked([]);

      //折叠全部节点

      for (let i = 0; i < this.$refs.tree.store._getAllNodes().length; i++) {

        this.$refs.tree.store._getAllNodes()[i].expanded = false;

      }

    },

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

    setKey(thisKey) {

      this.$refs.tree.setCurrentKey(thisKey);

      var 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;

      var t = [];

      thisKeys.map((item) => {//设置option选项

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

        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;

      var 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);//设置为未选中

      var 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();

    },

    //下拉框关闭执行

    popoverHide() {

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

    },

    // 多选,清空所有勾选

    clearSelectedNodes() {

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

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

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

      }

    },

    //树形转为集合

    treeToList(tree) {

      var queen = [];

      var out = [];

      queen = queen.concat(tree);

      while (queen.length) {

        var 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);

    // },

  },

  watch: {

    // eslint-disable-next-line no-unused-vars

    isShowSelect(val) {

      // 隐藏select自带的下拉框

      if (!val) {

        this.$refs.select.blur();

      }

    },

    checkKeys: {

      handler(newValue, oldValue) {

        this.returnDataKeys = newValue;

      },

      deep: true,

      immediate: true

    },

    // treeData() {//监听tree数据

    //   this.$nextTick(() => {

    //     this.init();

    //   })

    // },

    filterText(val) {

      this.$nextTick(() => {

        this.$refs.tree.filter(val);

      })

    }

  }

};

</script>


<style scoped>

.mask {

  height: 300px;

  position: fixed;

  top: 300px;

  left: 0;

  opacity: 0;

  z-index: 11;

}

.common-tree {

  overflow: auto;

}

.tree-select {

  z-index: 111;

}

.ok {

  float: right;

}

.el-row {

  padding-top: 0px !important;

}

.checkall-box {

  width: 100%;

  height: 40px;

  line-height: 40px;

  border-bottom: 1px solid #fff;

}

.el-popover {

  min-width: 300px !important;

}

</style>

<style lang="less">

.el-popover {

  min-width: 274px;

}

/* 隐藏默认下拉 */

.operateDropOption.el-select-dropdown__empty {

  display: none;

}

.operateDropOption.el-select-dropdown__list {

  display: none;

}

.operateDropOption.el-select-dropdown {

  display: none;

}

// 滚动条

::-webkit-scrollbar {

  max-width: 12px;

  height: 8px;

  background-color: #fff !important;

}

::-webkit-scrollbar-thumb {

  border-radius: 8px;

  background-color: #d8dce0 !important;

  border-style: solid;

  border-color: transparent;

  border-width: 2px;

  background-clip: padding-box;

}

::-webkit-scrollbar-track {

  border-radius: 12px;

  border-radius: 0 !important;

  background-color: #fff !important;

}

</style>



调用:

<template>

  <tree-select :height="height"

               width="width"

               size="small"

               :data="dataList"

               :obj="obj"

               multiple

               :default-key="defaultKey"

               :checkKeys="checkKeys"

               clearable

               collapseTags

               expand-click-node

               check-click-node

               @change="change">

  </tree-select>

</template>


<script>

import TreeSelect from '../tree-select/tree-select.vue';

import { DeviceService } from "@/service";

export default {

  components: {

    TreeSelect

  },

  data() {

    return {

      defaultKey: [],

      dataList: [],

    };

  },

  props: {

    value: [String, Array],         // 选择的值

    width: {

      type: String,

      default() {

        return '300px';

      }

    },

    height: {

      type: String,

      default() {

        return '200px';

      }

    },

    obj: {

      type: Object,

      required: false,

      default: () => {

        return {

          id: 'id',// ID

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

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

          path: 'path',//路径

          content: 'content',//描述

          pid: 'pid',//父id

        }

      }

    },

  },

  watch: {

    value: {

      handler(newValue, oldValue) {

        this.checkKeys = newValue;

        this.defaultKey = newValue;

        this.$emit("input", newValue);

      },

      deep: true,

      immediate: true

    },

  },

  created() {

    this.getDeviceGroupTree();

  },

  methods: {

    change(val) {

      this.$emit("input", val);

      this.$emit("onChange", val);

    },

    // 获取机型组树形数据

    getDeviceGroupTree() {

      DeviceService.getDeviceGroupTree().then(res => {

        if (res && res.data) {

          this.dataList = res.data.data

        }

      })

    }

  }

};

</script>


<style  lang="less" >

</style>

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

推荐阅读更多精彩内容