使用场景:
产品要求需要下拉选择,并且可以搜索对应的值,针对移动端没有类似的案例,因此vant+uni-app相结合,实现了可搜索的popup,具体代码如下:
dom解构
html
<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部分:
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内容:
css
<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组件内容:
css
<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>
使用方法;
css
<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: '越南' },
])
大家可自行复制代码体验,如有不足,可留言更改;如有对大家帮助,欢迎大家点赞收藏。