自定义树形筛选选择组件

先上效果图

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

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

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

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

代码结构:

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

复制代码
<template>
  <div class="cTree">
    <!-- 可点击可下拉选择组件 -->
    <div class="cTree-input">
      <div style="white-space: nowrap; position: relative">
        <div class="cTree-input-value">{{ value }}</div>
        <div class="cTree-input-value-icon">
          <i
            style="padding-right: 5px"
            class="el-icon-circle-close"
            v-show="value"
            @click.stop="value = ''"
          ></i>
          <i
            :style="{ transform: visible ? 'rotate(180deg)' : '' }"
            class="el-icon-arrow-down"
            @click.stop="visible = !visible"
          ></i>
        </div>
      </div>
    </div>
    <div class="cTree-box" v-if="visible">
      <div class="cTree-box-input">
        <el-input v-model="filterText" placeholder="筛选数据" clearable />
      </div>
      <div class="cTree-box-content">
        <el-tree
          ref="tree"
          node-key="label"
          :highlight-current="true"
          :expand-on-click-node="false"
          :data="treeList"
          :filter-node-method="filterNode"
          :props="defaultProps"
          @node-click="handleNodeClick"
        >
          <span class="custom-tree-node" slot-scope="{ node }">
            <span
              :style="{ cursor: node.disabled === true ? 'not-allowed' : '' }"
              >{{ node.label }}</span
            >
          </span>
        </el-tree>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'custonTree',
  props: {
    // 传入下拉框数据
    treeList: {
      type: Array,
      default: () => [],
    },
    defaultProps: {
      type: Object,
      default: () => ({
        children: 'children',
        label: 'label',
        disabled: function (data) {
          if (data.disabled === true) {
            return true;
          }
        },
      }),
    },
  },
  data() {
    return {
      value: '',
      filterText: '',
      label: '',
      visible: false,
    };
  },
  mounted() {
    let that = this;
    document.addEventListener('click', (e) => {
      if (!that.$el.contains(e.target)) this.visible = false;
    });
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val);
    },
  },
  methods: {
    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) {
      console.log('node', node.data);
      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.$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;
    }
    &-value {
      -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 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;
}
</style>

使用:

复制代码
<template>
  <div id="app">
    <CustonBtn :treeList="treeList" />
  </div>
</template>

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

export default {
  name: 'App',
  components: {
    CustonBtn,
  },
  data() {
    return {
      treeList: [
        {
          label: '一级1',
          disabled: true,
          children: [
            {
              label: '二级1-1',
              disabled: true,
              children: [
                {
                  label: '三级1-1-1',
                  disabled: false,
                },
              ],
            },
          ],
        },
        {
          label: '一级2',
          disabled: true,
          children: [
            {
              label: '二级2-1',
              disabled: true,
              children: [
                {
                  label: '三级2-1-1',
                  disabled: false,
                },
              ],
            },
            {
              label: '二级2-2',
              disabled: true,
              children: [
                {
                  label: '三级2-2-1',
                  disabled: false,
                },
              ],
            },
          ],
        },
        {
          label: '一级3',
          disabled: true,
          children: [
            {
              label: '二级3-1',
              disabled: true,
              children: [
                {
                  label: '三级3-1-1',
                  disabled: false,
                },
              ],
            },
            {
              label: '二级3-2',
              disabled: true,
              children: [
                {
                  label: '三级3-2-1',
                  disabled: false,
                },
              ],
            },
          ],
        },
      ],
    };
  },
};
</script>

<style></style>
相关推荐
weifont2 小时前
聊一聊Electron中Chromium多进程架构
javascript·架构·electron
大得3692 小时前
electron结合vue,直接访问静态文件如何跳转访问路径
javascript·vue.js·electron
水银嘻嘻4 小时前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
it_remember4 小时前
新建一个reactnative 0.72.0的项目
javascript·react native·react.js
小嘟嚷ovo4 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i5 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观5 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰5 小时前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
敲代码的小吉米5 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
是千千千熠啊5 小时前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js