最近在 uni-app 项目中需要一个功能更强的组合框,我对官方的 uni-combox 组件进行了一些实用改造。本文将详细介绍我添加的新功能,并与官方原版进行对比,帮助你在项目中更好地使用这个增强版组件。
一、功能对比一览表
| 功能点 | 官方原版 uni-combox | 增强版 uni-combox | 说明 |
|---|---|---|---|
| 基础输入选择 | ✅ 支持 | ✅ 支持 | 既可输入也可从列表选择 |
左侧标签 (label) |
✅ 支持 | ✅ 支持 | 带标签宽度控制 |
| 候选项筛选 | ✅ 支持 | ✅ 支持 | 根据输入内容过滤 |
无匹配提示 (emptyTips) |
✅ 支持 | ✅ 支持 | 可自定义提示文字 |
双向绑定 (v-model) |
✅ 支持 | ✅ 支持 | 支持 Vue2/Vue3 |
边框控制 (border) |
❌ 不支持 | ✅ 支持 | 控制是否显示组件边框 |
清除按钮 (clearAble) |
❌ 不支持 | ✅ 支持 | 一键清空输入内容 |
下拉方向 (toward) |
❌ 不支持(仅向下) | ✅ 支持 | 支持向上/向下展开 |
| 方向箭头指示器 | ❌ 不支持 | ✅ 支持 | 根据方向显示对应箭头 |
@input 事件 |
✅ 支持 | ✅ 支持 | 输入时触发 |
@change 事件 |
❌ 不支持 | ✅ 支持 | 仅选择候选项时触发 |
| 候选列表滚动 | ❌ 不支持 | ✅ 支持 | 超出 200px 可滚动 |
| 下拉阴影效果 | ❌ 不支持 | ✅ 支持 | 增强视觉层次 |
| H5 悬停效果 | ❌ 不支持 | ✅ 支持 | 候选项鼠标悬停样式 |
二、增强版完整代码
组件模板 (uni-combox.vue)
html
<template>
<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
<view v-if="label" class="uni-combox__label" :style="labelStyle">
<text>{{label}}</text>
</view>
<view class="uni-combox__input-box">
<input class="uni-combox__input" type="text" :placeholder="placeholder"
placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
@blur="onBlur" />
<view v-if="!inputVal || !clearAble"
style="width: 50rpx;height: 50rpx;line-height: 50rpx;text-align: center;" @click.stop="toggleSelector">
<uni-icons :type="showSelector ? 'top' : 'bottom'" size="19" color="#999">
</uni-icons>
</view>
<uni-icons v-if="inputVal && clearAble" type="clear" size="24" color="#999" @click="clean">
</uni-icons>
</view>
<view class="uni-combox__selector" :class="'uni-combox__selector--' + toward" v-if="showSelector">
<view :class="toward == 'top' ? 'arrow-down' : 'arrow-up'"></view>
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
<view class="uni-combox__selector-empty" v-if="filterCandidates.length == 0"
@click.stop="onSelectorClick('empty')">
<text>{{emptyTips}}</text>
</view>
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index">
<text @click.stop="onSelectorClick(index)">{{item}}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
/**
* Combox 组合输入框(增强版)
* @description 组合输入框一般用于既可以输入也可以选择的场景
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
* @property {String} label 左侧文字
* @property {String} labelWidth 左侧内容宽度
* @property {String} placeholder 输入框占位符
* @property {Array} candidates 候选项列表
* @property {String} emptyTips 筛选结果为空时显示的文字
* @property {String} value 组合框的值(Vue2)
* @property {String} modelValue 组合框的值(Vue3)
* @property {Boolean} clearAble 是否显示清除按钮(新增)
* @property {Boolean} border 是否显示边框(新增)
* @property {String} toward 下拉方向,可选值 'top'/'bottom'(新增)
* @event {Function} input 输入时触发,返回当前值
* @event {Function} change 选择候选项时触发,返回选中值(新增)
* @event {Function} update:modelValue Vue3 双向绑定事件
*/
export default {
name: 'uniCombox',
emits: ['input', 'update:modelValue', 'change'],
props: {
// 新增:是否显示清除按钮
clearAble: {
type: Boolean,
default: false
},
// 新增:是否显示边框
border: {
type: Boolean,
default: true
},
label: {
type: String,
default: ''
},
labelWidth: {
type: String,
default: 'auto'
},
placeholder: {
type: String,
default: ''
},
candidates: {
type: Array,
default () {
return []
}
},
emptyTips: {
type: String,
default: '无匹配项'
},
// 新增:下拉方向
toward: {
type: String,
default: 'bottom',
validator: (v) => ['top', 'bottom'].indexOf(v) > -1
},
// #ifndef VUE3
value: {
type: [String, Number],
default: ''
},
// #endif
// #ifdef VUE3
modelValue: {
type: [String, Number],
default: ''
},
// #endif
},
data() {
return {
showSelector: false,
inputVal: '',
blurTimer: null
}
},
computed: {
labelStyle() {
if (this.labelWidth === 'auto') {
return ""
}
return `width: ${this.labelWidth}`
},
filterCandidates() {
return this.candidates.filter((item) => {
return item.toString().indexOf(this.inputVal) > -1
})
},
},
watch: {
// #ifndef VUE3
value: {
handler(newVal) {
this.inputVal = newVal == null ? '' : String(newVal)
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal) {
this.inputVal = newVal == null ? '' : String(newVal)
},
immediate: true
},
// #endif
},
methods: {
// 切换下拉框显示状态
toggleSelector() {
this.showSelector = !this.showSelector
},
// 点击候选项
onSelectorClick(index) {
// 无匹配项,弹框关闭
if (index == 'empty') {
this.inputVal = ''
this.showSelector = false
return false;
}
// 选择有值的情况
const selectVal = this.filterCandidates[index]
this.inputVal = selectVal == null ? '' : String(selectVal)
this.showSelector = false
// 新增:change 事件,仅在选择候选项时触发
this.$emit('change', selectVal)
this.$emit('input', selectVal)
this.$emit('update:modelValue', selectVal)
},
// 输入事件
onInput() {
setTimeout(() => {
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
})
},
// 获得焦点时显示下拉框
onFocus() {
if (this.blurTimer) {
clearTimeout(this.blurTimer)
this.blurTimer = null
}
this.showSelector = true
},
// 失去焦点时延迟关闭(注释了延迟,可根据需要开启)
onBlur() {
// if (this.blurTimer) clearTimeout(this.blurTimer)
// this.blurTimer = setTimeout(() => {
// this.showSelector = false
// this.blurTimer = null
// }, 280)
},
// 新增:清空输入内容
clean() {
this.inputVal = ''
this.onInput()
}
}
}
</script>
<style lang="scss" scoped>
.uni-combox {
font-size: 14px;
border: 1px solid #DCDFE6;
border-radius: 4px;
padding: 6px 10px;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
.uni-combox__label {
font-size: 16px;
line-height: 22px;
padding-right: 10px;
color: #999999;
}
.uni-combox__input-box {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
}
.uni-combox__input {
flex: 1;
font-size: 14px;
height: 22px;
line-height: 22px;
}
.uni-combox__input-plac {
font-size: 14px;
color: #999;
}
.uni-combox__selector {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
position: absolute;
left: 0;
width: 100%;
background-color: #FFFFFF;
border: 1px solid #EBEEF5;
border-radius: 6px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 2000;
padding: 4px 0;
}
/* 新增:下拉方向样式 */
.uni-combox__selector--bottom {
top: calc(100% + 12px);
}
.uni-combox__selector--top {
bottom: calc(100% + 12px);
}
.uni-combox__selector-scroll {
/* #ifndef APP-NVUE */
max-height: 200px;
box-sizing: border-box;
/* #endif */
}
.uni-combox__selector-empty,
.uni-combox__selector-item {
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
line-height: 36px;
font-size: 14px;
padding: 0px 10px;
}
/* 新增:箭头公共样式 */
.arrow-up,
.arrow-down {
position: absolute;
left: 10%;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
}
/* 朝上箭头(下拉框在下方时显示) */
.arrow-up {
top: -6px;
border-bottom: 6px solid #fff;
border-top: none;
}
/* 朝下箭头(下拉框在上方时显示) */
.arrow-down {
bottom: -6px;
border-top: 6px solid #fff;
border-bottom: none;
}
/* 新增:无边框样式 */
.uni-combox__no-border {
border: none;
}
</style>
三、使用示例
基本使用(发票抬头选择器)
html
<uni-combox
class="CompanyName"
@input="inputChange"
@change="changeHandle"
:candidates="candidates"
toward="top"
placeholder="请选择发票抬头"
clearAble
v-model="formData.buyer_name">
</uni-combox>
完整页面示例
javascript
<template>
<view class="container">
<!-- 示例1:带清除按钮的普通下拉 -->
<uni-combox
clearAble
:candidates="cities"
placeholder="请选择城市"
v-model="city">
</uni-combox>
<!-- 示例2:向上展开模式 -->
<uni-combox
toward="top"
:border="true"
label="发票抬头"
:candidates="invoiceList"
placeholder="请选择或输入发票抬头"
clearAble
@change="onInvoiceChange">
</uni-combox>
<!-- 示例3:无边框模式 -->
<uni-combox
:border="false"
:candidates="['选项1', '选项2', '选项3']"
placeholder="无边框组合框">
</uni-combox>
<!-- 示例4:自定义空提示 -->
<uni-combox
emptyTips="暂无匹配的选项"
:candidates="[]"
placeholder="输入后无匹配项">
</uni-combox>
</view>
</template>
<script>
export default {
data() {
return {
city: '',
cities: ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '西安'],
invoiceList: ['XX科技有限公司', 'YY贸易有限公司', 'ZZ集团', 'AA股份有限公司'],
formData: {
buyer_name: ''
}
}
},
methods: {
inputChange(val) {
console.log('输入中:', val)
},
changeHandle(val) {
console.log('选择了候选项:', val)
},
onInvoiceChange(val) {
uni.showToast({
title: `已选择:${val}`,
icon: 'success'
})
}
}
}
</script>
四、核心改进说明
1. 新增 props 说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
clearAble |
Boolean | false |
是否显示清除按钮,为 true 时输入内容后右侧显示清除图标 |
border |
Boolean | true |
是否显示组件边框,设为 false 可去除边框 |
toward |
String | 'bottom' |
下拉列表展开方向,可选 'top'(向上)或 'bottom'(向下) |
2. 新增事件说明
| 事件名 | 说明 | 返回值 |
|---|---|---|
@change |
仅在点击选择候选项 时触发,区别于每次输入都触发的 @input |
选中的候选项的值 |
@input |
任何输入或选择都会触发 | 当前输入框的值 |
3. 交互逻辑优化
-
清除按钮优先级:当同时存在清除按钮和下拉切换按钮时,清除按钮优先显示
-
方向箭头适配 :根据
toward属性自动显示对应方向的三角形箭头 -
选中后自动关闭:点击候选项后立即关闭下拉列表,避免遮拦
-
无匹配项处理 :点击
emptyTips区域时清空输入并关闭下拉框
五、注意事项
-
NVUE 限制:官方组件本身不支持 nvue,增强版同样受此限制
-
依赖 uni-icons :组件使用了
uni-icons,请确保项目中已安装该组件库 -
事件冒泡处理 :清除按钮和切换按钮均使用了
@click.stop阻止事件冒泡 -
失焦行为:当前版本注释了延迟关闭逻辑,可根据实际需求取消注释
-
Vue2/Vue3 兼容 :已通过条件编译处理
value和modelValue的差异,两个版本均可正常使用
六、总结
本增强版 uni-combox 在保持官方原有功能的基础上,新增了以下核心特性:
-
✅ 边框控制 (
border) -
✅ 清除按钮 (
clearAble) -
✅ 下拉方向 (
toward+ 箭头指示器) -
✅ change 事件(区分输入和选择)
-
✅ 候选列表滚动(超出滚动)
-
✅ 视觉增强(阴影、悬停效果)
这些改进使得组件更适用于复杂的表单场景,尤其是位于页面边缘需要向上展开、需要快速清空内容、或需要区分用户交互行为的情况。
你可以在项目中直接复制使用上述完整代码,如有问题欢迎交流讨论!