自定义树形筛选选择组件

先上效果图

思路:刚开始最上面我用了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>
相关推荐
m0_748230943 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681011 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安1 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
编程百晓君2 小时前
一文解释清楚OpenHarmony面向全场景的分布式操作系统
vue.js
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235612 小时前
web 渗透学习指南——初学者防入狱篇
前端
℘团子এ2 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel