【自定义微信小程序拉下选择过滤组件】searchable-select
组件说明
点击输入框获取焦点,输入内容,自动匹配搜索结果,点击搜索结果,自动填充搜索结果。
组件使用
- 将组件文件夹放在项目中。
- 在需要使用的页面的json文件中,添加组件的路径。
{
"usingComponents": {
"searchable-select": "/components/searchable-select/searchable-select"
}
}
- 在需要使用的页面的wxml文件中,使用组件。
<searchable-select options="{{ hospitalOptions }}" selectedValue="{{ selectedValue }}" placeholder="搜索医院" bind:select="onSelect"></searchable-select>
- 在需要使用的页面的js文件中,定义组件的属性和方法。
Page({
data: {
hospitalOptions: [
{ id: 1, name: '选项1' },
{ id: 2, name: '选项2' },
{ id: 3, name: '选项3' }
],
selectedValue: ''
},
methods: {
// 下拉选择医院
onSelect(e) {
const selectedValue = e.detail.value;
this.setData({
selectedValue: selectedValue.label,
hospitalCurrent: selectedValue.value
});
},
}
})
组件详情
searchable-select.js
Component({
properties: {
// 下拉选项列表
options: {
type: Array,
value: []
},
// 初始选中的值
selectedValue: {
type: String,
value: ''
},
// 输入框的占位符
placeholder: {
type: String,
value: '请选择'
}
},
data: {
isOpen: false, // 下拉框是否展开
filteredOptions: [], // 过滤后的选项列表
inputValue: '', // 输入框的值
showNoData: false // 是否显示暂无数据提示
},
observers: {
// 监听 options 属性变化,更新过滤后的选项列表
options(newOptions) {
this.setData({
filteredOptions: newOptions
});
},
selectedValue(newValue) {
this.setData({
inputValue: newValue
});
}
},
methods: {
// 输入框获得焦点时展开下拉框
onInputFocus() {
this.setData({
isOpen: true
});
this.filterOptions(this.data.inputValue);
},
// 防抖函数
debounce(func, delay) {
let timer = null;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
},
// 处理输入框输入事件
handleInput: function (e) {
const inputValue = e.detail.value;
this.debounceFilterOptions(inputValue);
},
// 过滤选项列表
filterOptions(inputValue) {
if (!inputValue) {
this.setData({
filteredOptions: [],
showNoData: true
});
return;
}
const filteredOptions = this.data.options.filter(option => {
return option.label.includes(inputValue);
});
const showNoData = filteredOptions.length === 0;
this.setData({
inputValue,
filteredOptions,
showNoData
});
},
// 防抖处理过滤选项
debounceFilterOptions: function () {
this.debounce(this.filterOptions, 500).apply(this, arguments);
},
handleSelectOpen() {
this.setData({
isOpen: false
});
},
// 处理选项点击事件
handleOptionClick(e) {
const selectedValue = e.currentTarget.dataset.value;
this.setData({
inputValue: selectedValue.label,
isOpen: false,
showNoData: false
});
// 触发自定义事件,通知父组件选中的值
this.triggerEvent('select', { value: selectedValue });
}
}
});
searchable-select.json
{
"component": true,
"usingComponents": {},
"styleIsolation": "isolated"
}
searchable-select.wxml
<view class="searchable-select">
<view class="input-container">
<input
class="plugin-input"
type="text"
value="{{ inputValue }}"
placeholder="{{ placeholder }}"
bindfocus="onInputFocus"
bindblur="onInputBlur"
bindinput="handleInput"
readonly="{{ !isOpen }}"
/>
<view class="icon-wrapper {{ isOpen ? 'open' : '' }}"></view>
</view>
<view class="dropdown-box" bindtap="handleSelectOpen" wx:if="{{ isOpen }}">
<view class="dropdown" >
<view
class="option {{ inputValue == item.label ? 'selected' : '' }}"
wx:for="{{ filteredOptions }}"
wx:key="*this"
data-value="{{ item }}"
catchtap="handleOptionClick"
>
{{ item && item.label }}
</view>
<view class="no-data" wx:if="{{ showNoData }}">暂无</view>
</view>
</view>
</view>
searchable-select.wxss
/* 修改为直接类名选择器 */
.plugin-input {
flex: 1;
border: none;
outline: none;
z-index: 99;
}
.searchable-select {
position: relative;
}
.input-container {
display: flex;
align-items: center;
border-bottom: 1rpx solid #f1f1f1;
padding: 20rpx 28rpx;
}
.dropdown-box {
position: absolute;
top: 100%;
left: 0;
right: 0;
min-height: 100vh;
z-index: 10;
}
.dropdown {
top: 100%;
left: 0;
right: 0;
box-sizing: border-box;
border-top: none;
max-height: 300px;
overflow-y: auto;
z-index: 10;
border-radius: 6rpx;
padding: 12rpx 20rpx 10rpx 20rpx;
box-shadow: 0rpx 0rpx 1rpx 1rpx rgba(0, 0, 0, 0.2) inset;
background-color: #fff;
margin: 0 28rpx;
}
.option {
padding: 20rpx 10rpx;
cursor: pointer;
border-bottom: 1rpx solid #eee;
}
.option .selected {
background-color: #f0f0f0;
}
.option:last-child {
border-bottom: none;
}
.option:hover {
background-color: #f0f0f0;
}
.no-data {
padding: 10px;
text-align: center;
color: #999;
}
.icon-wrapper::after {
content: '';
position: absolute;
top: 50%;
right: 28rpx;
transform: translateY(-50%);
width: 0;
height: 0;
border: 10rpx solid transparent;
border-top: 10rpx solid #999;
transition: transform 0.3s ease;
}
.icon-wrapper.open::after {
transform: translateY(-50%) rotate(180deg);
-webkit-transform: translateY(-50%) rotate(180deg);
-moz-transform: translateY(-50%) rotate(180deg);
-ms-transform: translateY(-50%) rotate(180deg);
-o-transform: translateY(-50%) rotate(180deg);
}
效果图: