uniapp封装下拉框(支持输入、下拉)

子组件

<template>
  <view class="search-combox" :class="border ? '' : 'search-combox__no-border'">
    <view v-if="label" class="search-combox__label" :style="labelStyle">
      <text>{{ label }}</text>
    </view>
    <view class="search-combox__input-box">
      <input
        class="search-combox__input"
        type="text"
        :placeholder="placeholder"
        placeholder-class="search-combox__input-plac"
        v-model="inputVal"
        @input="onInput"
        @focus="onFocus"
        @blur="onBlur"
      />
      <uni-icons
        :type="showSelector ? 'top' : 'bottom'"
        size="14"
        color="#999"
        @click="toggleSelector"
      ></uni-icons>
    </view>
    <view class="search-combox__selector" v-if="showSelector">
      <view class="uni-popper__arrow"></view>
      <scroll-view scroll-y="true" class="search-combox__selector-scroll">
        <view
          class="search-combox__selector-empty"
          v-if="filterCandidatesLength === 0"
        >
          <text>{{ emptyTips }}</text>
        </view>
        <view
          class="search-combox__selector-item"
          v-for="(item, index) in filterCandidates"
          :key="index"
          @click="onSelectorClick(index)"
        >
          <text
            :style="
              (
                isJSON
                  ? item[keyName]
                    ? item[keyName] == inputVal
                    : false
                  : item == inputVal
              )
                ? 'font-weight: bold;background-color: ' +
                  selectedBackground +
                  ';color: ' +
                  selectedColor
                : ''
            "
          >
            {{
              isJSON
                ? item[keyName]
                  ? item[keyName]
                  : '字段' + keyName + '不存在'
                : item
            }}
          </text>
        </view>
      </scroll-view>
    </view>
  </view>
</template>

<script lang="ts" setup>
import { watch, computed, ref } from 'vue'

const props = defineProps({
  isJSON: {
    type: Boolean,
    default: false,
  },
  // 默认取text
  keyName: {
    type: String,
    default: 'text',
  },
  // 被选中的背景颜色
  selectedBackground: {
    type: String,
    default: '#e5e5e5',
  },
  // 被选中的字体颜色
  selectedColor: {
    type: String,
    default: '#3774c6',
  },
  // 选择输入框边框
  border: {
    type: Boolean,
    default: true,
  },
  // 前缀
  label: {
    type: String,
    default: '',
  },
  // 前缀宽度
  labelWidth: {
    type: String,
    default: 'auto',
  },
  // 提示文案
  placeholder: {
    type: String,
    default: '请选择',
  },
  candidates: {
    type: Array,
    /**
     * default
     */
    default: () => [],
  },
  // 无匹配项
  emptyTips: {
    type: String,
    default: '无匹配项',
  },
  // modelValue
  modelValue: {
    type: [String, Number],
    default: '',
  },
})

const emit = defineEmits(['update:modelValue', 'input', 'select'])
const isInput = ref(false)
const showSelector = ref(false)
const inputVal = ref<any>('')

const labelStyle = computed(() => {
  if (props.labelWidth === 'auto') {
    return ''
  }
  return `width: ${props.labelWidth}`
})

const filterCandidates: any = computed(() => {
  if (isInput.value) {
    if (props.isJSON) {
      return props.candidates.filter(
        (item: any) =>
          item[props.keyName].toString().indexOf(inputVal.value) > -1,
      )
    } else {
      return props.candidates.filter(
        (item: any) => item.toString().indexOf(inputVal.value) > -1,
      )
    }
  } else {
    return props.candidates
  }
})

const filterCandidatesLength = computed(() => filterCandidates.value.length)

watch(
  () => props.modelValue,
  (newVal) => {
    inputVal.value = newVal
    isInput.value = true
  },
  { immediate: true },
)

/**
 * 点击展示选项
 */
const toggleSelector = () => {
  showSelector.value = !showSelector.value
  isInput.value = false
}

/**
 * 获得焦点
 */
const onFocus = () => {
  showSelector.value = true
  isInput.value = false
}

/**
 * 失去焦点
 */
const onBlur = () => {
  setTimeout(() => {
    showSelector.value = false
    isInput.value = false
  }, 153)
}

/**
 * 选择事件
 * @param index index
 */
const onSelectorClick = (index: any) => {
  const item: any = filterCandidates.value[index]
  inputVal.value = props.isJSON ? item[props.keyName] : item
  showSelector.value = false
  emit('update:modelValue', inputVal.value)
  emit('input', inputVal.value)
  emit('select', item)
}

/**
 * 输入事件
 */
const onInput = () => {
  setTimeout(() => {
    emit('input', inputVal.value)
  })
}
</script>

<style lang="scss" scoped>
.search-combox {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
  min-height: 70rpx;
  padding: 0 15rpx 0 20rpx;
  font-size: 28rpx;
  border: 1px solid #e5e5e5;
  border-radius: 8rpx;
}

.search-combox__label {
  padding-right: 10rpx;
  color: #999999;
  font-size: 32rpx;
  line-height: 44rpx;
}

.search-combox__input-box {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: row;
  align-items: center;
}

.search-combox__input {
  flex: 1;
  height: 44rpx;
  font-size: 28rpx;
  line-height: 44rpx;
}

.search-combox__input-plac {
  color: #999;
  font-size: 12px;
}

.search-combox__selector {
  position: absolute;
  top: calc(100% + 12px);
  left: 0;
  z-index: 999;
  box-sizing: border-box;
  width: 100%;
  padding: 8rpx 0;
  background-color: #ffffff;
  border: 1px solid #ebeef5;
  border-radius: 12rpx;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.search-combox__selector-scroll {
  box-sizing: border-box;
  max-height: 300rpx;
}

.search-combox__selector-empty,
.search-combox__selector-item {
  display: flex;
  padding: 0px 0px;
  font-size: 14px;
  line-height: 70rpx;
  text-indent: 1rem;
  cursor: pointer;
}

.search-combox__selector-empty text,
.search-combox__selector-item text {
  width: 100%;
}

.search-combox__selector-item:hover {
  background-color: #e5e5e5;
}

.search-combox__selector-empty:last-child,
.search-combox__selector-item:last-child {
  border-bottom: none;
}

// picker 弹出层通用的指示小三角
.uni-popper__arrow,
.uni-popper__arrow::after {
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
  border-width: 12rpx;
}

.uni-popper__arrow {
  top: -12rpx;
  left: 80%;
  margin-right: 3px;
  border-top-width: 0;
  border-bottom-color: #ebeef5;
  filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
}

.uni-popper__arrow::after {
  top: 2rpx;
  margin-left: -12rpx;
  border-top-width: 0;
  border-bottom-color: #fff;
  content: ' ';
}

.search-combox__no-border {
  border: none;
}
</style>

父组件

 <searchCombox
    :candidates="[
      { id: '1', name: '选项一' },
      { id: '2', name: '选项二' },
      { id: '3', name: '选项三' },
      { id: '4', name: '选项四' },
      { id: '5', name: '选项五' },
    ]"
    :isJSON="true"
    keyName="name"
    placeholder="请选择归属部门"
    v-model="form.deptId"
    @select="handelSelect"
  ></searchCombox>

效果图

下拉

输入

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值