【自定义微信小程序拉下选择过滤组件】searchable-select

发布于:2025-03-18 ⋅ 阅读:(13) ⋅ 点赞:(0)

【自定义微信小程序拉下选择过滤组件】searchable-select

组件说明

点击输入框获取焦点,输入内容,自动匹配搜索结果,点击搜索结果,自动填充搜索结果。

组件使用

  1. 将组件文件夹放在项目中。
  2. 在需要使用的页面的json文件中,添加组件的路径。
{
  "usingComponents": {
    "searchable-select": "/components/searchable-select/searchable-select"
  }
}
  1. 在需要使用的页面的wxml文件中,使用组件。
<searchable-select options="{{ hospitalOptions }}" selectedValue="{{ selectedValue }}" placeholder="搜索医院" bind:select="onSelect"></searchable-select>
  1. 在需要使用的页面的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);
}

效果图:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到