自定义树形筛选选择组件

先上效果图

思路:刚开始最上面我用了el-input,选择框里面内容用了el-input+el-tree使用,但后面发现最上面那个可以输入,那岂不是可以不需要下拉就可以使用,岂不是违背了写这个组件的初衷,所以后面改成div自定义框

组件用法:上面框是你点击下面树形后自动填写到上面去,树形上面的筛选数据框是筛选树形数据的

代码可能没考虑那么周全,暂时还没加上校验,加了禁用点击和选择,属性是disabled

前提:请安装element ui组件,不会的参照:安装Element UI

2024年4月8日 加入一些优化和校验

代码结构:

上组件代码:在components创建customSelectTree文件夹下创建index.vue

<template>
  <div>
    <div class="cTree">
      <!-- 可点击可下拉选择组件 -->
      <div class="cTree-input">
        <span v-if="title" class="cTree-input-title">{{ title }}:</span>

        <div style="white-space: nowrap; position: relative">
          <div v-if="showTool && value">
            <el-tooltip :content="value" placement="top" effect="light">
              <div class="cTree-input-value">{{ value }}</div>
            </el-tooltip>
          </div>
          <div v-else>
            <div class="cTree-input-value">{{ value }}</div>
          </div>

          <div class="cTree-input-value-icon">
            <i
              style="padding-right: 5px"
              class="el-icon-circle-close"
              v-show="value"
              @click.stop="(value = ''), (id = '')"
            ></i>
            <i
              :style="{ transform: visible ? 'rotate(180deg)' : '' }"
              class="el-icon-arrow-down"
              @click.stop="visible = !visible"
            ></i>
          </div>
        </div>
        <el-button @click="confirm">按钮</el-button>
      </div>
      <div>
        <div class="item__error" v-show="showError">{{ tips }}</div>
        <div class="cTree-box" v-if="visible">
          <div class="cTree-box-input">
            <el-input
              v-model="filterText"
              :placeholder="inputPlaceholder"
              clearable
            />
          </div>
          <div class="cTree-box-content">
            <el-tree
              ref="tree"
              node-key="id"
              default-expand-all
              highlight-current
              :expand-on-click-node="false"
              :data="treeList"
              :filter-node-method="filterNode"
              :props="defaultProps"
              @node-click="handleNodeClick"
            >
              <span
                class="custom-tree-node"
                slot-scope="{ node }"
                style="
                  width: 100%;
                  overflow: hidden;
                  text-overflow: ellipsis;
                  white-space: nowrap;
                "
              >
                <el-tooltip
                  v-if="node.label.length > 5"
                  :content="node.label"
                  placement="top"
                  effect="light"
                >
                  <span
                    :style="{
                      cursor: node.disabled === true ? 'not-allowed' : '',
                    }"
                    >{{ node.label }}</span
                  >
                </el-tooltip>
                <span
                  v-else
                  :style="{
                    cursor: node.disabled === true ? 'not-allowed' : '',
                  }"
                  >{{ node.label }}</span
                >
              </span>
            </el-tree>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'custonTree',
  props: {
    selectId: {
      type: [String, Number],
    },
    showTool: {
      type: Boolean,
      default: true,
    },
    // 传入下拉框数据
    treeList: {
      type: Array,
      default: () => [],
    },
    // 标题
    title: {
      type: String,
      default: '',
    },
    inputPlaceholder: {
      type: String,
      default: '输入可模糊查询',
    },
    defaultProps: {
      type: Object,
      default: () => ({
        children: 'children',
        label: 'label',
        disabled: function (data) {
          if (data.disabled === true) {
            return true;
          }
        },
      }),
    },
    tips: {
      type: String,
      default: '请输入',
    },
  },
  data() {
    return {
      showError: false, //是否展示错误提示
      value: '', //点击后显示在上面的值
      filterText: '',
      label: '',
      visible: false,
      id: '', //点击后后端需要的id
    };
  },
  mounted() {
    let that = this;
    document.addEventListener('click', (e) => {
      if (!that.$el.contains(e.target)) this.visible = false;
    });
  },
  created() {},
  watch: {
    selectId: {
      handler(val) {
        if (val) {
          console.log('kkkkkk', typeof val);
          const setChecked = (arr) => {
            arr?.map((item) => {
              if (item.id === val.toString()) {
                this.id = item.id;
                this.value = item.label;
              }
              if (item?.children?.length) {
                setChecked(item?.children);
              }
            });
          };
          setChecked(this.treeList);
        }
      },
      immediate: true,
    },
    visible: {
      handler(val) {
        if (val === true) {
          if (this.value) {
            this.$nextTick(() => {
              this.$refs.tree.setCurrentKey(this.id);
            });
          }
        }
      },
    },
    filterText(val) {
      this.$refs.tree.filter(val);
    },
    value: {
      handler(val) {
        if (!val) {
          this.showError = true;
        } else {
          this.showError = false;
        }
      },
    },
  },
  methods: {
    confirm() {
      if (!this.value) {
        this.showError = true;
      } else {
        this.showError = false;
      }
    },
    toValidate() {
      if (!this.value) {
        this.showError = true;
        return true;
      } else {
        this.showError = false;
        return false;
      }
    },
    disabled(data) {
      if (data.disabled === true) {
        return true;
      }
    },
    filterNode(value, data, node) {
      if (!value) return true;
      let _array = [];
      this.getReturnNode(node, _array, value);
      let result = false;
      _array.forEach((item) => {
        result = result || item;
      });
      return result;
    },
    getReturnNode(node, _array, value) {
      let isPass = node && node.label && node.label.indexOf(value) !== -1;
      isPass ? _array.push(isPass) : '';
      if (!isPass && node.children) {
        this.getReturnNode(node.children, _array, value);
      }
    },

    handleNodeClick(data) {
      if (data.disabled === true) return;
      this.value = data.label;
      this.id = data.id;
      this.visible = false;
      // this.$emit(data);
    },
    clickTitle() {
      if (this.visible === true) {
        this.visible = false;
      }
    },
  },
};
</script>
<style lang="scss" scoped>
.cTree {
  &-input {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    box-sizing: border-box;
    color: #ffffff;
    font-size: 14px;
    border-radius: 4px;
    // cursor: pointer;
    margin-right: 20px;
    ::v-deep .el-icon-arrow-down,
    .el-icon-circle-close {
      color: #c0c4cc;
      font-size: 18px;
      cursor: pointer;
    }
    &-title {
      font-size: 14px;
      color: #606266;
      line-height: 40px;
      padding: 0 12px 0 0;
      box-sizing: border-box;
    }
    &-value {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      -webkit-appearance: none;
      background-color: #fff;
      background-image: none;
      border-radius: 4px;
      border: 1px solid #dcdfe6;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      height: 40px;
      line-height: 40px;
      outline: 0;
      padding: 0 52px 0 15px;
      transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
      width: 250px;
      &-icon {
        position: absolute;
        right: 0;
        top: 50%;
        transform: translateY(-50%);
        padding-right: 10px;
      }
    }
  }

  &-box {
    position: absolute;
    user-select: none;
    border-radius: 6px;
    margin-top: 12px;
    width: 300px;
    z-index: 99;
    border: 1px solid #f0f0f0;
    box-shadow: 5px 5px 5px #efefef;
    &:after {
      content: '';
      position: absolute;
      margin-top: -11px;
      top: 0;
      left: 57%;
      width: 0;
      height: 0;
      border-left: 10px solid transparent;
      border-right: 10px solid transparent;
      border-bottom: 10px solid #e8eaec;
      // margin-left: 120px;
      // box-shadow: 10px 5px 5px #efefef;
    }
    &-input {
      padding: 2px 6px;
    }
    &-content {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      color: #606266;
      font-size: 14px;
      line-height: 34px;
      box-sizing: border-box;
      cursor: pointer;
      max-height: 250px;
      overflow-y: auto;
    }
  }
}
::v-deep .el-input__inner {
  position: relative;
}
.item__error {
  color: #f56c6c;
  font-size: 12px;
  line-height: 1;
  padding-top: 4px;
  // position: absolute;
  // top: 100%;
  // left: 0;
}
</style>

使用:

<template>
  <div id="app">
    <CustonBtn ref="cb" selectId="211" :treeList="treeList" />
    <CustonBtn ref="cb" selectId="11" :treeList="treeList" />
    <el-button style="height: 40px" @click="toValidate">校验</el-button>
  </div>
</template>

<script>
import CustonBtn from '@/components/customSelectTree/index.vue';

export default {
  name: 'App',
  components: {
    CustonBtn,
  },
  data() {
    return {
      treeList: [
        {
          label: '一级1',
          disabled: true,
          id: '1',
          children: [
            {
              label: '二级1-1',
              disabled: true,
              id: '11',
              children: [
                {
                  label: '三级1-1-1',
                  disabled: false,
                  id: '111',
                },
              ],
            },
          ],
        },
        {
          label: '一级2',
          disabled: true,
          id: '2',
          children: [
            {
              label: '二级2-1',
              disabled: true,
              id: '21',
              children: [
                {
                  label:
                    '三级2-1-1-哈里发哈伦返回拉法基奥利弗哈喽蜡黄蜡黄法拉回复啦好伐好伐立法会',
                  disabled: false,
                  id: '211',
                },
              ],
            },
            {
              label: '二级2-2',
              disabled: true,
              id: '22',
              children: [
                {
                  label: '三级2-2-1',
                  disabled: false,
                  id: '221',
                },
              ],
            },
          ],
        },
        {
          label: '一级3',
          disabled: true,
          id: '3',
          children: [
            {
              label: '二级3-1',
              disabled: true,
              id: '31',
              children: [
                {
                  label: '三级3-1-1',
                  disabled: false,
                  id: '311',
                },
              ],
            },
            {
              label: '二级3-2',
              disabled: true,
              id: '32',
              children: [
                {
                  label: '三级3-2-1',
                  disabled: false,
                  id: '321',
                },
              ],
            },
          ],
        },
      ],
    };
  },
  methods: {
    // true 校验不通过 false 校验通过
    toValidate() {
      let isTrue = this.$refs.cb.toValidate();
      console.log('isTrue', isTrue);
    },
  },
};
</script>

<style scoped>
#app {
  display: flex;
}
</style>

更新记录:

2024/4/17

增加外部传参selectId去匹配下拉树形数据并高亮,加入当树形和输入框里面字体太长时候显示...并且Tooltip 文字提示,其他一些优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值