前言:因项目需要下拉选择模糊匹配,翻阅资料没找到类似组件,uni-app官网也没有找到,就自己随便写了一个
效果图:
1. 引言:介绍组件的作用和特点
2. 组件参数详解:列出所有参数并解释
3. 使用示例:提供完整的代码示例
4. 事件说明
5. 注意事项
6. 全部代码
一、组件概述
fuzzySearch是一款专为UniApp设计的智能下拉选择器组件,它结合了传统下拉选择和模糊搜索功能,特别适合需要从大量选项中选择的场景。该组件具有以下核心特点:
🔍 支持模糊搜索和精确搜索两种模式
🎨 高度可定制的UI样式
📱 完美兼容H5和小程序平台
✨ 简洁易用的API设计
🚫 支持清空选项功能
二、组件参数详解
基础参数
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
data | Array | [] | 下拉选项的数据源 |
value | String/Number | '' | 当前选中的值(v-model) |
placeholder | String | '请选择' | 未选择时的占位文本 |
数据映射配置
<fuzzySearch :valueType="{label:'name', value:'id'}" :data="productList" />
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
valueType | Object | {label:'label', value:'value'} | 数据字段映射配置 |
搜索功能配置
<fuzzySearch :filterable="true" :searchType="1" :noDataText="'未找到匹配项'" />
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
filterable | Boolean | false | 是否启用搜索功能 |
searchType | Number | 1 | 搜索类型:1-模糊搜索,2-精确搜索 |
noDataText | String | '暂无数据' | 无匹配数据时显示的文本 |
样式定制参数
<fuzzySearch :bgColor="'#F0F8FF'" :fontSize="'16px'" :placeFontSize="'14px'" :fontColor="'#333'" :arrLeft="30" :size="200" />
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
bgColor | String | '#EDF4FF' | 输入框背景色 |
fontSize | String | '14px' | 主文字大小 |
placeFontSize | String | '12px' | 占位符文字大小 |
fontColor | String | 'gray !important' | 文字颜色 |
placeFontColor | String | 'gray !important' | 占位符文字颜色 |
arrLeft | Number | 20 | 下拉箭头左侧偏移量(px) |
size | Number | 180 | 下拉框高度(px) |
sizeIcon | Number | 18 | 右侧图标大小 |
功能开关
<fuzzySearch :clearable="true" :showDel="true" :changeBtn="false" />
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
clearable | Boolean | false | 是否显示清空按钮 |
showDel | Boolean | false | 是否显示删除图标 |
changeBtn | Boolean | false | 是否在每次选择时都触发事件 |
三、使用示例
一、基本使用
<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>