Skip to content

Select组件完整代码

wxml

html
<view class="dropdown_component dropdown_wrapper" catch:tap="toggleDropdown">
  <slot></slot>

  <view
    wx:if="{{showSelectText}}"
    class="dropdown_sel_text custom_option_select_class"
    >{{triggerText}}</view
  >

  <slot name="slot_right"></slot>

  <!-- 选项列表 -->
  <!-- 这里需要动态计算展开后的高度,否则动画上会出现意料之外的效果 -->
  <scroll-view
    scroll-y="{{true}}"
    class="dropdown_options custom_options_class {{showDropdown?'show':'hide'}}"
    style="height: {{showDropdown ? options.length * 76 : 0}}rpx;"
  >
    <view
      class="dropdown_option custom_option_class {{item.value === value ? 'active' : ''}}"
      style="{{ item.value === value  ? 'color:' + activeColor : '' }}"
      wx:for="{{ options }}"
      wx:key="value"
      catch:tap="selectOption"
      data-value="{{ item.value }}"
    >
      {{ item.text }}
    </view>
  </scroll-view>
</view>

js

js
/**
 * Select 下拉组件
 * - 可联动:在组件上设置`class`和`classSelector`两个属性
 */
Component({
  options: {
    multipleSlots: true, // 在组件定义时的选项中启用多slot支持
  },

  /**
   * 组件:<custom-component class="custom_options_class"></custom-component>
   * 页面:<custom-component custom_options_class="red-text" />
   */
  externalClasses: ["custom_options_class", "custom_option_select_class"],

  properties: {
    value: {
      type: [String, Number],
      optionalTypes: [null, undefined],
      value: "",
      observer(newV, oldV) {
        if (this.properties.options.length) {
          this.updateOptionText(newV);
        }
      },
    },
    options: {
      type: Array,
      value: [],
      observer(newV, oldV) {
        if (newV.length) {
          this.updateOptionText(this.properties.value);
        }
      },
    },
    showSelectText: {
      type: Boolean,
      value: true,
    },
    activeColor: {
      type: String,
      value: "#000000",
    },
    /**
     * 若要做到展开新的,关闭其它已经展开的组件,需要在使用组件时设置 `class`和`classSelector`两个属性
     * 例如:<dropdown class="dd_comp" classSelector=".dd_comp" />
     * 这样才会产生联动效果,具体实现见 `closeBrothers` 方法
     */
    classSelector: {
      type: String,
      optionalTypes: [null, undefined],
      value: "",
    },
  },

  data: {
    showDropdown: false,
  },

  lifetimes: {
    attached() {},
    ready() {},
  },

  methods: {
    closeBrothers() {
      if (!this.properties.classSelector) {
        return;
      }

      // 获取组件使用者,相当于拿到兄弟组件的关联容器
      let owner = this.selectOwnerComponent();
      // 获取所有兄弟组件实例
      let brothers = owner.selectAllComponents(this.properties.classSelector);
      if (brothers.length) {
        brothers.map((compItem) => {
          // 关闭当前状态是“展开”的组件
          if (compItem.data.showDropdown) {
            compItem.close();
          }
        });
      }
    },
    /**
     * 切换“展开”、“关闭”
     */
    toggleDropdown() {
      if (this.data.showDropdown) {
        this.close();
      } else {
        // 关闭其它打开的dropdown组件
        this.closeBrothers();
        this.open();
      }
    },
    open() {
      this.setData({
        showDropdown: true,
      });
    },
    close() {
      this.setData({
        showDropdown: false,
      });
    },
    selectOption(event) {
      const { value } = event.currentTarget.dataset;
      this.triggerEvent(
        "onchange",
        {
          value,
          option: this.getSelOption(value),
        },
        {}
      );

      this.updateOptionText(value);

      this.toggleDropdown();
    },
    /**
     * 更新选中后展示的选项文字,并通知父组件更新menu展示项的title
     * @param {*} value
     */
    updateOptionText(value) {
      this.setData({
        triggerText: this.getSelOption(value)?.text || "",
      });
    },
    /**
     * 更新根据value选中项后,展示的title文字
     * @param {*} value
     */
    getSelOption(value) {
      const option = this.properties.options.find(
        (item) => item.value === value
      );
      return option || {};
    },
  },
});

wxss

css
.dropdown_wrapper {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}

.dropdown_options {
  overflow: visible !important;
  position: absolute;
  top: calc(100% + 20rpx);
  right: 0;
  min-width: 60px;
  max-width: 150px;
  z-index: 1001;
  background-color: #fff;
  padding: 0;
  transition: all 0.2s ease-out;
  border-radius: 16rpx;
  border-left: solid 1rpx #f1f1f1;
  border-right: solid 1rpx #f1f1f1;
  box-sizing: border-box;
}

.show {
  /* height: xxx; 实时计算 */
  max-height: 400rpx;
  border-top: solid 1rpx #f1f1f1;
  border-bottom: solid 1rpx #f1f1f1;
  box-shadow: 0 0 18rpx 10rpx #f1f1f1;
}

.show::before {
  content: "";
  /* border: solid 20rpx transparent;
	border-bottom: solid 30rpx #fdd000; */

  height: 26rpx;
  width: 26rpx;
  background-color: #fff;
  box-shadow: -10rpx -10rpx 18rpx 0 #f1f1f1;

  position: absolute;
  top: -10rpx;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  z-index: 0;
}

.hide {
  height: 0;
  max-height: 0;
  border-top: none;
  border-bottom: none;
}

.dropdown_option {
  text-align: center;
  padding: 20rpx;
  font-size: 26rpx;
}

.dropdown_option:nth-child(2n) {
  border-top: solid 1rpx #ccc;
  border-bottom: solid 1rpx #ccc;
}

.dropdown_sel_text {
  font-size: 30rpx;
  color: #000;
}

.dropdown_sel_text {
  font-size: 30rpx;
  color: #000;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  width: 100%;
}

分析记录

Released under the MIT License.