index.vue 3.38 KB
<template>
  <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
    <view class="sheet">
      <view class="sheet-header">
        <text class="cancel" @click="onCancel">取消</text>
        <text class="title">{{ title }}</text>
        <text class="ok" @click="onConfirm">确认</text>
      </view>
      <view class="sheet-body">
        <view
          v-for="(opt, i) in options"
          :key="i"
          :class="['option', { selected: isSelected(opt) }]"
          @click="select(opt)"
        >
          <text class="label">{{ displayOf(opt) }}</text>
        </view>
      </view>
    </view>
  </uni-popup>
</template>

<script>
export default {
  name: 'SingleSelectSheet',
  props: {
    visible: { type: Boolean, default: false },
    title: { type: String, default: '请选择' },
    // 统一约定:options 为 [{ label: string, value: string|number }]
    options: { type: Array, default: () => [] },
    value: { type: [String, Number], default: '' }
  },
  data() {
    return {
      innerValue: this.value,
      innerOption: null
    }
  },
  watch: {
    value(v) { this.innerValue = v },
    visible(v) { v ? this.open() : this.close() }
  },
  mounted() {
    if (this.visible) this.open()
  },
  methods: {
    open() {
      // 打开弹框时回显当前已选中的值
      this.innerValue = this.value
      this.innerOption = (this.options || []).find(o => this.valueOf(o) === this.value) || null
      this.$refs.popup && this.$refs.popup.open()
      this.$emit('update:visible', true)
    },
    close() {
      // 关闭时同步回显值,避免取消后残留未确认值
      this.innerValue = this.value
      this.$refs.popup && this.$refs.popup.close()
      this.$emit('update:visible', false)
    },
    displayOf(opt) {
      if (!opt) return ''
      return opt.label != null ? String(opt.label) : ''
    },
    valueOf(opt) {
      if (!opt) return ''
      return opt.value != null ? opt.value : ''
    },
    isSelected(opt) {
      return this.valueOf(opt) === this.innerValue
    },
    select(opt) {
      this.innerValue = this.valueOf(opt)
      this.innerOption = opt
    },
    onCancel() {
      this.close()
      this.$emit('cancel')
    },
    onConfirm() {
      const val = this.innerValue
      const opt = this.innerOption || (this.options || []).find(o => this.valueOf(o) === val) || null
      const label = opt ? this.displayOf(opt) : ''
      this.$emit('confirm', { value: val, label })
      this.$emit('input', val)
      this.$emit('update:value', val)
      this.close()
    }
  }
}
</script>

<style lang="scss" scoped>
.sheet {
  width: 100%;
  max-height: 45vh;
  background: #fff;
  border-radius: 20rpx 20rpx 0 0;
  display: flex;
  flex-direction: column;
}
.sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 30rpx 32rpx;
  border-bottom: 1rpx solid #f0f0f0;
}
.title {
  font-size: 36rpx;
  font-weight: 600;
}
.cancel {
  color: rgba(0,0,0,0.6);
  font-size: 28rpx;
}
.ok {
  color: $theme-primary;
  font-size: 28rpx;
}
.sheet-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 32rpx;
}
.option {
  padding: 20rpx;
  line-height: 40rpx;
  background: #fff;
  text-align: center;
  border-radius: 12rpx;
  font-size: 32rpx;
  .label {
    color: rgba(0,0,0,0.6);
    font-size: 32rpx;
  }
  &.selected {
    background: #f3f3f3;
    .label {
      color: rgba(0,0,0,0.9);
    }
  }
}

</style>