最近在写后台管理系统,遇到下拉框组件的使用需求。由于框架是antd
的,因此需要用到的是
但是这个组件跟elementUi
中的下拉选择器一样,当下拉框中的数据很多时,就会非常的卡顿。
之前我有写过一个关于elementUi
中下拉组件数据很多卡顿的文章,最终是自己写了一个组件实现了想要的功能。
elementUi——select组件渲染数据很多时卡顿问题解决:链接
这次仿照之前的案例,对antd
的select
组件进行自己封装。
1.props
父子组件传值
props: {
value: {
type: Object,
default: () => {},
},
width: {
type: String,
default: "100",
},
list: {
type: Array,
default: () => [],
},
placeholer: {
type: String,
default: "请选择品牌",
},
},
由于props
传值是异步的,因此当传递的数据是通过接口异步获取时,就会出现在组件的mounted
周期函数中拿不到父组件传递过来的数据的情况,此时可以通过watch
监听来实现。
watch: {
value: {
handler(newName, oldName) {
this.selectValue = { ...newName };
},
immediate: true,
deep: true,
},
list: {
handler(newName, oldName) {
this.arr = [...newName];
},
immediate: true,
deep: true,
},
},
定义的变量:
data() {
return {
showData: false,
selectValue: {},
searchTimer: null,
arr: [],
mouseFlag: false,
top: 0,
left: 0,
offsetLeft: 0,
};
},
2.组件组成:input
搜索框+div
下拉选项框
2.1 input
搜索框
<a-input
type="text"
v-model="selectValue.name"
:placeholder="placeholer"
@focus="inputFocus"
@blur="inputBlur"
@change="inputChange"
allowClear
:style="{ width: width + 'px' }"
></a-input>
关于这个组件,需要实现以下功能:
- focus:光标移入时:输入框内容要清空,div的下拉选项框要显示。
- blur:光标移出时:输入的内容要清空,展示已选择的内容,div的下拉框要隐藏
- change:监听输入框的内容,实时进行下拉框内容的筛选
2.1.1 focus
代码
inputFocus(e) {
var wh = document.documentElement.clientHeight,
ot = e.target.getBoundingClientRect().top;
if (
(Math.abs(wh - ot) > 300 && Math.abs(wh - ot) < 360) ||
Math.abs(wh - ot) < 250
) {
this.top = e.target.offsetTop - 150;
} else {
this.top = e.target.offsetTop + 40;
}
console.log("left", e.target.offsetLeft);
this.offsetLeft = e.target.offsetLeft;
this.left = e.target.offsetLeft;
this.selectValue = {};
if (this.arr && this.arr.length) {
this.showData = true;
} else {
this.$message.error("无相关数据");
}
},
2.1.2 blur
代码
inputBlur(e) {
setTimeout(() => {
if (!this.mouseFlag) {
this.showData = false;
}
this.selectValue = this.value;
this.arr = [...this.list];
}, 200);
},
2.1.3 change
代码
//监听输入
inputChange(e) {
var val = e.target.value;
if (val) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
this.arr = this.list.filter((l) => {
return l.name.indexOf(val.toLowerCase()) > -1;
});
}, 50);
} else {
this.arr = [...this.list];
}
if (!val) {
this.$emit("ok", {});
}
},
2.2 div
下拉选项框
<div
class="selectOptionCls"
:style="{ top: top + 'px', left: left + 'px', width: width + 'px' }"
v-show="showData"
ref="selectOptionCls"
@mouseenter="mouseenter"
@mouseleave="mouseleave">
<div
v-for="data in arr"
:key="data.id"
:class="data.id == selectValue.id ? 'active' : ''"
@click="selectVal(data)"
>
{{ data.name }}
</div>
</div>
关于这个鼠标移入和移出的问题,当光标在input组件中时,下拉选择框显示,当鼠标移入到下拉选择框中时,下拉选择框还需要显示,而非隐藏,因此需要监听下拉选择框中是否有鼠标移入和移出的操作。
相应的代码如下:
mouseleave() {
this.mouseFlag = false;
},
mouseenter() {
this.mouseFlag = true;
},
//选择某一项
selectVal(data) {
this.$emit("ok", data);//选择后将选中的数据抛出
this.mouseFlag = false;
this.showData = false;
},
3.style
样式代码
<style scoped lang="less">
.wrap {
height: 40px;
z-index: 1;
// -webkit-transition: all 0.2s;
// transition: all 0.2s;
}
.selectOptionCls {
background: #fff;
height: 150px;
position: absolute;
top: 40px;
left: 0;
z-index: 9;
border-radius: 4px;
color: #999;
border: 1px solid #d9d9d9;
box-sizing: border-box;
overflow: auto;
div {
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
font-size: 14px;
-webkit-box-orient: vertical;
padding: 0 10px;
line-height: 30px;
&.active {
font-weight: bold;
}
&:hover {
cursor: pointer;
background: #fff9e6;
}
}
}
</style>
父组件中的使用
<big-data-select
:style="`width:${
width - 10
}px;text-align:left;left:-${scrollLeft}px;top:-10px;`"
:value="{ id: record.mfgId, name: record.mfg }"
@ok="selectVal($event, record)"
:width="`${col.width - 10}`"
:list="brandList"
placeholer="请选择品牌"
/>
注意上面的数据:
对应props
中的:width
:宽度 list
:下拉框的数据 placeholer
:输入框中的提示信息 value
:展示的值,是个对象,属性有mfgId
和mfg
两个。
选择某一项后触发的函数:
selectVal(data, row) {
if (data && data.id) {
row.mfgId = data.id;
var arr = this.brandList.filter((brand) => brand.id == data.id);
if (arr && arr[0]) {
row.mfg = arr[0].name;
}
} else {
row.mfgId = undefined;
row.mfg = undefined;
}
},
此时发现功能已经实现了。
但是出现了层级问题,就是下拉框中的内容会被遮挡。此时可以给子组件外面添加一个绝对定位的div
标签来实现层级问题。
<div style="position: absolute">
子组件
</div>
这样就可以了。
但是又出现了新的问题。由于现在给子组件
进行的是绝对定位,则滚动条滚动时,下拉框的部分并不会跟随滚动条滚动,绝对定位的元素是跟随页面位置的。
这个时候我想到的解决办法就是:当页面滚动时,不展示子组件,只有当滚动结束时再展示子组件。
<div v-if="!scrollFlag" style="position: absolute">
子组件
</div>
<div v-else>子组件的值xxxx</div>
此时的问题变成了,在vue
代码中监听表格滚动事件。
vue
代码中监听表格滚动事件
mounted() {
this.$nextTick(() => {
var bodyDom =
this.$refs.table.$el &&
this.$refs.table.$el.getElementsByClassName("ant-table-body")[0];
bodyDom.addEventListener(
"scroll",
(e) => {
clearTimeout(this.timer);
this.timer = null;
this.scrollLeft = e.target.scrollLeft;
this.scrollFlag = true;
this.timer = setTimeout(() => {
if (this.scrollLeft == e.target.scrollLeft) {
//停止滑动
this.scrollFlag = false;
}
}, 200);
},
false
);
});
},
监听滚动事件:dom.addEventListener('scroll',函数,false)
如果延迟200毫秒时滚动的距离与上一次滚动距离相等,则视为滚动结束。也就是上面的this.timer = setTimeout
的部分。
最终效果如下:
完成!!!