转 https://blog.csdn.net/wcy0112/article/details/143368550
1 简介
微信小程序没有下拉选择的组件所以只能自己写一个了。
先看效果
  
2 组件说明
  功能类似 element-ui 的 el-select 【根据 value 去匹配 label 回显在输入框】
  使用时不需要选项数组options做特殊处理【可以通过传入 labelName 和 valueName 自定义选中值和回显】
  选中值有高亮显示【可以通过传入 itemBgColor 和 textColor 自定义选中样式】
  内容区域会根据输入框的位置,在输入框上/下方展示【父组件必须加上类名 inputPlaceholder ,如果不加则只在下方展示】
3 组件配置项
  options	【必填】选项数组
  labelName	【必填】选项数组-绑定的label名称
  valueName	【必填】选项数组-绑定的value名称
  modelValue	【必填】默认选中的value
  placeholder	输入框为空时占位符,默认“请选择”
  disabled	是否禁用,默认false
  readonly	是否只读,默认false
  title	下拉框标题
  titleWidth	(样式参数)标题长度,默认60px
  itemBgColor	(样式参数)选中选项的背景颜色,默认#F5F8FE
  textColor	(样式参数)选中选项的字体颜色,默认#3772E9
  bindhandleChange	【方法】接收组件选中的值e.detail
  bindhandleShow	【方法】处理滚动选项区域时背景页面滚动问题,接受组件下拉框展示的show值
4 组件代码wan-select
1)wxml
<!--单选下拉框组件-->
<view class="w100 select_all_view">
  <!-- 标题,可以没有 -->
  <view class="mr-10 pt-10 size-28" :style="width: {{titleWidth}};" wx:if="{{title}}">{{title}}</view>
  <view class="select_view relative" :style="width: {{title ? 'calc(100% - ' + titleWidth + ' - 10rpx)' : '100%'}};max-width: {{title ? 'calc(100% - ' + titleWidth + ' - 10rpx)' : '100%'}};">
    <view class="inputPlaceholder h100 w100 radius-10 relative flex_l pd-10 {{ disabled ? 'gray-3' : 'black' }}" bindtap="{{disabled || readonly ? '' : 'changeShow'}}" :style="background: {{disabled ?'#f5f7fa' : bgColor}};border: 2rpx solid #ddd;">
      <block wx:if="{{disabled || readonly}}">
        <view class="flex-1" wx:if="{{selectLabel}}">{{selectLabel}}</view>
        <view class="flex-1 gray-3 line-1" wx:else>{{placeholder}}</view>
        <van-icon class="gray-3" name="arrow-down" />
      </block>
      <block wx:else>
        <block wx:if="{{selectLabel}}">
          <view class="flex-1">{{selectLabel}}</view>
          <van-icon class="gray-3" name="clear" wx:if='{{!show}}' catchtap="clearInput" />
          <van-icon class="gray-3" name="arrow-up" wx:else />
        </block>
        <block wx:else>
          <view class="flex-1 gray-3 line-1">{{placeholder}}</view>
          <van-icon class="gray-3" name="arrow-down" class="transfer {{show ? 'is-reverse' : 'no-reverse' }}" />
        </block>
      </block>
    </view>
    <!-- 下拉展开后的可选择内容 -->
    <block wx:if='{{show}}'>
      <view class="{{toTop ? 'triangleBox-top' : 'triangleBox'}}">
        <view class="{{toTop ? 'triangle-top' : 'triangle'}}"></view>
      </view>
      <view class="content radius-10 pd-20 size-28" :style="{{toTop ? 'top: -' + (options.length > 4 ? 296 : (options.length * 64 + 40)) + 'rpx; margin-top: -10rpx;' : 'margin-top: 10rpx;'}}">
        <view class="pd-10 center gray-3" wx:if="{{options.length < 1}}">暂无数据</view>
        <view class="line-1 w100 pd-10 contentItem {{item[valueName] == selectValue ? 'bold':''}}" wx:for="{{options}}" wx:key="index" bindtap="handleChange" data-item="{{item}}" :style="color: {{ item[valueName] == selectValue ? textColor : '#000'}}; background: {{item[valueName] == selectValue ? itemBgColor:''}};">
          {{item[labelName]}}
        </view>
      </view>
    </block>
  </view>
</view>
2)js
// components/wan-select/select.js
Component({
  options: {
    addGlobalClass: true,
  },
  properties: {
    /* --------- 样式参数 --------- */
    titleWidth: { // 标题长度
      type: String,
      value: "60px"
    },
    bgColor: { // 输入框背景颜色
      type: String,
      value: "#fff"
    },
    itemBgColor: { // 选中的选项背景颜色
      type: String,
      value: "#F5F8FE"
    },
    textColor: { // 选中的字体颜色
      type: String,
      value: "#3772E9"
    },
    /* --------- 数据参数 --------- */
    title: { // 下拉框标题
      type: String,
      value: ""
    },
    options: { // 选项数组
      type: Array,
      value: [],
      observer: function (v) {
        this.handleData(); // 这里是为了去回显默认选中的,本来应该在modelValue去做的,发现那里取到的options是空的,所在在这里和modelValue都掉一次这个方法
      }
    },
    labelName: { // 选项数组-绑定的label名称
      type: String,
      value: "dictLabel",
    },
    valueName: { // 选项数组-绑定的value名称
      type: String,
      value: "dictValue"
    },
    modelValue: { // 默认选中的
      type: String,
      value: "",
      observer: function () {
        //如果有默认值,需要匹配出name,所以这里使用obersver,当父组件中值改变时触发
        this.handleData();
      }
    },
    placeholder: { // 输入框为空时占位符
      type: String,
      value: "请选择"
    },
    disabled: { // 是否禁用
      type: Boolean,
      value: false
    },
    readonly: { // 是否只读
      type: Boolean,
      value: false
    }
  },
  /**
   * 页面的初始数据
   */
  data: {
    show: false, //选项框及图标展示
    selectValue: "", //选中的value
    selectLabel: "", //选中的label
    toTop: false, // 下拉框是否展示在输入框上方
  },
  attached() {
    this.handleData()
  },
  methods: {
    // 清空输入框
    clearInput() {
      this.setData({
        selectValue: "", //选中的value
        selectLabel: "", //选中的label
        show: false,
      })
    },
    // 下拉框收起和展开
    changeShow(e) {
      let that = this
      const query = wx.createSelectorQuery();
      // 选择当前点击的 view 元素
      query.select('.inputPlaceholder').boundingClientRect();
      query.exec(function (res) { // res[0].bottom 是元素距离可视区域顶部的距离加上元素自身的高度; res[1].scrollTop 是页面的滚动距离
        var show = !that.data.show
        if (res[0]) {
          /* that.triggerEvent("handleShow", show); // [暂未发现]处理滚动选项区域时背景页面滚动问题 */
          let toBottom = wx.getSystemInfoSync().windowHeight - res[0].bottom;
          that.setData({
            toTop: toBottom < 160 ? true : false,
            show: show
          })
        } else {
          that.setData({ show: show })
        }
      });
    },
    // 选择数据后回显
    handleChange(e) {
      let { item } = e.currentTarget.dataset
      let { labelName, valueName } = this.data
      this.setData({
        selectValue: item[valueName],
        selectLabel: item[labelName],
        show: false
      })
      let obj = {}
      obj[valueName] = item[valueName]
      obj[labelName] = item[labelName]
      this.triggerEvent("handleChange", obj);// 传参
    },
    // 匹配值并回显
    handleData() {
      let { modelValue, valueName, labelName } = this.properties;
      if (modelValue) {
        let item = this.properties.options.find(r => r[valueName] == modelValue)
        this.setData({
          selectLabel: item ? item[labelName] : modelValue,
          selectValue: modelValue,
        });
      }
    }
  }
})
3)json
{
  "component": true,
  "usingComponents": {}
}
4)wxss
/* components/wan-select/select.wxss */
.select_all_view {
  display: flex;
  justify-content: start;
  align-items: start;
  z-index: 999;
}
.select_view {
  /* min-width: 200rpx; */
  min-height: 64rpx;
}
.inputPlaceholder {
  font-size: 28rpx;
}
.icon {
  position: absolute;
  right: 12rpx;
  top: 20rpx;
}
.contentItem {
  height: 64rpx;
}
.content {
  width: calc(100% - 4px);
  margin-left: 2px;
  position: absolute;
  z-index: 999;
  max-height: 296rpx;
  background: #FFFFFF;
  /* border: 1px solid #ccc; */
  box-shadow: 0 0 4px #ccc;
  opacity: 1;
  /* margin-top: 10rpx; */
  overflow-x: hidden;
  overflow-y: scroll;
}
.triangleBox {
  position: absolute;
  z-index: 1000;
  left: 30rpx;
}
.triangle {
  position: relative;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-bottom: 10rpx solid #ccc;
}
.triangle::after {
  content: '';
  position: absolute;
  top: 3rpx;
  left: -12rpx;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-bottom: 10rpx solid #fff;
}
.triangleBox-top {
  position: absolute;
  z-index: 1000;
  left: 30rpx;
}
.triangle-top {
  position: relative;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-top: 10rpx solid #ccc;
}
.triangle-top::after {
  content: '';
  position: absolute;
  bottom: 3rpx;
  left: -12rpx;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-top: 10rpx solid #fff;
}
.is-reverse {
  transform: rotate(180deg);
}
.transfer {
  transition: transform .3s;
}
.no-reverse {
  transition: rotate(0deg);
}
5)对样式做了点调整-调整过的样式
/* components/wan-select/select.wxss */
.select_all_view {
  display: flex;
  justify-content: start;
  align-items: start;
  z-index: 999;
}
.select_view {
  /* min-width: 200rpx; */
  min-height: 64rpx;
}
.inputPlaceholder {
  font-size: 28rpx;
}
.icon {
  position: absolute;
  right: 12rpx;
  top: 20rpx;
}
.contentItem {
  height: 64rpx;
}
.content {
  width: calc(45% - 4px);
  margin-left: 2px;
  position: absolute;
  z-index: 999;
  max-height: 296rpx;
  background: #FFFFFF;
  /* border: 1px solid #ccc; */
  box-shadow: 0 0 4px #ccc;
  opacity: 1;
  /* margin-top: 10rpx; */
  overflow-x: hidden;
  overflow-y: scroll;
}
.triangleBox {
  position: absolute;
  z-index: 1000;
  left: 30rpx;
}
.triangle {
  position: relative;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-bottom: 10rpx solid #ccc;
}
.triangle::after {
  content: '';
  position: absolute;
  top: 3rpx;
  left: -12rpx;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-bottom: 10rpx solid #fff;
}
.triangleBox-top {
  position: absolute;
  z-index: 1000;
  left: 30rpx;
}
.triangle-top {
  position: relative;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-top: 10rpx solid #ccc;
}
.triangle-top::after {
  content: '';
  position: absolute;
  bottom: 3rpx;
  left: -12rpx;
  border-left: 12rpx solid transparent;
  border-right: 12rpx solid transparent;
  border-top: 10rpx solid #fff;
}
.is-reverse {
  transform: rotate(180deg);
}
.transfer {
  transition: transform .3s;
}
.no-reverse {
  transition: rotate(0deg);
}
5 使用组件
1)json文件引入组件
"wan-select": "/components/wan-select/wan-select"
2)js配置对应的参数和方法
  /**
   * 页面的初始数据
   */
  data: {
    options: [
      { label: "测试数据一", value: 1 },
      { label: "测试数据二", value: 2 },
      { label: "测试数据三", value: 3 },
      { label: "测试数据四", value: 4 },
      { label: "测试数据五", value: 5 },
      { label: "测试数据六", value: 6 },
    ],
    selectedId: 1,// 已选中值,需回显时传
    showSelect: false, // [暂未发现]处理滚动选项区域时背景页面滚动问题
  },
  handleChange(e) {
    console.log(e.detail); // {value: 3, label: "测试数据三"}
  },
  handleShow(e) {
    /*   
      // [暂未发现]处理滚动选项区域时背景页面滚动问题
      this.setData({
        showSelect: e.detail
      })
    */
  },
3)wxml使用组件
<!-- [暂未发现]处理滚动选项区域时背景页面滚动问题 -->
<!-- <page-meta page-style="{{ showSelect ? 'overflow: hidden;' : '' }}" /> -->
  <wan-select class="inputPlaceholder" options="{{options}}" labelName="label" valueName="value" modelValue="{{selectedId}}" placeholder="请选择测试数据" bindhandleChange="handleChange" title="测试下拉" readonly="{{false}}"></wan-select>
    <!-- bindhandleShow="handleShow" -->