在开发一个练手项目的时候,需要一个远程搜索的下拉选择组件;
elementui自带的el-select支持远程搜索;但如果一次性查询的数据过多;会导致卡顿。故自己实现一个可分页的远程下拉选择组件
效果:
代码:
javascript
<template>
<el-select
v-model="selectedValue"
filterable
remote
reserve-keyword
placeholder="请输入关键词"
:remote-method="remoteSearch"
:loading="loading"
@change="handleChange"
@focus="handleFocus"
@clear="handleClear"
clearable
>
<el-option
v-for="item in options"
:key="item[keyField]"
:label="item[labelField]"
:value="item[valueField]"
/>
<div class="select-footer" v-if="total > 0">
<div class="total-count">共 {{ total }} 条</div>
<div class="select-pagination" v-if="total > pageSize">
<el-pagination
small
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page.sync="currentPage"
@current-change="handlePageChange"
/>
</div>
</div>
</el-select>
</template>
<script>
import request from '@/utils/request'
export default {
name: 'RemoteSearchSelect',
props: {
value: {
type: [String, Number],
default: ''
},
apiUrl: {
type: String,
required: true
},
// 自定义字段映射
labelField: {
type: String,
default: 'label'
},
valueField: {
type: String,
default: 'value'
},
keyField: {
type: String,
default: 'id'
},
// 请求参数自定义
searchParamName: {
type: String,
default: 'keyword'
},
pageParamName: {
type: String,
default: 'pageNum'
},
pageSizeParamName: {
type: String,
default: 'pageSize'
},
// 响应数据结构配置
responseListField: {
type: String,
default: 'list'
},
responseTotalField: {
type: String,
default: 'total'
},
// 额外的请求参数
extraParams: {
type: Object,
default: () => ({})
}
},
data() {
return {
selectedValue: this.value,
options: [],
loading: false,
currentPage: 1,
pageSize: 10,
total: 0,
keyword: '',
isFirstFocus: true,
selectedOption: null
}
},
created() {
this.remoteSearch('');
},
methods: {
async remoteSearch(query) {
this.loading = true
this.keyword = query
try {
const params = {
[this.searchParamName]: query,
[this.pageParamName]: this.currentPage,
[this.pageSizeParamName]: this.pageSize,
...this.extraParams
}
// 使用配置的响应数据字段
const responseData = await request({
url: this.apiUrl,
method: 'post',
data: params
})
this.options = responseData[this.responseListField] || []
this.total = responseData[this.responseTotalField] || 0
} catch (error) {
console.error('搜索出错:', error)
this.$message.error('搜索失败,请重试')
} finally {
this.loading = false
}
},
handleChange(value) {
this.selectedOption = this.options.find(item => item[this.valueField] === value)
this.$emit('input', value)
this.$emit('change', value)
this.$emit('select', this.selectedOption)
},
handleFocus() {
// 如果是首次点击或当前没有选项,则执行搜索
this.remoteSearch(this.keyword)
this.isFirstFocus = false
},
handleClear() {
this.selectedValue = ''
this.keyword = ''
this.selectedOption = null
this.$emit('input', '')
this.$emit('change', '')
this.$emit('select', null)
this.remoteSearch('')
},
async handlePageChange(page) {
this.currentPage = page
if (this.keyword) {
await this.remoteSearch(this.keyword)
}
}
},
watch: {
value: {
handler(newVal) {
this.selectedValue = newVal
},
immediate: true
}
}
}
</script>
<style scoped>
.select-footer {
padding: 8px;
border-top: 1px solid #EBEEF5;
display: flex;
align-items: center;
justify-content: space-between;
}
.total-count {
color: #909399;
font-size: 12px;
padding-left: 8px;
}
:deep(.el-pagination) {
padding: 0;
margin-left: 8px;
}
</style>
用法
javascript
<remote-search-select
v-model="assignForm.ownerId"
api-url="remoteUrl"
label-field="label"
value-field="value"
key-field="id"
search-param-name="childName"
response-list-field="rows"
response-total-field="total"
@select="handleProxyerSelectChange"
/>
参数介绍:
参数 | 说明 |
---|---|
v-model | 绑定的data数据 |
api-url | 远程查询的数据接口路径,方法名,请求方式为post |
label-field | 需要展示的名称属性名 |
value-field | 下拉值的属性名 |
key-field | 主键的属性名 |
search-param-name | 模糊搜索的参数的字段名 |
response-list-field | 返回list的属性名 |
response-total-field | 返回list的总数的属性名 |
@select | funtion,接收选中的值 |
组件可根据自己的具体需求做源码修改。