当前位置:   article > 正文

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

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

使用场景:

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

dom解构

  1. <template>
  2. <!-- uni-app结合vant组件库,实现可搜索的弹层,只能单选 -->
  3. <view class="popup-vant-select" @click.prevent="handleOpen">
  4. <!-- :class="{ open: popupOpenFlag, clear: (props.clear && selectLabel) }" -->
  5. <text
  6. class="icon"
  7. :class="{ open: popupOpenFlag, clear: props.clear && selectLabel }"
  8. @click.stop="handleClear"
  9. ></text>
  10. <!-- 下拉框中显示默认的值 -->
  11. <view v-if="!selectLabel" class="placeholder">{{ props.placeholder }}</view>
  12. <!-- 下拉框中显示选择的值 -->
  13. <view v-else>{{ selectLabel }}</view>
  14. <uni-popup ref="popupRef" type="bottom" background-color="#fff" :is-mask-click="false">
  15. <view class="select-box">
  16. <view v-if="props.title" class="title">这里可以设置标题</view>
  17. <view class="btn-box">
  18. <text class="cancel" @click="handleCancel">取消</text>
  19. <text class="confirm" @click="handleConfirm">确定</text>
  20. </view>
  21. <CommonSearch
  22. v-if="props.filterable"
  23. @input="hanndleInput"
  24. placeholder="请输入"
  25. background="#fff"
  26. />
  27. <!-- option-height:选项高度;visible-option-num:可见的选项个数 -->
  28. <Picker
  29. :show-toolbar="false"
  30. v-model="selectValue"
  31. :columns="list"
  32. option-height="40rpx"
  33. visible-option-num="4"
  34. :columns-field-names="customFieldName"
  35. />
  36. </view>
  37. </uni-popup>
  38. </view>
  39. </template>

JavaScript部分:

  1. <script setup lang="ts">
  2. import { ref, watch, type PropType } from 'vue'
  3. import { Picker } from 'vant'
  4. import 'vant/lib/picker/style'
  5. // import type { PickerCancelEventParams, PickerChangeEventParams, PickerConfirmEventParams } from 'vant'
  6. export interface OptionItem {
  7. value: number | string
  8. label: string
  9. }
  10. const props = defineProps({
  11. title: {
  12. type: String,
  13. default: '',
  14. },
  15. modelValue: {
  16. type: String || (Number as PropType<string | number>),
  17. default: '',
  18. },
  19. options: {
  20. type: Array as PropType<OptionItem[]>,
  21. default: () => [],
  22. },
  23. filterable: {
  24. type: Boolean,
  25. default: true,
  26. },
  27. clear: {
  28. type: Boolean,
  29. default: true,
  30. },
  31. placeholder: {
  32. type: String,
  33. default: '请选择',
  34. },
  35. // 只有单选,没有多选功能
  36. multiple: {
  37. type: Boolean,
  38. default: false,
  39. },
  40. })
  41. const customFieldName = {
  42. text: 'label',
  43. value: 'value',
  44. }
  45. const list = ref<OptionItem[]>([])
  46. // 选中的value
  47. const selectValue = ref<string[]>([])
  48. // 选中的label
  49. const selectLabel = ref<string>()
  50. // 是否打开弹层标志【用于设置下拉框右侧图标】
  51. const popupOpenFlag = ref(false)
  52. // 弹出层组件的ref
  53. const popupRef = ref<{
  54. open: (type?: UniHelper.UniPopupType) => void
  55. close: () => void
  56. }>()
  57. // 默认显示所有内容
  58. watch(
  59. () => props.options,
  60. (val) => {
  61. list.value = val
  62. },
  63. { immediate: true, deep: true },
  64. )
  65. const emits = defineEmits(['update:modelValue', 'change'])
  66. // 手动点击打开弹层
  67. const handleOpen = () => {
  68. popupRef.value?.open()
  69. popupOpenFlag.value = true
  70. }
  71. // 确认选择时触发
  72. const handleConfirm = () => {
  73. // if (!props.multiple) {
  74. // // 单选逻辑: 单选时,只返回选中值的key即可
  75. // emits('update:modelValue', selectValue.value[0])
  76. // } else {
  77. // // 多选逻辑: 直接返回选中元素的key值数组
  78. // emits('update:modelValue', selectValue.value)
  79. // }
  80. emits('update:modelValue', selectValue.value[0])
  81. // 如果需要在选中元素发生变化时,做一些其他操作,可以直接使用change方法
  82. emits('change', selectValue.value)
  83. selectLabel.value = handleLabel(selectValue.value[0], list.value)
  84. // 关闭popup弹层
  85. popupRef.value?.close()
  86. popupOpenFlag.value = false
  87. }
  88. // 取消时触发
  89. const handleCancel = () => {
  90. popupRef.value?.close()
  91. popupOpenFlag.value = false
  92. }
  93. // 根据value查找对应的label
  94. const handleLabel = (value: string | number, options: OptionItem[]) => {
  95. const item = options.find((e) => e.value === value)
  96. return item?.label
  97. }
  98. // 搜索
  99. const hanndleInput = (val: string) => {
  100. if (!val) {
  101. // 当输入值为空时,不过滤
  102. list.value = JSON.parse(JSON.stringify(props.options))
  103. } else {
  104. // 根据输入的值,过滤下拉选项
  105. let res: OptionItem[] = []
  106. let arr: OptionItem[] = []
  107. // 将输入的关键词,切割成数组,检查下拉选项中,是否包含各个字符,利用filter去重
  108. const strArr: string[] = val
  109. .split('')
  110. .filter((item, index, self) => self.indexOf(item) === index)
  111. strArr.forEach((str) => {
  112. // 只要包含有输入的字符,都筛选出来
  113. arr = props.options.filter((e) => e.label.indexOf(str) > -1)
  114. // 将模糊搜索到的下拉选项赋值给res
  115. res = res.concat(arr)
  116. })
  117. // 下拉选项赋值
  118. list.value = res
  119. }
  120. }
  121. // 清空选项内容
  122. const handleClear = () => {
  123. selectValue.value = []
  124. selectLabel.value = ''
  125. }
  126. </script>

style内容:

  1. <style lang="scss" scoped>
  2. .popup-vant-select {
  3. /** 此样式是下拉框的样式 */
  4. position: relative;
  5. background-color: #fff;
  6. width: 100%;
  7. height: 80rpx;
  8. // line-height: 80rpx;
  9. border-radius: 9rpx;
  10. border: 1rpx solid #e9ebf0;
  11. font-family: PingFangSC, PingFangSC-Semibold;
  12. font-size: 32rpx;
  13. font-weight: 400;
  14. /** 此处设置padding-top而不使用line-height的原因: 是因为该组件内部使用LyenuSearch,如果设置了line-height,则会影响LyenuSearch中的图标位置 */
  15. padding: 18rpx 10rpx 0 20rpx;
  16. .placeholder {
  17. color: #98a0b3;
  18. font-size: 28rpx;
  19. font-weight: 400;
  20. }
  21. .select-box {
  22. // height: 30vh;
  23. background-color: #fff;
  24. padding: 30rpx 0 100rpx;
  25. .title {
  26. text-align: center;
  27. color: #262e40;
  28. font-weight: 600;
  29. }
  30. .btn-box {
  31. display: flex;
  32. justify-content: space-between;
  33. border-bottom: 1px solid #e9ebf0;
  34. padding: 30rpx;
  35. .cancel {
  36. color: #888;
  37. }
  38. .confirm {
  39. color: $theme-color-primary;
  40. }
  41. }
  42. }
  43. :deep(.van-picker-column__item--selected) {
  44. font-weight: 600;
  45. }
  46. .icon::after {
  47. // 字体图标右箭头
  48. content: '\e602';
  49. font-family: 'iconfont';
  50. position: absolute;
  51. right: 10rpx;
  52. }
  53. .open::after {
  54. // 字体图标下箭头
  55. content: '\e605';
  56. font-family: 'iconfont';
  57. }
  58. .clear::after {
  59. // 关闭按钮
  60. content: '\e603';
  61. font-family: 'iconfont';
  62. font-size: 20rpx;
  63. }
  64. }
  65. </style>

附加CommonSearch组件内容:

  1. <template>
  2. <view class="search-box" :style="setBackGround">
  3. <input
  4. class="input"
  5. type="text"
  6. :placeholder="props.placeholder"
  7. v-model="content"
  8. :confirm-type="props.confimrType"
  9. @confirm="handleConfirm"
  10. @input="handleInput"
  11. />
  12. <view class="search-icon" @click="hanndleSearch">
  13. <text class="iconfont icon-sousuo"></text>
  14. </view>
  15. </view>
  16. </template>
  17. <script setup lang="ts">
  18. import { ref, computed } from 'vue'
  19. const props = defineProps({
  20. placeholder: {
  21. type: String,
  22. default: '请输入',
  23. },
  24. // 设置键盘右下角按钮的文字
  25. confimrType: {
  26. type: String,
  27. // 可输入的值有:seand(发送)、search(搜索)、next(下一个)、go(前往)、done(完成)
  28. default: 'done',
  29. },
  30. background: {
  31. type: String,
  32. default: '#f3f7fa',
  33. },
  34. })
  35. const content = ref()
  36. const emit = defineEmits(['change', 'confirm', 'input'])
  37. // 点击小图片,确认搜索
  38. const hanndleSearch = () => {
  39. emit('change', content.value)
  40. }
  41. // 点击输入键盘的右下角的按钮
  42. const handleConfirm = () => {
  43. emit('confirm', content.value)
  44. }
  45. // 实时输入事件
  46. const handleInput = () => {
  47. emit('input', content.value)
  48. }
  49. // 设置背景
  50. const setBackGround = computed(() => `background-color: ${props.background};`)
  51. </script>
  52. <style lang="scss" scoped>
  53. .search-box {
  54. position: fixed;
  55. // background-color: #f3f7fa;
  56. width: 100%;
  57. z-index: 5;
  58. .input {
  59. width: 690rpx;
  60. height: 76rpx;
  61. margin: 30rpx;
  62. padding: 0 60rpx 0 20rpx;
  63. border-radius: 45rpx;
  64. border: 1rpx solid #dcdfe6;
  65. font-size: 28rpx;
  66. }
  67. .input-placeholder {
  68. color: #dcdfe6;
  69. font-size: 28rpx;
  70. }
  71. .search-icon {
  72. width: 34rpx;
  73. height: 36rpx;
  74. z-index: 8;
  75. position: absolute;
  76. right: 50rpx;
  77. top: 50rpx;
  78. /* 防止图标遮挡输入框点击事件 */
  79. // pointer-events: none;
  80. font-size: 28rpx;
  81. }
  82. }
  83. </style>

使用方法;

  1. <VantSelect v-model="selectValue" :options="countryOptions" />
  2. const selectValue = ref('')
  3. const countryOptions = ref([
  4. { value: 'china', label: '中国' },
  5. { value: 'USA', label: '美国' },
  6. { value: 'Brazil', label: '巴西' },
  7. { value: 'Japan', label: '日本' },
  8. { value: 'SouthKorea', label: '韩国' },
  9. { value: 'NorthKorea', label: '朝鲜' },
  10. { value: 'Vietnam', label: '越南' },
  11. ])

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/539937
推荐阅读
相关标签
  

闽ICP备14008679号