项目开发中需要用到部门下拉选择框(层级下拉选择器),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结合使用便可实现该效果,但毕竟不是修改源码,仍可能会产生意外的问题,实现效果如下:
留言
该功能为团队需求封装,用于个人、团队开发分享,有部分个性化代码,转载套用请注明出处