【组件封装-优化】vue+element plus:二次封装select组件,实现下拉列表有分页、自定义是否可搜索的一系列功能
第一章 前言
1.1 实现效果
1.2 需求及准备
- 开发前言:由于公司针对下拉框都是直接使用组件,并且下拉数据都不是特别多。而小编的需求是接口直接返回了2000多条数据,一个原因是数据特别庞大,请求接口需要花费特别长时间;另一个是2000调数据我们也不好一次性都展示在下拉框,体验感不是很好;于是通过小编的研究与优化,与后端约定将数据改成提供分页接口,并且小编也利用element-plus组件提供了两个方法:一个是直接使用无限滚动组件,触底掉接口(Infinite Scroll);另一个是再二次封装一个分页的下拉框,经过最终讨论,确定方案用后者(原因:扩展性更高,且后续其他项目可通用)
- 核心需求:因下拉框中的数据过多,所以需要使用分页的方式来实现。
- 工具:element-plus
设计 | Element Plus
Infinite Scroll 无限滚动 | Element Plus
- v-bind="$attrs" 理解
【vue】v-bind=“$attrs“理解与使用-CSDN博客
- 插槽
【vue】slot插槽:灵活内容分发的艺术-CSDN博客
第二章 源代码
2.1 组件开发
- 实现思路:
- 基于element-plus开发,必须需要两个组件el-selec和组件el-pagination
- 需要能继承到上面两个组件的所有属性,减少任务量
- 支持是否可搜索(可用props控制显隐或者插槽等);是否可分页(由于小编的需求是支持分页,所以小编的思路是默认就有,大家可根据需求添加配置项或者做成插槽);
- 分页与下拉框数据联动;
- 要学会利用v-model对某些必要数据双向绑定方便我们对数据的处理
- 除了以上必要功能,还有就是样式了,也要稍微处理
- 注意:小编在封装时会已经使用一些element组件的配置项,如果父组件在使用时用了同名的属性,则会覆盖;大家需要留意一下
- 组件原代码如下
<!-- 组件名:ui-select -->
<script setup>
// 接收一些必要的配置项
const props = defineProps({
val: {
type: String,
default: ''
},
// 选择框宽度
width: {
type: String,
default: ''
},
// options弹窗宽度
optionsWidth: {
type: String,
default: ''
},
// 配置项
options: {
type: Array,
default: () => [
{
id: 1,
label: 'Option1',
value: 'Option1'
}
]
},
// 分页数据
pageData: {
type: Object,
default: () => {
return {
pageNo: 1,
pageSize: 10,
total: 20
}
}
}
})
const emit = defineEmits(['update:val', 'update:pageData', 'pageChange'])
const selectVal = ref('')
const page = ref({})
onMounted(() => {
const { val, pageData } = props
// element组件是支持select的双向绑定的,所以小编单独用了一个新的变量处理
selectVal.value = val
// 分页数据
page.value = pageData
})
const selectChange = (item) => {
// select选中值的更新
emit('update:val', item)
}
const pageChange = () => {
// 分页变化更新分页数据
emit('update:pageData', page.value)
。// 同时emit一个方法,让父组件可以利用这个方法执行某些操作
emit('pageChange')
}
</script>
<template>
<div>
<!-- v-bind="$attrs"小编前面有提到,就是完整的拿到父组件传的参数,从而不需要props再接一层 -->
<!-- select 和 pagination都设置v-bind="$attrs",继承两个组件的配置项 -->
<!-- 注意:小编在封装的时候已经使用了一些element组件的配置项,如果父组件在使用时用了同名的属性,则会覆盖 -->
<el-select
v-model="selectVal"
v-bind="$attrs"
:style="{
width: width || '100%'
}"
@change="selectChange"
class="custom_select"
>
<!-- 最小宽度是分页撑的宽度 -->
<div
:style="{
width: optionsWidth || '100%'
}"
class="options_content"
>
<!-- 这里使用到了插槽,主要是扩展我们二次封装的select组件是否可搜索或者再前面需要加什么内容…… -->
<slot name="header" class="header"></slot>
<!-- element的select的options配置项 -->
<div class="options_main">
<el-option
v-for="item in options"
:key="item.id"
:label="item.label"
:value="item.value"
/>
</div>
<!-- 分页组件 -->
<!-- 注意这里也有v-bind="$attrs"使用 -->
<el-pagination
v-model:current-page="page.pageNo"
:page-size="page.pageSize"
layout="prev, pager, next"
:total="page.total"
@current-change="pageChange"
v-bind="$attrs"
class="pagination_page"
/>
</div>
</el-select>
</div>
</template>
// 下面就是我们针对二次封装组件的一些其他样式
<style lang="scss" scoped>
.pagination_page {
width: 100%;
padding: 10px 20px 10px 20px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.options_main {
// element select组件的弹窗是在根下的,不好直接设置弹窗的高,只能限制内容了
height: 140px;
margin: 0 20px;
overflow: auto;
}
::-webkit-scrollbar {
width: 5px;
}
</style>
2.2 组件使用
<!--
说明:
1、placeholder、clearable是el-select组件的属性,
由于我们使用了$attrs,所以再uiselect直接使用也是可以生效的;
同理background是el-pagination的也是可以的
2、小编使用了header插槽为分页组件提供了可搜索的功能
3、要留意这些字段的含义
-->
<!--
解释一下字段:
aiForm.description: 下拉框绑定的值
scopePage: 分页数据对象
{
pageNo: 1,
pageSize: 10,
total: 100
}
decOptions: 下拉数据
scopeSearchForm.description: 搜索关键字
-->
<UiSelect
v-model:val="aiForm.description"
v-model:pageData="scopePage"
placeholder="行业表诉"
class="ai_search-border margin_right_bottom"
width="150px"
:options="decOptions"
background
@pageChange="getDescriptions"
clearable
>
<template #header>
<div class="scope_header">
<el-input
v-model="scopeSearchForm.description"
class="ai_search-border margin_right_bottom"
style="border-color: #dcdfe6"
placeholder="请输入关键字"
clearable
>
<template #append>
<el-icon style="cursor: pointer" @click="getDescriptions"><Search /></el-icon>
</template>
</el-input>
</div>
</template>
</UiSelect>