组件展示效果图
在 Vue3 的 elementPlus项目中,我们经常需要使用下拉选择框 (el-select) 来展示大量数据。然而,默认情况下 el-select 不支持分页和搜索功能。本文将介绍如何通过二次封装 el-select 组件来实现这一需求,并使用自定义的 Hook 来简化开发流程。
1. 创建 useCustomOption.js Hook
首先,我们需要创建一个自定义 Hook,用于处理分页和搜索逻辑。
// src/composables/useCustomOption.js
import { ref, reactive } from 'vue';
import { listRegion } from '@/api/manage/region'; // 示例 API 调用
/**
* 自定义选项搜索钩子
* 该钩子用于在给定的API列表中搜索特定项,通过提供的查询参数来筛选结果
*
* @param {Function} listApi - 一个函数,用于获取列表数据
* @param {string} queryParamsKey - 用于搜索的项的键名
* @param {string} itemValue - 用于搜索的项的值
* @param {Object} queryParams - 其他查询参数,用于过滤结果
* @returns {Object} - 返回一个对象,包含搜索结果和相关方法
*/
export function useCustomOption(listApi, queryParamsKey, itemValue, queryParams) {
const selectDataList = ref([]); // 数据列表
const selectPageInfo = reactive({
pageNum: 1,
pageSize: 5,
total: 0,
});
// 查询数据列表
function getSelectDataList(pageNum, pageSize, searchQuery) {
selectPageInfo.pageNum = pageNum;
selectPageInfo.pageSize = pageSize;
const data = { [itemValue]: searchQuery, ...selectPageInfo };
listApi(data).then(response => {
selectPageInfo.total = response.total;
if (response.rows?.length > 0 && searchQuery !== '') {
// 设置默认值
queryParams.value[queryParamsKey] = response.rows[0].id;
}
selectDataList.value = response.rows;
});
}
// 处理分页器选中项变化事件
function handleSelectChange(selectedId, searchQuery) {
queryParams.value[queryParamsKey] = selectedId;
getSelectDataList(selectPageInfo.pageNum, selectPageInfo.pageSize, searchQuery);
}
return {
selectDataList,
selectPageInfo,
getSelectDataList,
handleSelectChange,
};
}
2. 创建 ElCustomOptionForSearch.vue 组件
接下来,我们将创建一个自定义的 el-select 组件,该组件将使用上述 Hook 实现分页和搜索功能。
<template>
<!-- 引入Element UI的Select组件,用于创建一个支持分页的数据选择器 -->
<el-select v-model="copyValue"
:disabled="disabled"
filterable
clearable
@clear="selectClear"
placeholder="请选择"
@change="updateValue"
:filter-method="debounceFilterMethod"
@focus="focusMethod"
class="elPaginationSelect"
>
<!-- 为每个数据项生成一个Option,方便用户选择 -->
<el-option
v-for="item in dataList"
:key="item.value"
:label="item[labelKey]"
:value="item[valueKey]"
></el-option>
<template #footer>
<!-- 自定义选择器的后缀,这里放置了一个分页器 -->
<div style="float: right; margin-right: 10px; padding-bottom: 10px;">
<el-pagination
@current-change="handleCurrentChange"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
layout="prev, pager, next, total"
:total="pageInfo.total"
></el-pagination>
</div>
</template>
</el-select>
</template>
<script setup>
import {ref} from 'vue';
// 定义组件属性
const props = defineProps({
value: String,
dataList: Array,
labelKey: String,
valueKey: String,
pageInfo: Object,
disabled: Boolean,
elSelectClass: Boolean,
});
// 定义组件事件发射器
const emit = defineEmits(['selectChange', 'selectPageChange']);
// 将父组件传入的value值拷贝一份,用于组件内部逻辑处理
const copyValue = ref(props.value);
// 当用户选择了一个不同的选项时触发,更新父组件的值
const updateValue = (select_key) => {
props.dataList.forEach(item => {
if (item[props.valueKey] === select_key) {
props.dataList = item;
emit('selectChange', select_key, item[props.labelKey]);
// 避免选中选项后 ->下拉框 执行搜索的bug
debounceFilterMethod(item[props.labelKey], false)
}
})
};
// todo 实时过滤方法,用户在输入框输入内容时会调用,以更新当前页和搜索关键词
import {throttle} from "lodash"; // 引入 lodash 的 throttle 函数:实现节流
// 根据第二个参数判断是否执行
// is_exec默认为true
const filterMethod = (query, is_exec = true) => {
// 解决当用户选中下拉选项时,会触发两次请求,一次是搜索,一次是选中,所以需要判断是否执行
if (!is_exec||query==='') {
return
}
// 区分focusMethod 和 输入框输入时为空字符串调用
if (query === 'focusMethod'){
query = ''
}
console.log("filterMethod", query)
copyValue.value = query;
emit('selectPageChange', 1, props.pageInfo.pageSize, query);
};
// debounceFilterMethod是通过lodash 的 throttle 函数对 filterMethod 进行节流处理后返回的一个新方法
const debounceFilterMethod = throttle(filterMethod, 300, {
leading: true, // 延长开始后调用
trailing: false, // 延长结束前调用
})
// 当选择器聚焦时调用,如果数据列表为空,则清空搜索关键词
const focusMethod = () => {
if (!props.dataList || (props.dataList && props.dataList.length === 0)) {
debounceFilterMethod('focusMethod');
}
};
// 当用户清空选择器时调用,重置当前页和搜索关键词
const selectClear = () => {
emit('selectPageChange', 1, props.pageInfo.pageSize, '');
};
// 分页器的当前页发生改变时调用,更新父组件的当前页码
const handleCurrentChange = (pageNum) => {
console.log("分页") // todo
props.pageInfo.pageNum = pageNum
console.log(copyValue.value);
// 解决分页器bug :当选中选项 -> 查询 -> 会修改pageSize,当前组件的handleCurrentChange->触发多个渲染事件
emit('selectPageChange', pageNum, props.pageInfo.pageSize, copyValue.value);
};
</script>
<style scoped lang="scss">
// 为选择器自定义样式,去除默认边框,使其更适合分页数据选择的场景
.elPaginationSelect {
width: 100%;
border: none !important;
}
// 自定义输入框样式,去除默认边框,设置宽度以适应内容
.elPaginationSelect .el-input__inner {
border: none !important;
width: 100%;
}
</style>
3.使用 ElCustomOptionForSearch.vue 组件
现在,您可以在任何 Vue 组件中使用 ElCustomOptionForSearch 组件来实现带有分页和搜索功能的下拉选择框。
使用组件
<el-form-item label="搜索区域" prop="regionId">
<!-- 使用ElCustomOptionForSearch这个自定义组件,通过传入必要参数来实现搜索功能 -->
<!-- v-model绑定的queryParams.regionId是搜索区域的ID,用于双向绑定搜索框的值 -->
<!-- :data-list传入一个列表数据源,用于渲染搜索选项 -->
<!-- :label-key和:value-key分别指定列表数据中的属性,用于显示和作为选项的值 -->
<!-- :pageInfo传入一个分页信息对象,用于控制搜索选项的分页显示 -->
<!-- :disabled用于控制搜索功能是否禁用 -->
<!-- :el-select-class表示是否使用Element UI的样式类 -->
<!-- @selectChange和@selectPageChange分别监听选项变化和分页变化事件,触发相应的处理函数 -->
<el-custom-option-for-search
v-model="queryParams.regionId"
:data-list="selectDataList"
:label-key="'regionName'"
:value-key="'id'"
:pageInfo="selectPageInfo"
:disabled="false"
:el-select-class="true"
@selectChange="handleSelectChange"
@selectPageChange="getSelectDataList"
/>
</el-form-item>
使用hook
/** 查询区域列表操作 */
import ElCustomOptionForSearch from "@/components/ElCustomOptionForSearch"
import {listRegion} from "@/api/manage/region.js";
import {useCustomOptionSearch} from "@/components/ElCustomOptionForSearch/hook/useCustomOption.js";
const {selectDataList,selectPageInfo,getSelectDataList,
handleSelectChange} = useCustomOptionSearch(listRegion, "regionId",'regionName',queryParams)