ElementUI cascader级联动态加载回显和搜索看这个就够了
UI
以下是思考和开发的过程,不感兴趣可以直接看使用文档。
https://github.com/zhuss/lazy-cascadergithub.com
为什么要再写一篇呢?
当然不是思想觉悟有多高,还不是因为产品提的需求,产品哭哭唧唧的说:“类目太多啦,我要有一个搜索的功能。”
一开始我是拒绝的,毕竟上一次为了解决回显问题,耗费了一波本来就不多的头发,可是后来想一想,都是打工人,打工人和打工人应该是相亲相爱的一家人。
既然接下这个锅,那就想办法解决吧。
最开始,延续的原有的思路,既然回显可以,那搜索应该也不在话下,所以就埋头去研究官方文档,然后发现了 filter-method 和 before-filter 这两个方法。
尝试过之后就pass掉了filter-method,这个只是在选择的时候判断节点是否匹配,不适合动态请求和处理数据。
而 before-filter 方法是可以的,而且文档写的比较明确。
筛选之前的钩子,参数为输入的值,若返回 false 或者返回 Promise 且被 reject,则停止筛选
如果我们在方法中根据输入值去请求后端接口,拿到一组备选项,然后根据拿到的备选项动态更新我们的options参数,那么就可以在组件中筛选出我们想要的节点了吧,至于动态更新options的方法,可以同上一篇的回显逻辑类似。
按照这个思路,同事在一番倒腾之后,确实可以实现动态搜索的需求。
但是,这样处理是有不足的地方的。
1、产品设计的UI和ElementUI的组件并不一致,交互方式也不一样。
2、在动态更新options参数的时候会动态请求很多节点数据,而这些节点大部分都是不需要展示的。
所以,为了解决这两个问题,我又陷入的沉思,甚至想不看ElementUI手动撸一个组件(想想而已)。
那既然原有组件在UI上没办满足产品需求,那我们就自己写这个UI吧,顺着这个思路,那我们就需要用到一个关键的东西,级联面板。
因为上图UI的组成部分就这么几个东西。
输入框
Popover 弹出框
搜索选择框
级联选择面板
级联选择面板的值仅仅是一个数据,也就是节点id的路径数组,那我们就必须根据这个值,然后遍历options得到对应的label的数组,显示在输入框中。
通过搜索拿到的数据也可以拿到一个节点的值。
那么我们只需要根据值的变化,动态请求需求展示的节点,获取到对应的label即可。
于是就有了最紧要的一段代码。
html
/**格式化id=>object */ async getObject(id) { let options = this.options; let nameArray = []; for (let i = 0; i < id.length; i++) { let index = options.findIndex(item => { return item[this.props.value] == id[i]; }); nameArray.push(options[index][this.props.label]); if (i < id.length - 1 && options[index].children == undefined) { let list = new Promise(resolve => { this.props.lazyLoad(id[i], list => { resolve(list); }); }); this.$set(options[index], "children", await list); options = options[index].children; } else { options = options[index].children; } } return { value: id, label: nameArray }; }
解决了这个问题,基本上就已经实现了回显了。
至于搜索,直接用的ElementUI的组件autocomplete就可以了,最后在仿照cascader的参数封装这个组件的,把需要的参数暴露出去就可以了。
完整的代码看下
<template> <div class="lazy-cascader" :style="{ width: width }"> <!-- 禁用状态 --> <div v-if="disabled" class="el-input__inner lazy-cascader-input lazy-cascader-input-disabled" > <span class="lazy-cascader-placeholder" v-show="placeholderVisible"> {{ placeholder }} </span> <div class="lazy-cascader-tags" v-if="props.multiple"> <el-tag class="lazy-cascader-tag" type="info" disable-transitions v-for="(item, index) in labelArray" :key="index" closable > <span> {{ item.label.join(separator) }}</span> </el-tag> </div> <div class="lazy-cascader-label" v-else> <el-tooltip placement="top-start" :content="labelObject.label.join(separator)" > <span>{{ labelObject.label.join(separator) }}</span> </el-tooltip> </div> </div> <!-- 禁用状态 --> <!-- 可选状态 --> <el-popover v-else trigger="click" placement="bottom-start" ref="popover"> <!-- 搜索 --> <div class="lazy-cascader-search"> <el-autocomplete :style="{ width: width }" v-if="filterable" class="inline-input" prefix-icon="el-icon-search" label="name" v-model="keyword" :fetch-suggestions="querySearch" :trigger-on-focus="false" placeholder="请输入" @select="handleSelect" > <template slot-scope="{ item }"> <div class="name">{{ item[props.label].join(separator) }}</div> </template> </el-autocomplete> </div> <!-- 搜索 --> <!-- 级联面板 --> <div class="lazy-cascader-panel"> <el-cascader-panel ref="panel" v-model="current" :options="options" :props="currentProps" @change="change" ></el-cascader-panel> </div> <!-- 级联面板 --> <!--内容区域--> <div class="el-input__inner lazy-cascader-input" :class="disabled ? 'lazy-cascader-input-disabled' : ''" slot="reference" > <span class="lazy-cascader-placeholder" v-show="placeholderVisible"> {{ placeholder }} </span> <div class="lazy-cascader-tags" v-if="props.multiple"> <el-tag class="lazy-cascader-tag" type="info" disable-transitions v-for="(item, index) in labelArray" :key="index" closable @close="handleClose(item)" > <span> {{ item.label.join(separator) }}</span> </el-tag> </div> <div class="lazy-cascader-label" v-else> <el-tooltip placement="top-start" :content="labelObject.label.join(separator)" > <span>{{ labelObject.label.join(separator) }}</span> </el-tooltip> </div> </div> <!--内容区域--> </el-popover> <!-- 可选状态 --> </div></template>
js
export default { props: { value: { type: Array, default: () => { return []; } }, separator: { type: String, default: " > " }, placeholder: { type: String, default: "请选择" }, width: { type: String, default: "400px" }, filterable: Boolean, disabled: Boolean, props: { type: Object, default: () => { return {}; } } }, data() { return { keyword: "", options: [], current: [], labelObject: { label: [], value: [] }, labelArray: [], currentProps: { multiple: this.props.multiple, checkStrictly: this.props.checkStrictly, value: this.props.value, label: this.props.label, leaf: this.props.leaf, lazy: true, lazyLoad: this.lazyLoad } }; }, computed: { placeholderVisible() { if (this.current) { return this.current.length == 0; } else { return true; } } }, watch: { current() { this.getLabelArray(); }, value(v) { this.current = v; } }, created() { this.initOptions(); }, methods: { //搜索 querySearch(query, callback) { this.props.lazySearch(query, list => { callback(list); }); }, //选中搜索下拉搜索项 handleSelect(item) { if (this.props.multiple) { let index = this.current.findIndex(obj => { return obj.join() == item[this.props.value].join(); }); if (index == -1) { this.current.push(item[this.props.value]); this.$emit("change", this.current); } } else { //选中下拉选变更值 if ( this.current == null || item[this.props.value].join() !== this.current.join() ) { this.current = item[this.props.value]; this.$emit("change", this.current); } } this.keyword = ""; }, //初始化数据 async initOptions() { this.props.lazyLoad(0, list => { this.$set(this, "options", list); if (this.props.multiple) { this.current = [...this.value]; } else { this.current = this.value; } }); }, async getLabelArray() { if (this.props.multiple) { let array = []; for (let i = 0; i < this.current.length; i++) { let obj = await this.getObject(this.current[i]); array.push(obj); } this.labelArray = array; this.$emit("input", this.current); if (!this.disabled) { this.$nextTick(() => { this.$refs.popover.updatePopper(); }); } } else { this.labelObject = await this.getObject(this.current || []); this.$emit("input", this.current); } }, /**格式化id=>object */ async getObject(id) { let options = this.options; let nameArray = []; for (let i = 0; i < id.length; i++) { let index = options.findIndex(item => { return item[this.props.value] == id[i]; }); nameArray.push(options[index][this.props.label]); if (i < id.length - 1 && options[index].children == undefined) { let list = new Promise(resolve => { this.props.lazyLoad(id[i], list => { resolve(list); }); }); this.$set(options[index], "children", await list); options = options[index].children; } else { options = options[index].children; } } return { value: id, label: nameArray }; }, //懒加载数据 async lazyLoad(node, resolve) { let current = this.current; if (this.props.multiple) { current = [...this.current]; } if (node.root) { resolve(); } else if (node.data[this.props.leaf]) { resolve(); } else if (node.data.children) { resolve(); } else { this.props.lazyLoad(node.value, list => { node.data.children = list; if (this.props.multiple) { this.current = current; } resolve(list); }); } }, //删除多选值 /**删除**/ handleClose(item) { let index = this.current.findIndex(obj => { return obj.join() == item.value.join(); }); if (index > -1) { let node = this.$refs.panel.getCheckedNodes().find(n => { return n.value == this.current[index][this.current[index].length - 1]; }); if (node) { node.checked = false; } this.current.splice(index, 1); this.$emit("change", this.current); } }, change() { this.$emit("change", this.current); } }};
css
.lazy-cascader { display: inline-block; width: 300px; .lazy-cascader-input { width: 100%; background: #fff; height: auto; min-height: 36px; padding: 5px; line-height: 1; cursor: pointer; > .lazy-cascader-placeholder { padding: 0 2px; line-height: 28px; color: #999; font-size: 14px; } > .lazy-cascader-label { padding: 0 2px; line-height: 28px; color: #606266; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } .lazy-cascader-input-disabled { background-color: #f5f7fa; border-color: #e4e7ed; color: #c0c4cc; cursor: not-allowed; > .lazy-cascader-label { color: #c0c4cc; } > .lazy-cascader-placeholder { color: #c0c4cc; } }}.lazy-cascader-tag { display: inline-flex; align-items: center; max-width: 100%; margin: 2px; text-overflow: ellipsis; background: #f0f2f5; > span { flex: 1; overflow: hidden; text-overflow: ellipsis; } > .el-icon-close { -webkit-box-flex: 0; -ms-flex: none; flex: none; background-color: #c0c4cc; color: #fff; }}.lazy-cascader-panel { margin-top: 10px; display: inline-block;}
其实,在自己封装组件的时候,也会不自觉的学习或者掌握一些东西,还是比较有趣的。
比如,为了解决多选的时候Popover 弹出框错位的问题,看了Element 的源码,发现Popover组件有一个updatePopper方法。
而且,封装组件需要考虑的问题比较多,倒不是说复杂,就是尽可能要全面。
好了,这一篇就写到这里,至于适用性,我也不能保证,至少很好的解决了cascader级联动态加载的不足,而且简化了动态加载的方法,很方便的实现了回显和搜索。
如果这正是你需要的,或者你预感自己会需要,可以先收藏,对于组件的不足之处也可以交流和讨论,以方便我去优化和改进。
上一篇 ???? ElementUI cascader级联动态加载及回显
作者:四哥0819
链接:https://www.jianshu.com/p/06385601003b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。