vue2结合element-ui实现TreeSelect 树选择功能

需求背景

在日常开发中,我们会遇见很多不同的业务需求。如果让你用element-ui实现一个 tree-select 组件,你会怎么做?

这个组件在 element-plus 中是有这个组件存在的,但是在 element-ui 中是没有的。

可能你会直接使用 element-plus 组件库,或者其他组件库。但是若你的项目目前的基于vue2和element-ui进行开发的呢?

下面这种思路利用 el-tree 和 el-select 进行嵌套从而实现我们想要的 tree-select 组件

最终效果
大致思路:

el-select和el-tree进行嵌套,将el-tree放到el-option里,循环遍历el-option,同时定义一个方法比如:formatData,对树形数据进行递归处理,这样就可以实现无论嵌套的层级有几层都可以正常渲染在界面上

利用 v-model 和 update:selectValue 实现父子组件之间的双向通信,同时利用computed进行监听以实现实时更新

组件中的 v-model

我们在input 中可以使用v-model来完成双向绑定:

  •  这个时候往往会非常方便,因为v-model默认会帮助我们完成两件事:
  •  v-bind:value的数据绑定和@input的事件监听;

如果我们现在封装了一个组件 ,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢?

当我们在组件上使用的时候,等价于如下的操作:

  •  我们会发现和input元素不同的只是属性的名称和事件触发的名称而已;

如果我们希望绑定多个属性呢?

  •  也就是我们希望在一个组件上使用多个v-model是否可以实现呢?
  •  我们知道,默认情况下的v-model其实是绑定了 modelValue 属性和 @update:modelValue的事件;
  •  如果我们希望绑定更多,可以给v-model传入一个参数,那么这个参数的名称就是我们绑定属性的名称;

实现代码示例

子组件:

复制代码
<template>
  <div>
    <h4>Counter: {{modelValue}}</h4>
    <button @click="changeCounter">修改数据</button>
  </div>
</template>

<script>
export default {
  props: {
    modelValue:  {
      type: Number
    },
  },
  emits: ['update:modelValue'],
  methods: {
    changeCounter(){
      this.$emit('update:modelValue',101)
    }
  }
}
</script>

父组件:

复制代码
<template>
  <!-- <child v-model="appCounter" /> -->
  <!-- 等同于如下做法:modelValue--默认
    可以自定义名称,通过 v-model:counter 类似于这种格式
   -->
  <child :modelValue="appCounter" @update:modelValue="appCounter = $event" />
</template>

<script>
import child from '@/components/child.vue'
export default {
  components: {
    child
  },
  data() {
    return {
      appCounter: 100,
    };
  },
  methods: {
    
  },
};
</script>

有了上面的知识,那么下面实现就很简单了,这里直接上代码

组件封装

子组件:TreeSelect.vue

复制代码
<template>
  <div class="app-container" style="padding: 0">
    <el-select
      class="main-select-tree"
      ref="selectTree"
      v-model="value"
      style="width: 240px"
      clearable
      @clear="clearSelectInput"
    >
      <el-input
        style="width: 220px; margin-left: 10px; margin-bottom: 10px"
        placeholder="输入关键字进行过滤"
        v-model="filterText"
        clearable
      >
      </el-input>
      <el-option
        v-for="item in formatData(data)"
        :key="item.value"
        :label="item.label"
        :value="item.value"
        style="display: none"
      />
      <el-tree
        class="main-select-el-tree"
        ref="selecteltree"
        :data="data"
        node-key="id"
        highlight-current
        :props="defaultProps"
        @node-click="handleNodeClick"
        :current-node-key="value"
        :expand-on-click-node="true"
        default-expand-all
        :filter-node-method="filterNode"
      />
    </el-select>
  </div>
</template>
    
<script>
export default {
  props: {
    selectValue: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      filterText: "",
      value: "",
      data: [
        {
          id: 1,
          label: "云南",
          children: [
            {
              id: 2,
              label: "昆明",
              children: [
                {
                  id: 3,
                  label: "五华区",
                  children: [
                    {
                      id: 8,
                      label: "xx街道",
                      children: [
                        {
                          id: 81,
                          label: "yy社区",
                          children: [{ id: 82, label: "北辰小区" }],
                        },
                      ],
                    },
                  ],
                },
                { id: 4, label: "盘龙区" },
              ],
            },
          ],
        },
        {
          id: 5,
          label: "湖南",
          children: [
            { id: 6, label: "长沙" },
            { id: 7, label: "永州" },
          ],
        },
        {
          id: 12,
          label: "重庆",
          children: [
            { id: 10, label: "渝北" },
            { id: 9, label: "合川" },
          ],
        },
        {
          id: 13,
          label: "江苏",
          children: [{ id: 14, label: "盐城" }],
        },
      ],
      defaultProps: {
        children: "children",
        label: "label",
      },
    };
  },
  watch: {
    filterText(val) {
      this.$refs.selecteltree.filter(val);
    },
  },
  methods: {
    filterNode(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    },
    // 递归遍历数据
    formatData(data) {
      let options = [];
      const formatDataRecursive = (data) => {
        data.forEach((item) => {
          options.push({ label: item.label, value: item.id });
          if (item.children && item.children.length > 0) {
            formatDataRecursive(item.children);
          }
        });
      };
      formatDataRecursive(data);
      return options;
    },
    // 点击事件
    handleNodeClick(node) {
      this.value = node.id;
      this.$refs.selectTree.blur();
      this.$emit('update:selectValue', node.label);
    },
    // 清空事件
    clearSelectInput() {
        this.$emit('update:selectValue', '');
        // 获取 el-tree 实例的引用
        const elTree = this.$refs.selecteltree;
       // 将当前选中的节点设置为 null
       elTree.setCurrentKey(null);
    },
  },
};
</script>

<style>
.main-select-el-tree .el-tree-node .is-current > .el-tree-node__content {
  font-weight: bold;
  color: #409eff;
}
.main-select-el-tree .el-tree-node.is-current > .el-tree-node__content {
  font-weight: bold;
  color: #409eff;
}
</style>
使用方式
复制代码
<TreeSelect v-model="selectedValue" @update:selectValue="handleSelectValueChange"></TreeSelect>

<el-button size="medium" :disabled="todoIsTotal">交接当前{{ tableData.length }}条任务</el-button>
                
import TreeSelect from "./TreeSelect.vue";

export default {
    components: {
        TreeSelect,
    },
    data() {
        selectedValue: "",
    },
    computed: {
        todoIsTotal() {
            return this.selectedValue === "";
        },
    },
    methods: {
        handleSelectValueChange(value) {
            if (value && value.length > 0) {
                this.selectedValue = value;
            } else {
                this.selectedValue = "";
            }
        },
    },
}
相关推荐
周之鸥36 分钟前
使用 Electron 打包可执行文件和资源:完整实战教程
前端·javascript·electron
前端snow1 小时前
前端全栈第二课:用typeorm向数据库添加数据---一对多关系
前端·javascript
全栈老李技术面试1 小时前
【高频考点精讲】async/await原理剖析:Generator和Promise的完美结合
前端·javascript·css·vue·html·react·面试题
kadog1 小时前
PubMed PDF下载 cloudpmc-viewer-pow逆向
前端·javascript·人工智能·爬虫·pdf
乌夷2 小时前
使用spring boot vue 上传mp4转码为dash并播放
vue.js·spring boot·dash
fxshy2 小时前
ai聊天流式响应,阻塞式和流式响应 nginx遇到的坑
运维·javascript·nginx
这颗橘子不太甜QAQ3 小时前
Husky使用技巧
javascript·git·代码规范
장숙혜3 小时前
ElementUi的tabs样式太难修改,自定义tabs标签页
前端·javascript
火星思想3 小时前
Promise 核心知识点(非基础)
前端·javascript·面试
前端大白话3 小时前
炸裂!10个 React 实战技巧,让你的代码从“青铜”秒变“王者”
前端·javascript·react.js