Vue Element-Ui Cascader实现兼容远程关键字搜索

2,602 阅读2分钟

项目开发中需要用到部门下拉选择框(层级下拉选择器),element ui中的el-cascader就很适合,但它有个缺陷是缺乏远程搜索功能,而远程搜索效果el-select就可以实现,于是就想着把这两个组件进行组合

本封装组件支持的功能有:层级选择、单选、动态加载、可搜索(搜索时展示未平铺结构)、可清空,实现代码如下:

1.template部分

通过变量控制显示层级下拉选择器还是搜索下拉选择器

<template>
    <div>
        <el-select v-model="search" filterable remote v-show="search" class="w-100" @change="handleChange"
            reserve-keyword clearable ref="select" :placeholder="placeholder" :remote-method="filterSearch" :loading="loading">
            <el-option v-for="item in searchList" :key="item.id" :label="item.name" :value="item.id"></el-option>
        </el-select>
        <el-cascader v-model="value" ref="cascader" :options="departmentDataList" :props="defaultProps" clearable :placeholder="placeholder" :show-all-levels="false"
            filterable :before-filter="cascaderSearch" v-if="!search" @change="handleChange" class="w-100"></el-cascader>
    </div>
</template>

2.script部分

<script>
import SelectDeptSvc from "./index.svc";
export default {
  name: "deptCascader",
  props: {
    placeholder: {
      type: String,
      default: '请输入'
    },
    change: {
      type: Function
    },
  },
  data() {
      let vm = this;
    return {
        value: 0,
        search: '',
        loading: false,
        defaultProps: {
            checkStrictly: true,
            emitPath: false,
            lazy: true,
            // 懒加载函数由于在data内部,所以使用this会指向data本身,需要提前声明vue实例变量调用实例方法
            lazyLoad (data, resolve) {
                let dataObj = data.data;
                // console.log(dataObj);
                if (dataObj && dataObj.id) {
                    // 每次点击部分都会触发加载事件,若已经加载则不再加载,避免重复数据
                    if (dataObj.children && dataObj.children.length) {
                        resolve([]);
                    } else {
                        // 调用加载下级子部门接口
                        vm.getChildren(dataObj).then(res => {
                            resolve(res);
                        })
                    }
                }
            },
            children: 'children',
            label: 'name',
            value: 'id',
            leaf: 'isLeaf' // 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效
        },
        searchList: [],
        departmentDataList: [],
        founded: false
    };
  },
  methods: {
    // 首次获取第一层部门,由于第一层为根部门,会返回一条数据
    getData() {
      SelectDeptSvc.getDeptTreeList(0).then((res) => {
        res.forEach((val) => {
          val.isLeaf = !!val.is_leaf;
          val.children = [];
        });
        this.departmentDataList = res || [];
        // 若已有默认值,且该默认值不是根部门,则自动加载下级部门搜索
        if (this.value && this.value != res[0].id) {
            this.getChildren(res[0]).then(res => {
                this.loadNodes(res);
            })
        }
      });
    },
    // 已有默认值时加载下级部门搜索
    // 已加载默认值部门时(this.founded为true)不再自动加载,否则加载所有当前层有子部门的子部门数据
    loadNodes(nodes) {
        var arr = [];
        nodes.forEach(val => {
            if (val.id == this.value) {
                this.founded = true;
            }
        })
        if (!this.founded) {
            nodes.forEach(val => {
                if (!val.isLeaf) {
                    arr.push(this.getChildren(val))
                }
            })
            Promise.all(arr).then(res => {
                console.log(res)
                var list  = [];
                res.forEach(val => {
                    list = list.concat(val)
                })
                this.loadNodes(list)
            }, err => {
                console.log(err)
            })
        }
        

    },
    // 获取下级部门接口
    getChildren(data) {
        return new Promise((resolve, reject) => {
            SelectDeptSvc.getDeptTreeList(0, data.id, 0, 0).then((res) => {
                // this.departmentDataList = res || {}
                let childrenData = res || [];
                var dept_ids = [];
                childrenData.forEach((val) => {
                    val.isLeaf = !!val.l;
                    dept_ids.push(val.id);
                    val.children = [];
                });
                SelectDeptSvc.getDeptNameList(dept_ids).then((res) => {
                    for (const id in res) {
                        var num = id - 0;
                        childrenData[dept_ids.indexOf(num)].name = res[id];
                    }
                    data.children = childrenData;
                    resolve(childrenData);
                }, err => {
                    reject(err)
                });
            });
        })
      
    },
    // 远程搜索
    filterSearch(value) {
        SelectDeptSvc.getDeptSearchList(value).then((res) => {
            res.list.forEach((val) => {
                val.isLeaf = true;
                val.level = 1;
            });
            this.searchList = res.list || [];
        })
    },
    // 重定义层级选择器的搜索逻辑,切换为select下拉选择器
    cascaderSearch(value) {
        this.search = value;
        this.searchList = [];
        this.filterSearch(value);
        setTimeout(() => {
            this.$refs.select.focus()
        })
        return false
    },
    // 兼容两种选择器的选择结果,将选择值的名称和id传给父组件
    handleChange(node) {
        if (this.change) {
            if (node) {
                if (this.search) {
                    console.log(this.$refs["select"].selected, this.searchList)
                    var name = '';
                    this.searchList.forEach(val => {
                        if (node == val.id) {
                            name = val.name
                        }
                    })
                    this.change({name: name, id: node})
                } else {
                    // this.$refs["cascader"].getCheckedNodes()[0].label
                    this.change({name: '', id: node})
                }
            } else {
                this.change({name: '', id: 0})
            }
            
        }
    },
    // 初始化,加载根部门   由父组件调用触发
    init(id) {
        // value为默认值 若有值需要默认加载数据
        this.value = id;
        this.getData();
    }
  },
  // 卸载前删除可能打开的下拉弹框元素,微前端环境下的bug处理
  beforeDestroy() {
    var dropdown = document.querySelector('.el-popper.el-cascader__dropdown') || document.querySelector('.el-popper.el-select-dropdown');
    if (dropdown) {
        dropdown.remove();
    }
  }
};
</script>

3.css部分

设置每一层的展示宽度,防止样式混乱

<style lang="scss">
.el-cascader-menu {
    max-width: 200px;
}
</style>

4.父组件引入

<dept-cascader placeholder="请选择部门" :change="chooseDep" ref="deptCascader"></dept-cascader>
import deptCascader from '@/components/depTree/cascader.vue'
export default {
  name: 'Info',
  components: {
    deptCascader
  },
  data () {
      return {
          dept: {}
      }
  },
  methods: {
    chooseDep (data) {
        this.dept.name = data.name
        this.dept.id = data.id
    }
  },
  mounted() {
    // 无默认值时加载后初始化,否则当获取到默认值时初始化
    this.$refs.deptCascader.init(this.dept.id)
  }
}

总结

将el-select和el-cascader结合使用便可实现该效果,但毕竟不是修改源码,仍可能会产生意外的问题,实现效果如下:

微信图片_20220508173227.png

微信图片_20220508173154.png

留言

该功能为团队需求封装,用于个人、团队开发分享,有部分个性化代码,转载套用请注明出处