什么?封装el-select组件,再也不用为选择树烦恼,支持多选,过滤,回显,双向绑定,今天这个鱼我摸定了,耶稣都拦不住我

项目需求,需要选择树,针对人员选择、部门选择、公司选择、多选或者单选,并且很多地方都用得到,刚舒舒服服摸鱼没几天,又来,那封呗,一劳永逸。使用简单,同样只需要传递对应的接口数据及参数,其他就不用你操心了。

先上效果

回显部分

选择部分

代码部分

index.vue

<template>
  <div class="SelectionTree" :style="{ width: width }">
    <!-- 选择树部分 -->
    <template>
      <div
        v-show="lable"
        style="padding: 6px 0px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
      >
        {{ lable }}
      </div>
      <el-select
        class="autoWidth"
        style="width: 100%;"
        v-model="echoValue"
        :multiple="multiple"
        placeholder="请选择"
        :collapse-tags="false"
        popper-class="popper-class"
        :popper-append-to-body="false"
        @change="SelectionTreeChange"
        @focus="selectClick"
        @remove-tag="removeTag"
      >
        <el-option
          v-for="item in options"
          :key="item[optionsProps.value]"
          :label="item[optionsProps.label]"
          :value="item[optionsProps.value]"
        >
        </el-option>
      </el-select>
    </template>
    <!-- 弹窗部分 -->
    <template>
      <Dialogbox
        width="50%"
        :title="title || ''"
        :dialogVisible="RenewalShow"
        v-if="RenewalShow"
        @confirm="confirm"
        @cancel="cancel"
        @close="close"
        :customFooter="true"
      >
        <template slot="content">
          <div style="display: flex;justify-content: space-between;">
            <div class="hb">
              <el-input
                class="treeSearch"
                placeholder="输入关键字进行搜索"
                v-model="filterText"
                @change="filterChange"
              />
              <div class="hb-c" style="height:460px;">
                <el-tree
                  :data="data"
                  show-checkbox
                  ref="tree"
                  :check-strictly="true"
                  class="filter-tree"
                  @check-change="checkChange"
                  :highlight-current="true"
                  :props="defaultProps"
                  :default-expanded-keys="defaultExpandedArr"
                  :default-checked-keys="defaultCheckedArr"
                  :filter-node-method="filterNode"
                  :node-key="nodekey"
                  @check="handleCheckChange"
                >
                </el-tree>
              </div>
            </div>
            <div class="hb">
              <div style="height:504px;overflow-y: scroll;">
                <ul style="padding: 4px;overflow: hidden;">
                  <li
                    class="hb-li"
                    v-for="(item, index) in checkedData"
                    :key="index"
                    style="padding: 4px 8px 4px 4px;overflow: hidden;"
                  >
                    <div
                      style="display: flex; justify-content: space-between;line-height: 16px !important;"
                    >
                      <span style="font-weight: bold;">{{
                        item[defaultProps.label]
                      }}</span>
                      <i
                        class="el-icon-delete"
                        style="cursor: pointer;"
                        title="删除"
                        @click="treeDel(item)"
                      ></i>
                    </div>
                  </li>
                </ul>
                <div v-show="checkedData.length == 0">
                  <el-empty description="暂无数据,请先勾选"></el-empty>
                </div>
              </div>
            </div>
          </div>
        </template>
        <template slot="footer">
          <el-button
            v-for="(item, index) in butArr"
            :key="index"
            :size="item.size"
            :type="item.type"
            :icon="item.icon"
            :class="item.class"
            @click="item.clickFun"
            >{{ item.text }}</el-button
          >
        </template>
      </Dialogbox>
    </template>
  </div>
</template>

<script>
import Dialogbox from "@/components/Dialogbox/index";
import { hrmDeptsFullPersonnelTree } from "@/api/hrm/employee";
export default {
  components: {
    Dialogbox
  },
  data() {
    return {
      // 自定义接口数据
      customFun: null,
      oooooo: false,
      // 计数
      changeFlag: 1,
      // 宽度
      width: "200px",
      // 标题
      lable: "",
      // 选中值
      selectValue: [],
      // 回显值
      echoValue: [],
      // 字段名
      field: "",
      // 提示语
      placeholder: "",
      // 是否多选下拉框
      multiple: true,
      // 是否多选 -- 树
      multipleChoice: false,
      // 弹窗标题
      title: "",
      // 树结构唯一字段 默认id
      nodekey: "id",
      // 树结构绑定字段
      defaultProps: { children: "childes", label: "name" },
      // 选择框绑定字段
      optionsProps: { label: "name", value: "value" },
      // 弹窗显隐
      RenewalShow: false,
      // 选中内容
      checkedData: [],
      // 默认展开 需要加node-key
      defaultExpandedArr: [],
      // 默认选中 需要加node-key
      defaultCheckedArr: [],
      filterText: "",
      butArr: [
        {
          text: "确定",
          icon: "",
          class: "ok-btn",
          size: "small",
          type: "primary",
          clickFun: this.confirm
        },
        {
          text: "取消",
          icon: "",
          class: "cancel-btn",
          size: "small",
          type: "default",
          clickFun: this.cancel
        }
      ],
      // 1 为公司 2为部门 e 为人员,不同则禁用
      type: [],
      // 下拉框数据
      options: [],
      data: []
    };
  },
  props: {
    value: {
      type: true,
      default: ""
    },
    config: {
      type: Object,
      default: () => {
        return {
          lable: "标题", // 标题
          customFun: null,
          width: "200px", // 下拉框宽度
          selectValue: [], // 选中值
          field: "selectValue", // 字段名
          placeholder: "请选择", // 提示语
          title: "选择",
          nodekey: "id", // 唯一值
          // 树指定字段
          defaultProps: { children: "childes", label: "name" },
          // 下拉框指定字段
          optionsProps: {
            label: "name",
            value: "value"
          },
          // 1 为公司 2为部门 e 为人员,不同则禁用 A 为不禁用
          // [1,2,e]
          type: "A",
          multipleChoice: false //是否多选
        };
      }
    }
  },
  computed: {},
  watch: {
    config: {
      handler(val) {
        this.init();
        console.log("---初始化");
      },
      immediate: true,
      deep: true
    },
    value: {
      handler(val) {
        if (val) {
          this.selectValue = val.split(",");
        }
      },
      immediate: true,
      deep: true
    }
  },
  created() {
    this.gethrmDeptsFullPersonnelTree();
  }, // 生命周期 - 创建完成
  mounted() {}, // 生命周期 - 挂载完成
  beforeCreate() {}, // 生命周期 - 创建之前
  beforeMount() {}, // 生命周期 - 挂载之前
  beforeUpdate() {}, // 生命周期 - 更新之前
  updated() {}, // 生命周期 - 更新之后
  beforeDestroy() {}, // 生命周期 - 销毁之前
  destroyed() {}, // 生命周期 - 销毁完成
  activated() {}, // 如果页面有keep-alive缓存功能,这个函数会触发
  methods: {
    // 删除标签时触发
    removeTag(val) {
      let index = this.checkedData.findIndex(
        item => item[this.nodekey] === val
      );
      if (index !== -1) {
        this.checkedData.splice(index, 1);
        this.selectValue.splice(index, 1);
        this.defaultCheckedArr.splice(index, 1);
        this.defaultExpandedArr.splice(index, 1);
        this.options.splice(index, 1);
      }
      // 空数据默认展开第一级
      this.defaultExpandFirst();
      this.$emit("input", this.echoValue.join(",")); // v-model
      this.$emit("confirm", this.echoValue, this.checkedData, this.field);
    },
    // 过滤
    filterChange(val) {
      this.$refs.tree.filter(val);
    },
    // 控制单选多选
    handleCheckChange(node, list) {
      if (!this.multipleChoice) {
        if (list.checkedKeys.length == 2) {
          //单选实现
          this.$refs.tree.setCheckedKeys([node[this.nodekey]]);
        }
      }
    },
    // 获取树
    gethrmDeptsFullPersonnelTree() {
      // 是否存在自定义接口
      if (typeof this.customFun === "function" && this.customFun != null) {
        this.customFun().then(res => {
          if (res.code != 0) {
            this.dataErr();
            return;
          }
          if (res.data.length) {
            let list = res.data;
            // 禁用部分
            this.getDisabled(list);
            this.data = list;
            this.firstEcho();
          } else {
            this.dataErr();
            return;
          }
        });
      } else {
        hrmDeptsFullPersonnelTree().then(res => {
          if (res.code != 0) {
            this.dataErr();
            return;
          }
          if (res.data.length) {
            let list = res.data;
            // 禁用部分
            this.getDisabled(list);
            this.data = list;
            this.firstEcho();
          } else {
            this.dataErr();
            return;
          }
        });
      }
    },
    // 数据异常
    dataErr() {
      let err = {};
      err[this.defaultProps.label] = "网络异常,获取数据失败!";
      err[this.nodekey] = "ERROR";
      err.disabled = true;
      err[this.defaultProps.children] = [];
      this.data = [err];
    },
    // 根据type类型 默认禁用部分
    getDisabled(data) {
      // 1 为公司 2为部门 e 为人员
      if (this.type) {
        data.forEach(item => {
          // 当传入A的时候 全部可选
          if (this.type != "A") {
            if (
              !this.type.includes(
                this.customFun != null ? item.deptType : item.type
              )
            ) {
              item.disabled = true;
            }
          }
          // 判断是否存在子集
          if (
            item[this.defaultProps.children] &&
            item[this.defaultProps.children].length > 0
          ) {
            this.getDisabled(item[this.defaultProps.children]);
          }
        });
      }
    },
    // 初始化数据
    init() {
      const {
        lable,
        type,
        width,
        multipleChoice,
        selectValue,
        field,
        placeholder,
        title,
        nodekey,
        defaultProps,
        optionsProps,
        defaultExpandedArr,
        defaultCheckedArr,
        customFun
      } = this.config;
      // 自定义方法
      this.customFun = customFun != null ? customFun : null;
      // 标题
      this.lable = lable != undefined ? lable : "";
      // 选择类型 默认人员
      this.type = type != undefined ? type : [1, 2, "e"];
      // 宽度
      this.width = width != undefined ? width : "200px";
      // 是否多选-树
      this.multipleChoice =
        multipleChoice != undefined ? multipleChoice : false;
      // 选中值
      this.selectValue = selectValue != undefined ? selectValue : [];
      // 字段名
      this.field = field != undefined ? field : "value";
      // 提示语
      this.placeholder = placeholder != undefined ? placeholder : "请选择";
      // 弹窗标题
      this.title = title != undefined ? title : "选择";
      // 树结构唯一字段 默认id
      this.nodekey = nodekey != undefined ? nodekey : "id";
      // 树结构绑定字段
      this.defaultProps =
        defaultProps != undefined
          ? defaultProps
          : { children: "childes", label: "name" };
      // 选择框绑定字段
      this.optionsProps =
        optionsProps != undefined
          ? optionsProps
          : { label: "name", value: "value" };
      // 默认展开内容
      this.defaultExpandedArr =
        defaultExpandedArr != undefined ? defaultExpandedArr : [];
      // 默认选中内容
      this.defaultCheckedArr =
        defaultCheckedArr != undefined ? defaultCheckedArr : [];
      // 发生变化时调用 前提存在数据
      if (this.data && this.data.length) {
        this.firstEcho();
      }
    },
    // 首次回显
    firstEcho() {
      if (this.selectValue && this.selectValue.length) {
        this.$nextTick(() => {
          // 初始化
          this.options = [];
          this.filterText = "";
          this.checkedData = [];
          let arr = this.data;
          // 默认展开
          this.defaultExpandedArr = this.selectValue;
          // 默认选中
          this.defaultCheckedArr = this.selectValue;
          // 获取选中的节点
          let list = this.selectValue.map(item => this.findNode(item, arr));
          // 过滤掉空节点
          list = list.filter(item => item !== undefined);
          this.checkedData = list;
          this.checkedData.forEach((item, index) => {
            let obj = {};
            obj[this.optionsProps.label] = item[this.defaultProps.label];
            obj[this.optionsProps.value] = item[this.nodekey];
            this.options.push(obj);
          });
          this.echoValue = this.selectValue;
        });
      } else {
        // 没有任何选中
        this.checkedData = [];
        // 空数据默认展开第一级
        this.defaultExpandFirst();
      }
    },
    // 递归查找 找出对应的数据
    findNode(id, arr) {
      for (let i = 0; i < arr.length; i++) {
        if (arr[i][this.nodekey] == id) {
          return arr[i];
        }
        if (arr[i][this.defaultProps.children]) {
          let node = this.findNode(id, arr[i][this.defaultProps.children]);
          if (node) {
            return node;
          }
        }
      }
    },
    // 切换事件
    SelectionTreeChange() {},
    // 点击了
    selectClick(val) {
      this.RenewalShow = true;
    },
    // 确定
    confirm() {
      this.RenewalShow = false;
      // changeFlag 用来判断是否是默认回显,还是手动操作
      this.changeFlag += 1;
      this.reset(); // 重置
      this.checkedData.forEach((item, index) => {
        let obj = {};
        obj[this.optionsProps.label] = item[this.defaultProps.label];
        obj[this.optionsProps.value] = item[this.nodekey];
        this.options.push(obj);
        this.echoValue.push(item[this.nodekey]);
        this.selectValue.push(item[this.nodekey]);
        // 默认展开
        this.defaultExpandedArr.push(item[this.nodekey]);
        // 默认选中
        this.defaultCheckedArr.push(item[this.nodekey]);
      });
      // 空数据默认展开第一级
      this.defaultExpandFirst();
      this.closeMask(); // 蒙层层级
      this.$emit("input", this.echoValue.join(",")); // v-model
      this.$emit("confirm", this.echoValue, this.checkedData, this.field);
    },
    // 默认展开第一级
    defaultExpandFirst() {
      if (
        this.defaultExpandedArr.length == 0 &&
        this.data &&
        this.data.length
      ) {
        this.defaultExpandedArr.push(this.data[0][this.nodekey]);
      }
    },
    // 重置
    reset() {
      this.filterText = "";
      this.options = [];
      this.selectValue = [];
      this.echoValue = [];
      this.defaultExpandedArr = [];
      this.defaultCheckedArr = [];
    },
    // 取消
    cancel() {
      this.RenewalShow = false;
      if (this.data && this.data.length) {
        this.firstEcho();
      }
      this.closeMask();
    },
    // 关闭
    close() {
      this.RenewalShow = false;
      if (this.data && this.data.length) {
        this.firstEcho();
      }
      this.closeMask();
    },
    // 过滤
    filterNode(value, data) {
      if (!value) return true;
      return data[this.defaultProps.label].indexOf(value) !== -1;
    },
    // 节点选中
    checkChange(data, checked, indeterminate) {
      if (checked) {
        // 选中就新增
        this.checkedData.push(data);
      } else {
        // 取消选中就判断数组是否存在,存在及删除
        let index = this.checkedData.findIndex(
          item => item[this.nodekey] === data[this.nodekey]
        );
        if (index !== -1) {
          this.checkedData.splice(index, 1);
        }
      }
    },
    // 删除
    treeDel(val) {
      let index = this.checkedData.findIndex(
        item => item[this.nodekey] === val[this.nodekey]
      );
      if (index !== -1) {
        this.checkedData.splice(index, 1);
        this.removeCheckedKeys(this.checkedData);
      }
    },
    // 同步取消树选中-通过node
    removeCheckedKeys(arr) {
      this.$refs.tree.setCheckedNodes(arr);
    },
    // 多层级 关闭 蒙层还在的问题
    closeMask() {
      this.$nextTick(() => {
        let dom = document.getElementsByClassName("SelectionTree");
        for (let index = 0; index < dom.length; index++) {
          const element = dom[index];
          let model = element.getElementsByClassName("v-modal");
          if (model[0]) {
            model[0].style.zIndex = -1;
          }
        }
      });
    }
  }
};
</script>
<style scoped lang="scss">
/deep/ .popper-class {
  display: none;
}

.SelectionTree {
  .hb {
    width: 49%;
    height: 520px;
    border: 1px solid #ccc;
    padding: 8px;
    overflow: hidden;
    // border-radius: 4px;
  }

  .hb-c {
    margin: 8px 0px;
    overflow-y: scroll;
  }
  .hb-li:hover {
    background-color: #f5f7fa;
  }
}

/deep/ .el-dialog__header {
  border-bottom: 1px solid #ccc;
}

/deep/ .el-dialog__body {
  padding: 20px !important;
}

/deep/.el-select__tags {
  flex-wrap: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/deep/ .el-tag {
  background-color: #f3f7ff;
  max-width: 93%;
  color: #333333;
}

/deep/ .el-input--suffix {
  cursor: pointer;
}
/deep/ .el-select__tags-text {
  font-size: 13px !important;
}
.treeSearch {
  /deep/ .el-input__inner {
    border-color: #ccc !important;
  }
}
</style>

使用方法

HTML

    <SelectionTree
      :config="config"
      v-model="value"
      @confirm="confirm"
      ref="SelectionTree"
    />

JS

    import SelectionTree from "@/components/SelectionTree/index";
    export default {
        components: {
            SelectionTree
        },
        data(){
            return{
                value:"",
                config:{
                    // 自定义接口数据
                    customFun: null,
                    lable: "标题", // 标题
                    width: "200px", // 宽度
                    selectValue: [], // 选中值 用来回显
                    field: "selectValue", // 字段名
                    placeholder: "请选择", // 提示语
                    title: "选择", // 弹窗标题
                    nodekey: "id", // 树唯一值
                    // 树指定字段
                    defaultProps: {
                        children: "childes",
                        label: "name"
                    },
                    // 下拉框指定字段
                    optionsProps: {
                        label: "name",
                        value: "value"
                    },
                    // 1 为公司 2为部门 e 为人员,不同则禁用 A 为不禁用
                    // [1,2,'e']
                    type: "A",
                    multipleChoice: false //是否多选
                };
            }
        },
        methods:{
            // 确定事件 
            // echoValue 返回值
            // checkedData 被勾选的数组
            // field 字段名
            confirm(echoValue,checkedData,field){
                console.log(echoValue,checkedData,field,'confirm====>');
            }
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值