element 封装 select-tree

<template>
  <el-popover
    ref="popover"
    style="display: block; height: 32px; line-height: 32px; padding: 0px"
    trigger="click"
    :disabled="disabled"
    :width="treeWidth"
    @show="onShowPopover"
    @hide="onHidePopover"
    :append-to-body="AppendToBody"
  >
    <el-scrollbar :id="id" :style="{ height: treeHeight }">
      <el-tree
        ref="tree"
        :indent="0"
        highlight-current
        :style="`width: ${treeWidth}`"
        :data="data"
        :props="props"
        :expand-on-click-node="false"
        :filter-node-method="filterNode"
        :default-expand-all="expandAll"
        :default-expanded-keys="[value || '']"
        :node-key="props.value"
        :show-checkbox="multiple"
        :default-checked-keys="defaultKeys"
        @node-click="onClickNode"
        @check="onCheckNode"
      >
        <span class="custom-tree-node" slot-scope="{ data }">
          <slot :data="data" v-if="!!isSlot"></slot>
          <span v-else>
            <i
              v-if="showIcon"
              :class="
                data[props.icon]
                  ? data[props.icon]
                  : data[props.children] && data[props.children].length
                  ? 'iconfont iconsucai'
                  : 'iconfont iconshujiedian'
              "
            ></i>
            {{ data[props.label] }}
          </span>
        </span>
      </el-tree>
    </el-scrollbar>

    <el-input
      slot="reference"
      ref="input"
      v-model="labelModel"
      :clearable="clearable"
      :disabled="disabled"
      :style="`width: ${width}px`"
      :class="{ rotate: showStatus }"
      suffix-icon="el-icon-arrow-down"
      :placeholder="placeholder"
      :readonly="readonly"
      @input="handleLabelChange"
    ></el-input>
  </el-popover>
</template>

<script>
export default {
  props: {
    // 接收绑定参数
    disabled: {
      type: Boolean,
      default: false, //禁用
    },
    expandAll: {
      type: Boolean,
      default: false, //展开所有
    },
    value: {
      type: String,
      default: "", //值
    },
    // 输入框宽度
    width: String,
    popoverWidth: String,
    treeHeight: {
      type: String,
      default: "300px", //值
    },
    // 选项数据
    options: {
      type: Array,
      required: true,
    },
    banField: {
      //字段判断 哪些节点禁止被单击
      type: Object,
      default: () => ({
        name: "",
        value: "",
      }),
    },
    banParent: {
      //只容许选择叶子节点
      type: Boolean,
      default: false,
    },
    banRoot: {
      //根节点不允许选择 parentId=0
      type: Boolean,
      default: false,
    },
    banData: {
      //节点不允许选择 data
      type: Boolean,
      default: false,
    },
    // 输入框占位符
    placeholder: {
      type: String,
      required: false,
      default: "请选择",
    },
    //可以为空
    clearable: {
      type: Boolean,
      default: true,
    },
    //只读
    readonly: {
      type: Boolean,
      default: false,
    },
    // 树节点配置选项
    props: {
      type: Object,
      required: false,
      default: () => ({
        parent: "parentId",
        value: "id",
        label: "text",
        children: "children",
        icon: "icon",
      }),
    },
    multiple: {
      //多选
      type: Boolean,
      default: false,
    },
    AppendToBody: {
      //弹窗添加到body
      type: Boolean,
      default: false,
    },
    showIcon: {
      //显示icon
      type: Boolean,
      default: true,
    },
    isSlot: {
      //是否插槽
      type: Boolean,
      default: false,
    },
    // 默认选中的节点
    defaultKeys: {
      type: Array,
      default: () => [],
    },
  },

  // 设置绑定参数
  model: {
    prop: "value",
    event: "selected",
  },
  computed: {
    // 是否为树状结构数据
    dataType() {
      // const jsonStr = JSON.stringify(this.options);

      // return jsonStr.indexOf(this.props.children) !== -1;

      let isFlag = false;
      for (let i = 0; i < this.options.length; i++) {
        isFlag = !!this.options[i][this.props.children];
      }
      return isFlag;
    },
    // 若非树状结构,则转化为树状结构数据
    data() {
      return this.dataType ? this.options : this.switchTree();
    },
  },
  watch: {
    data(val, oldValue) {
      if (JSON.stringify(val) != JSON.stringify(oldValue)) {
        this.labelModel = this.queryTree(val, this.value) || this.value;
      }
    },
    value(val) {
      this.labelModel = this.queryTree(this.data, val) || this.value;
    },
  },
  data() {
    return {
      // 树状菜单显示状态
      showStatus: false,
      // 菜单宽度
      treeWidth: "auto",
      // 输入框显示值
      labelModel: "",
      // 实际请求传值
      valueModel: "0",
      //单击的node
      nodeModel: null,
      id: "select-tree-" + new Date().getTime(),
      checkNodes: [],
    };
  },
  created() {
    // 检测输入框原有值并显示对应 label
    if (this.value) {
      this.labelModel = this.queryTree(this.data, this.value) || this.value;
    }
    // 获取输入框宽度同步至树状菜单宽度
    this.$nextTick(() => {
      //  this.treeWidth = `100px`;
      this.treeWidth = `${
        this.popoverWidth ||
        this.width ||
        (this.$refs.input
          ? this.$refs.input.$refs.input.clientWidth - 8
          : this.width)
      }px`;
    });
  },

  methods: {
    onCheckNode(node) {
      const nodes = this.$refs.tree.getCheckedNodes();
      this.$emit("checked", nodes);
    },
    // 单击节点
    onClickNode(node) {
      let banField = this.banField;

      if (banField.name) {
        let data = node.data ? JSON.parse(node.data) : node;
        if (data[banField.name] == banField.value) {
          return;
        }
      }
      if (this.banParent) {
        let children = node[this.props.children] || [];
        if (children.length > 0) {
          return;
        }
      }

      if (this.banRoot) {
        if (node[this.props.parent] == "0") {
          return;
        }
      }

      if (this.banData) {
        if (!node.data) {
          return;
        }
      }

      this.labelModel = node[this.props.label];
      this.valueModel = node[this.props.value];
      this.nodeModel = node;
      this.onCloseTree();
    },

    // 偏平数组转化为树状层级结构
    switchTree() {
      return this.cleanChildren(this.buildTree(this.options, "0"));
    },
    // 隐藏树状菜单
    onCloseTree() {
      this.$refs.popover.showPopper = false;
    },
    // 显示时触发
    onShowPopover() {
      this.showStatus = true;
      this.$nextTick(() => {
        this.$refs.tree.filter(false);
        if (this.value) {
          this.$refs.tree.setCurrentKey(this.value);
        }
        setTimeout(() => {
          let scrollbar_wrap = $("#" + this.id).find(".el-scrollbar__wrap");
          scrollbar_wrap.css("overflow", "auto");
        }, 100);
      });
    },
    // 隐藏时触发
    onHidePopover() {
      this.showStatus = false;
      if (this.nodeModel && !this.multiple) {
        //获取node
        let node = this.$refs.tree.getNode(this.valueModel);

        this.$emit(
          "selected",
          this.valueModel,
          this.nodeModel || {},
          node || {}
        );
        this.$emit("checked", node.data);
      }
    },
    // 树节点过滤方法
    filterNode(query, data) {
      if (!query) return true;
      return (
        data[this.props.label] && data[this.props.label].indexOf(query) !== -1
      );
    },
    // 搜索树状数据中的 ID
    queryTree(tree, id) {
      let stark = [];
      stark = stark.concat(tree);
      while (stark.length) {
        const temp = stark.shift();
        if (temp[this.props.children]) {
          stark = stark.concat(temp[this.props.children]);
        }
        if (temp[this.props.value] === id) {
          return temp[this.props.label] || this.value;
        }
      }

      return "";
    },
    // 将一维的扁平数组转换为多层级对象
    buildTree(data, id = "0") {
      const fa = (parentId) => {
        const temp = [];
        for (let i = 0; i < data.length; i++) {
          const n = data[i];
          if (n[this.props.parent] === parentId) {
            n[this.props.children] = fa(n.rowGuid);
            temp.push(n);
          }
        }
        return temp;
      };
      return fa(id);
    },
    // 清除空 children项
    cleanChildren(data) {
      const fa = (list) => {
        list.map((e) => {
          if (e.children.length) {
            fa(e.children);
          } else {
            delete e.children;
          }
          return e;
        });
        return list;
      };
      return fa(data);
    },
    handleLabelChange(val) {
      if (!val) {
        this.valueModel = "";
        this.nodeModel = null;
        let node = this.$refs.tree.getNode(this.valueModel);
        this.$emit("selected", this.valueModel, this.nodeModel || {}, node);

        //停止后续事件冒泡 弹出
        window.event.stopPropagation();
      }

      this.$refs.tree.filter(val);
    },
  },
};
</script>

<style lang="scss" scoped>
.el-input.el-input--suffix {
  cursor: pointer;
  overflow: hidden;
}

.el-input.el-input--suffix.rotate .el-input__suffix {
  transform: rotate(180deg);
}
.custom-tree-node {
  font-weight: 500;
}
</style>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值