uni-app + vant 实现可搜索的popup

发布于:2024-05-04 ⋅ 阅读:(43) ⋅ 点赞:(0)

使用场景:

        产品要求需要下拉选择,并且可以搜索对应的值,针对移动端没有类似的案例,因此vant+uni-app相结合,实现了可搜索的popup,具体代码如下:

dom解构

<template>
  <!-- uni-app结合vant组件库,实现可搜索的弹层,只能单选 -->
  <view class="popup-vant-select" @click.prevent="handleOpen">
    <!-- :class="{ open: popupOpenFlag, clear: (props.clear && selectLabel) }" -->
    <text
      class="icon"
      :class="{ open: popupOpenFlag, clear: props.clear && selectLabel }"
      @click.stop="handleClear"
    ></text>
    <!-- 下拉框中显示默认的值 -->
    <view v-if="!selectLabel" class="placeholder">{{ props.placeholder }}</view>
    <!-- 下拉框中显示选择的值 -->
    <view v-else>{{ selectLabel }}</view>
    <uni-popup ref="popupRef" type="bottom" background-color="#fff" :is-mask-click="false">
      <view class="select-box">
        <view v-if="props.title" class="title">这里可以设置标题</view>
        <view class="btn-box">
          <text class="cancel" @click="handleCancel">取消</text>
          <text class="confirm" @click="handleConfirm">确定</text>
        </view>
        <CommonSearch
          v-if="props.filterable"
          @input="hanndleInput"
          placeholder="请输入"
          background="#fff"
        />
        <!-- option-height:选项高度;visible-option-num:可见的选项个数 -->
        <Picker
          :show-toolbar="false"
          v-model="selectValue"
          :columns="list"
          option-height="40rpx"
          visible-option-num="4"
          :columns-field-names="customFieldName"
        />
      </view>
    </uni-popup>
  </view>
</template>

JavaScript部分:

<script setup lang="ts">
import { ref, watch, type PropType } from 'vue'
import { Picker } from 'vant'
import 'vant/lib/picker/style'
// import type { PickerCancelEventParams, PickerChangeEventParams, PickerConfirmEventParams } from 'vant'

export interface OptionItem {
  value: number | string
  label: string
}

const props = defineProps({
  title: {
    type: String,
    default: '',
  },
  modelValue: {
    type: String || (Number as PropType<string | number>),
    default: '',
  },
  options: {
    type: Array as PropType<OptionItem[]>,
    default: () => [],
  },
  filterable: {
    type: Boolean,
    default: true,
  },
  clear: {
    type: Boolean,
    default: true,
  },
  placeholder: {
    type: String,
    default: '请选择',
  },
  // 只有单选,没有多选功能
  multiple: {
    type: Boolean,
    default: false,
  },
})
const customFieldName = {
  text: 'label',
  value: 'value',
}
const list = ref<OptionItem[]>([])

// 选中的value
const selectValue = ref<string[]>([])
// 选中的label
const selectLabel = ref<string>()
// 是否打开弹层标志【用于设置下拉框右侧图标】
const popupOpenFlag = ref(false)

// 弹出层组件的ref
const popupRef = ref<{
  open: (type?: UniHelper.UniPopupType) => void
  close: () => void
}>()

// 默认显示所有内容
watch(
  () => props.options,
  (val) => {
    list.value = val
  },
  { immediate: true, deep: true },
)

const emits = defineEmits(['update:modelValue', 'change'])

// 手动点击打开弹层
const handleOpen = () => {
  popupRef.value?.open()
  popupOpenFlag.value = true
}

// 确认选择时触发
const handleConfirm = () => {
  // if (!props.multiple) {
  //   // 单选逻辑: 单选时,只返回选中值的key即可
  //   emits('update:modelValue', selectValue.value[0])
  // } else {
  //   // 多选逻辑: 直接返回选中元素的key值数组
  //   emits('update:modelValue', selectValue.value)
  // }
  emits('update:modelValue', selectValue.value[0])
  // 如果需要在选中元素发生变化时,做一些其他操作,可以直接使用change方法
  emits('change', selectValue.value)
  selectLabel.value = handleLabel(selectValue.value[0], list.value)

  // 关闭popup弹层
  popupRef.value?.close()
  popupOpenFlag.value = false
}

// 取消时触发
const handleCancel = () => {
  popupRef.value?.close()
  popupOpenFlag.value = false
}

// 根据value查找对应的label
const handleLabel = (value: string | number, options: OptionItem[]) => {
  const item = options.find((e) => e.value === value)
  return item?.label
}

// 搜索
const hanndleInput = (val: string) => {
  if (!val) {
    // 当输入值为空时,不过滤
    list.value = JSON.parse(JSON.stringify(props.options))
  } else {
    // 根据输入的值,过滤下拉选项
    let res: OptionItem[] = []
    let arr: OptionItem[] = []
    // 将输入的关键词,切割成数组,检查下拉选项中,是否包含各个字符,利用filter去重
    const strArr: string[] = val
      .split('')
      .filter((item, index, self) => self.indexOf(item) === index)
    strArr.forEach((str) => {
      // 只要包含有输入的字符,都筛选出来
      arr = props.options.filter((e) => e.label.indexOf(str) > -1)
      // 将模糊搜索到的下拉选项赋值给res
      res = res.concat(arr)
    })
    // 下拉选项赋值
    list.value = res
  }
}

// 清空选项内容
const handleClear = () => {
  selectValue.value = []
  selectLabel.value = ''
}
</script>

style内容:

<style lang="scss" scoped>
.popup-vant-select {
  /** 此样式是下拉框的样式 */
  position: relative;
  background-color: #fff;
  width: 100%;
  height: 80rpx;
  // line-height: 80rpx;
  border-radius: 9rpx;
  border: 1rpx solid #e9ebf0;
  font-family: PingFangSC, PingFangSC-Semibold;
  font-size: 32rpx;
  font-weight: 400;
  /** 此处设置padding-top而不使用line-height的原因: 是因为该组件内部使用LyenuSearch,如果设置了line-height,则会影响LyenuSearch中的图标位置 */
  padding: 18rpx 10rpx 0 20rpx;

  .placeholder {
    color: #98a0b3;
    font-size: 28rpx;
    font-weight: 400;
  }

  .select-box {
    // height: 30vh;
    background-color: #fff;
    padding: 30rpx 0 100rpx;

    .title {
      text-align: center;
      color: #262e40;
      font-weight: 600;
    }

    .btn-box {
      display: flex;
      justify-content: space-between;
      border-bottom: 1px solid #e9ebf0;
      padding: 30rpx;

      .cancel {
        color: #888;
      }

      .confirm {
        color: $theme-color-primary;
      }
    }
  }

  :deep(.van-picker-column__item--selected) {
    font-weight: 600;
  }

  .icon::after {
    // 字体图标右箭头
    content: '\e602';
    font-family: 'iconfont';
    position: absolute;
    right: 10rpx;
  }

  .open::after {
    // 字体图标下箭头
    content: '\e605';
    font-family: 'iconfont';
  }

  .clear::after {
    // 关闭按钮
    content: '\e603';
    font-family: 'iconfont';
    font-size: 20rpx;
  }
}
</style>

附加CommonSearch组件内容:

<template>
  <view class="search-box" :style="setBackGround">
    <input
      class="input"
      type="text"
      :placeholder="props.placeholder"
      v-model="content"
      :confirm-type="props.confimrType"
      @confirm="handleConfirm"
      @input="handleInput"
    />
    <view class="search-icon" @click="hanndleSearch">
      <text class="iconfont icon-sousuo"></text>
    </view>
  </view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'

const props = defineProps({
  placeholder: {
    type: String,
    default: '请输入',
  },
  // 设置键盘右下角按钮的文字
  confimrType: {
    type: String,
    // 可输入的值有:seand(发送)、search(搜索)、next(下一个)、go(前往)、done(完成)
    default: 'done',
  },
  background: {
    type: String,
    default: '#f3f7fa',
  },
})

const content = ref()

const emit = defineEmits(['change', 'confirm', 'input'])

// 点击小图片,确认搜索
const hanndleSearch = () => {
  emit('change', content.value)
}
// 点击输入键盘的右下角的按钮
const handleConfirm = () => {
  emit('confirm', content.value)
}
// 实时输入事件
const handleInput = () => {
  emit('input', content.value)
}

// 设置背景
const setBackGround = computed(() => `background-color: ${props.background};`)
</script>
<style lang="scss" scoped>
.search-box {
  position: fixed;
  // background-color: #f3f7fa;
  width: 100%;
  z-index: 5;

  .input {
    width: 690rpx;
    height: 76rpx;
    margin: 30rpx;
    padding: 0 60rpx 0 20rpx;
    border-radius: 45rpx;
    border: 1rpx solid #dcdfe6;
    font-size: 28rpx;
  }

  .input-placeholder {
    color: #dcdfe6;
    font-size: 28rpx;
  }

  .search-icon {
    width: 34rpx;
    height: 36rpx;
    z-index: 8;
    position: absolute;
    right: 50rpx;
    top: 50rpx;
    /* 防止图标遮挡输入框点击事件 */
    // pointer-events: none;
    font-size: 28rpx;
  }
}
</style>

使用方法;

<VantSelect v-model="selectValue" :options="countryOptions" />

const selectValue = ref('')

const countryOptions = ref([
  { value: 'china', label: '中国' },
  { value: 'USA', label: '美国' },
  { value: 'Brazil', label: '巴西' },
  { value: 'Japan', label: '日本' },
  { value: 'SouthKorea', label: '韩国' },
  { value: 'NorthKorea', label: '朝鲜' },
  { value: 'Vietnam', label: '越南' },
])

大家可自行复制代码体验,如有不足,可留言更改;如有对大家帮助,欢迎大家点赞收藏。


网站公告

今日签到

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