UniApp智能模糊搜索下拉组件教程

前言:因项目需要下拉选择模糊匹配,翻阅资料没找到类似组件,uni-app官网也没有找到,就自己随便写了一个

效果图:
   
1. 引言:介绍组件的作用和特点
2. 组件参数详解:列出所有参数并解释
3. 使用示例:提供完整的代码示例
4. 事件说明
5. 注意事项
6. 全部代码
 
一、组件概述
fuzzySearch是一款专为UniApp设计的智能下拉选择器组件,它结合了传统下拉选择和模糊搜索功能,特别适合需要从大量选项中选择的场景。该组件具有以下核心特点:

🔍 支持模糊搜索和精确搜索两种模式

🎨 高度可定制的UI样式

📱 完美兼容H5和小程序平台

✨ 简洁易用的API设计

🚫 支持清空选项功能

二、组件参数详解

基础参数

参数名类型默认值说明
dataArray[]下拉选项的数据源
valueString/Number''当前选中的值(v-model)
placeholderString'请选择'未选择时的占位文本

数据映射配置

<fuzzySearch 
  :valueType="{label:'name', value:'id'}" 
  :data="productList"
/>
参数名类型默认值说明
valueTypeObject{label:'label', value:'value'}数据字段映射配置

搜索功能配置

<fuzzySearch 
  :filterable="true"
  :searchType="1"
  :noDataText="'未找到匹配项'"
/>
参数名类型默认值说明
filterableBooleanfalse是否启用搜索功能
searchTypeNumber1搜索类型:1-模糊搜索,2-精确搜索
noDataTextString'暂无数据'无匹配数据时显示的文本

样式定制参数

<fuzzySearch 
  :bgColor="'#F0F8FF'"
  :fontSize="'16px'"
  :placeFontSize="'14px'"
  :fontColor="'#333'"
  :arrLeft="30"
  :size="200"
/>
参数名类型默认值说明
bgColorString'#EDF4FF'输入框背景色
fontSizeString'14px'主文字大小
placeFontSizeString'12px'占位符文字大小
fontColorString'gray !important'文字颜色
placeFontColorString'gray !important'占位符文字颜色
arrLeftNumber20下拉箭头左侧偏移量(px)
sizeNumber180下拉框高度(px)
sizeIconNumber18右侧图标大小

功能开关

<fuzzySearch 
  :clearable="true"
  :showDel="true"
  :changeBtn="false"
/>
参数名类型默认值说明
clearableBooleanfalse是否显示清空按钮
showDelBooleanfalse是否显示删除图标
changeBtnBooleanfalse是否在每次选择时都触发事件

三、使用示例
   一、基本使用
<template>
  <view class="container">
    <fuzzySearch 
      :data="fruitOptions"
      v-model="selectedFruit"
      placeholder="选择喜欢的水果"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      selectedFruit: '',
      fruitOptions: [
        { id: 1, name: '苹果' },
        { id: 2, name: '香蕉' },
        { id: 3, name: '橙子' },
        { id: 4, name: '西瓜' },
        { id: 5, name: '葡萄' },
        { id: 6, name: '芒果' },
        { id: 7, name: '菠萝' },
        { id: 8, name: '草莓' }
      ]
    };
  }
};
</script>


二、高级使用(带搜索功能)

<template>
  <view>
    <fuzzySearch 
      :data="productList"
      :valueType="{ label: 'productName', value: 'sku' }"
      v-model="selectedProduct"
      :filterable="true"
      :clearable="true"
      placeholder="搜索产品..."
      bgColor="#FFFFFF"
      @input="handleProductSelect"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      selectedProduct: '',
      productList: [
        { sku: 'P001', productName: '智能手机 X1' },
        { sku: 'P002', productName: '蓝牙耳机 Pro' },
        { sku: 'P003', productName: '智能手表 S3' },
        // ...更多产品
      ]
    };
  },
  methods: {
    handleProductSelect(sku) {
      console.log('已选择产品SKU:', sku);
      // 这里可以执行选择后的操作,如获取详情等
    }
  }
};
</script>

三、事件说明
   input事件  当用户选择选项时触发,返回选中项的值
<fuzzySearch @input="handleSelect" />
javascript
methods: {
  handleSelect(value) {
    console.log('选中的值:', value);
    // 执行相关业务逻辑
  }
}


change事件
当用户清空选项时触发(需启用clearable):

<fuzzySearch :clearable="true" @change="handleClear" />
javascript
methods: {
  handleClear() {
    console.log('已清空选择');
    // 执行清空后的逻辑
  }
}



样式深度定制
通过覆盖CSS类名实现完全自定义样式:

css
/* 自定义输入框样式 */
.select_input {
  border: 1px solid #dcdfe6;
  border-radius: 8px;
  padding: 0 15px;
}

/* 自定义下拉选项样式 */
.select_content_li {
  font-size: 16px;
  color: #333;
  padding: 12px 20px;
}

/* 选中项样式 */
.select_content_li.selected {
  background-color: #f0f7ff;
  color: #409eff;
  font-weight: bold;
}

/* 下拉框箭头样式 */
.cons_arrow {
  border-width: 8px;
  left: 30px !important;
}



六、全部代码

  1、使用文件代码
<!-- @  -->
<template>
	<view class="bg-white" style="min-height: 100vh;">
		
				<view class="flex align-center">
					<text class="text-black width-30">名称:</text>
					<fuzzySearch v-model="model.insured" 
						:data="dataRange" filterable clearable :searchType='1' :value-type="{
							label: 'name',
							value: 'id'
						}" @input="handleProductSelect"   />
				</view>
				
	</view>
</template>
<script>
	
	import fuzzySearch from "@/components/acom/fuzzySearch.vue"; //模糊下拉查询
	export default {
		components: {
		
			fuzzySearch,
			
		},
		name: 'insureStart',
		data() {
			return {
				loading: false,
				
				dataRange: [{
						id: 1,
						name: '苹果'
					},
					{
						id: 2,
						name: '香蕉'
					},
					{
						id: 3,
						name: '橙子'
					},
					{
						id: 4,
						name: '西瓜'
					},
					{
						id: 5,
						name: '葡萄'
					},
					{
						id: 6,
						name: '芒果'
					},
					{
						id: 7,
						name: '菠萝'
					},
					{
						id: 8,
						name: '草莓'
					}
				],
				model: {}
			}
		},
		
		methods: {
			handleProductSelect(value) {
			      console.log('已选择value', value);
			      // 这里可以执行选择后的操作,如获取详情等
			    },
			

		}
	}
</script>


  2、fuzzySearch文件代码

<template name="fuzzySearch">
	<view class="select_wrap" style="width: 100%;">
		<!-- 输入框区域 -->
		<view :class="['select_input',isClick?'select_input_select':'']" ref="select-input"
			:style="{backgroundColor:bgColor}" style="width: 100%;">
			<view class="input_info" @click.stop="openSelect" v-if="!readonly">
				<input style="width: 100%;white-space: nowrap; overflow: hidden;text-overflow: ellipsis;"
					:placeholderStyle="`font-size:${placeFontSize};color:${placeFontColor}`" :focus="isClick"
					@input="selectData" :value="selLabel" type="text" :readonly="readonly" :disabled="readonly"
					autocomplete="off" :placeholder="placeholder" class="text_tips"
					:style="{fontSize:`${fontSize} !important`,color:`${fontColor}`}">
			</view>
			<view class="input_info" @click.stop="openSelect" v-else>
				<view :placeholder="placeholder" class="text_tips" :style="{fontSize:`${placeFontSize} !important`}">
					{{selLabel}} <text v-if="!selLabel">{{placeholder}}</text>
				</view>
			</view>
			<view class="icon_arrow">
				<view @click.stop="clearItem"
					v-if="(!value&&!clearable) || (value&&!clearable) || (!value&&clearable) && !filterable && showDel"
					:class="['arrow',show?'arrow_down':'arrow_up']"></view>
				<view class="arrow-clear" v-if="value&&clearable&& showDel">x</view>
				<uni-icons :type="'bottom'" :size="sizeIcon" color="#999" />
			</view>

		</view>
		<!-- 新增蒙层:下拉框显示时出现,点击蒙层关闭下拉(点击外部的核心) -->
		<view class="select_mask" v-if="show" @click="closeSelect"></view>

		<!-- 下拉内容区域 -->
		<view class="select_modal_con" v-if="show" @touchmove.stop.prevent="() => {}">
			<!-- #ifndef H5 -->
			<scroll-view scroll-y="true" class="select_modal select_modal_scroll">
				<view class="select_content" ref="select_content">
					<view v-for="(item, index) in showData" :key="index" class="select_content_li"
						:style="{fontSize:`${fontSize} !important`}"
						:class="{'selected': value == item[valueType.value]}" @click.stop="change(item)">
						{{item[valueType.label]}}
					</view>
					<view class="select_content_li" :style="{fontSize:`${fontSize} !important`}"
						v-if="!showData.length">{{noDataText}}</view>
				</view>
			</scroll-view>
			<!--  #endif -->
			<!-- #ifdef H5  -->
			<view class="select_modal">
				<view class="select_content" ref="select_content">
					<view v-for="(item, index) in showData" :key="index" class="select_content_li"
						:style="{fontSize:`${fontSize} !important`}"
						:class="{'selected': value == item[valueType.value]}" @click.stop="change(item)">
						{{item[valueType.label]}}
					</view>
					<view class="select_content_li" v-if="!showData.length"
						:style="{color:`${placeFontSize} !important`}">{{noDataText}}</view>
				</view>
			</view>
			<!--  #endif -->
			<view class="cons_arrow" :style="{'left': arrLeft +'px'}"></view>
		</view>
	</view>
</template>

<script>
	export default {
		name: 'fuzzySearch',
		props: {
			// 数据源:提供下拉选项的数组数据
			// 类型:Array (数组)
			// 默认值:空数组
			data: {
				type: Array,
				default: () => []
			},

			// 字段映射:定义数据中标签和值的字段名
			// 类型:Object (对象)
			// 默认值:{label: 'label', value: 'value'}
			valueType: {
				type: Object,
				default: () => ({
					label: 'label', // 显示文本字段名
					value: 'value' // 实际值字段名
				})
			},

			// 字体大小:设置输入框和选项的字体大小
			// 类型:String (字符串)
			// 默认值:'14px'
			fontSize: {
				type: String,
				default: '14px'
			},

			// 占位符字体大小:设置未选择时的提示文字大小
			// 类型:String (字符串)
			// 默认值:'12px'
			placeFontSize: {
				type: String,
				default: '12px'
			},

			// 占位符字体颜色:设置未选择时的提示文字颜色
			// 类型:String (字符串)
			// 默认值:'gray !important'
			placeFontColor: {
				type: String,
				default: 'gray !important'
			},

			// 字体颜色:设置已选择文本的颜色
			// 类型:String (字符串)
			// 默认值:'gray !important'
			fontColor: {
				type: String,
				default: 'gray !important'
			},

			// 背景颜色:设置输入框的背景色
			// 类型:String (字符串)
			// 默认值:'#EDF4FF' (浅蓝色)
			bgColor: {
				type: String,
				default: '#EDF4FF'
			},

			// 变更按钮:控制是否每次选择都触发事件
			// 类型:Boolean (布尔值)
			// 默认值:false
			changeBtn: {
				type: Boolean,
				default: false
			},

			// 显示删除图标:控制是否显示清除按钮
			// 类型:Boolean (布尔值)
			// 默认值:false
			showDel: {
				type: Boolean,
				default: false
			},

			// 当前值:组件当前选中的值(v-model绑定值)
			// 类型:String | Number (字符串或数字)
			// 默认值:空字符串
			value: {
				type: [String, Number],
				default: ''
			},

			// 可清空:控制是否显示清空按钮
			// 类型:Boolean (布尔值)
			// 默认值:false
			clearable: {
				type: Boolean,
				default: false
			},

			// 可筛选:控制是否启用搜索功能
			// 类型:Boolean (布尔值)
			// 默认值:false
			filterable: {
				type: Boolean,
				default: false
			},

			// 搜索类型:定义搜索匹配模式
			// 类型:Number (数字)
			// 默认值:1 (模糊搜索)
			// 可选值:
			//   1 - 模糊搜索(包含即匹配)
			//   2 - 精确搜索(完全匹配)
			searchType: {
				type: Number,
				default: 1
			},

			// 占位文本:未选择时显示的提示文字
			// 类型:String (字符串)
			// 默认值:'请选择'
			placeholder: {
				type: String,
				default: '请选择'
			},

			// 无数据文本:没有匹配选项时显示的文字
			// 类型:String (字符串)
			// 默认值:'暂无数据'
			noDataText: {
				type: String,
				default: '暂无数据'
			},

			// 箭头左侧位置:下拉箭头距离左侧的位置(单位px)
			// 类型:Number (数字)
			// 默认值:20
			arrLeft: {
				type: Number,
				default: 20
			},

			// 下拉框高度:下拉列表的高度(单位px)
			// 类型:Number (数字)
			// 默认值:180
			size: {
				type: Number,
				default: 180
			},

			// 图标尺寸:右侧下拉图标的尺寸
			// 类型:Number (数字)
			// 默认值:18
			sizeIcon: {
				type: Number,
				default: 18
			},
		},
		data() {
			return {
				show: false,
				readonly: true,
				isClick: false,
				totalArr: [],
				showData: [],
				selLabel: ''
			}
		},
		watch: {
			// 保持原watch不变
			'filterable': {
				immediate: true,
				deep: true,
				handler(news) {
					this.readonly = !news
				}
			},
			'data': {
				immediate: true,
				deep: true,
				handler(news) {
					this.showData = news;
					this.totalArr = news
				}
			},
			'value': {
				immediate: true,
				deep: true,
				handler(news) {
					if (news) {
						let index = this.data.findIndex(ite => ite[this.valueType.value] == news)
						if (index == -1) {
							uni.showToast({
								title: '传入的value不存在',
								icon: 'none',
								duration: 1500
							})
						} else {
							this.selLabel = this.data[index][this.valueType.label]
						}
					}
				}
			}
		},
		methods: {
			// 打开/切换下拉框(添加.stop阻止冒泡到蒙层)
			openSelect() {
				this.show = !this.show
				this.isClick = !this.isClick
			},

			// 关闭下拉框(蒙层点击时触发)
			closeSelect() {
				this.show = false
				this.isClick = false
			},

			// 选择选项(添加.stop阻止冒泡到蒙层)
			change(item) {
				if (this.value != item[this.valueType.value]) {
					this.$emit('input', item[this.valueType.value])
				}
				if (this.changeBtn) this.$emit('input', item[this.valueType.value])
				this.selLabel = item[this.valueType.label]
				this.show = false
				this.isClick = false
				this.showData = this.data
			},

			// 清空选中(添加.stop阻止冒泡到蒙层)
			clearItem() {
				if (this.clearable) {
					this.$emit('input', '')
					this.$emit('change', '')
				}
				this.selLabel = ''
			},

			// 搜索过滤(保持不变)
			selectData(e) {
				let sel = e.detail.value
				if (sel) {
					let selVal = []
					this.data.forEach((item) => {
						if (this.searchType == 1) {
							if (item[this.valueType.label].indexOf(sel) != -1) {
								selVal.push(item)
							}
						} else {
							if (item[this.valueType.label] == sel) {
								selVal.push(item)
							}
						}
					})
					this.show = true
					this.showData = selVal
				} else {
					this.showData = this.data
				}
			}
		}
	}
</script>

<style lang="less" scoped>
	.select_wrap {
		// width: 240px;
		// display: inline-block;
		position: relative;

		// 新增蒙层样式:覆盖全屏,透明背景,z-index低于下拉框
		.select_mask {
			position: fixed;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background-color: rgba(0, 0, 0, 0); // 透明蒙层,不影响视觉
			z-index: 2061; // 低于下拉框的z-index(2062)
		}

		.select_input {
			background-image: none;
			border-radius: 4px;
			box-sizing: border-box;
			color: #606266;
			display: inline-block;
			font-size: inherit;
			height: 30px;
			line-height: 30px;

			outline: none;
			box-sizing: border-box;
			transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
			width: 100%;
			padding-right: 30px;

			.input_info {
				width: 100%;
				height: 100%;
				display: flex;
				align-items: center;
			}

			.icon_arrow {
				position: absolute;
				width: 30px;
				height: 30px;
				right: 0;
				top: 0;
				text-align: center;
				color: #c0c4cc;
				cursor: pointer;
				display: flex;
				justify-content: center;
				align-items: center;
				z-index: 999;
			}
		}

		.select_input_select {
			border-color: #409eff;
		}
	}

	.select_modal_con {
		width: 100%;
		transform-origin: center top;
		z-index: 9999; // 高于蒙层的z-index(2061),确保下拉框可交互
		position: absolute;
		top: 20px;
		left: 0;
		border: 1px solid #e4e7ed;
		border-radius: 4px;
		background-color: #fff;
		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
		box-sizing: border-box;
		margin-top: 12px;

		.cons_arrow {
			position: absolute;
			display: block;
			width: 0;
			height: 0;
			border-color: transparent;
			border-style: solid;
			top: -6px;
			left: 10%;
			margin-right: 3px;
			border-top-width: 0;
			border-bottom-color: #ebeef5;
		}

		.cons_arrow:after {
			content: " ";
			border-width: 6px;
			position: absolute;
			display: block;
			width: 0;
			height: 0;
			border-color: transparent;
			border-style: solid;
			top: 1px;
			margin-left: -6px;
			border-top-width: 0;
			border-bottom-color: #fff;
		}
	}

	.select_modal {
		overflow: scroll;
		height: 160px;

		.select_content {
			list-style: none;
			padding: 6px 0;
			margin: 0;
			box-sizing: border-box;

			.select_content_li {
				padding: 0 20px;
				position: relative;
				white-space: nowrap;
				overflow: hidden;
				text-overflow: ellipsis;
				color: #606266;
				height: 30px;
				line-height: 30px;
				box-sizing: border-box;
				cursor: pointer;

				&.selected {
					color: #409eff;
					font-weight: 700;
					background-color: #f5f7fa;
				}
			}

			.select_content_li:hover {
				background-color: #f5f7fa;
			}
		}
	}

	.select_modal_scroll {
		overflow: hidden;
		height: 160px;
	}
</style>


        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一条&小鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值