【自定义微信小程序拉下选择过滤组件】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"><inputclass="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"><view class="dropdown" wx:if="{{ isOpen }}"><viewclass="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);
}
效果图: