深度定制 uni-combox:新增功能详解与实战指南

最近在 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 区域时清空输入并关闭下拉框

五、注意事项

  1. NVUE 限制:官方组件本身不支持 nvue,增强版同样受此限制

  2. 依赖 uni-icons :组件使用了 uni-icons,请确保项目中已安装该组件库

  3. 事件冒泡处理 :清除按钮和切换按钮均使用了 @click.stop 阻止事件冒泡

  4. 失焦行为:当前版本注释了延迟关闭逻辑,可根据实际需求取消注释

  5. Vue2/Vue3 兼容 :已通过条件编译处理 valuemodelValue 的差异,两个版本均可正常使用

六、总结

本增强版 uni-combox 在保持官方原有功能的基础上,新增了以下核心特性:

  • 边框控制 (border)

  • 清除按钮 (clearAble)

  • 下拉方向 (toward + 箭头指示器)

  • change 事件(区分输入和选择)

  • 候选列表滚动(超出滚动)

  • 视觉增强(阴影、悬停效果)

这些改进使得组件更适用于复杂的表单场景,尤其是位于页面边缘需要向上展开、需要快速清空内容、或需要区分用户交互行为的情况。

你可以在项目中直接复制使用上述完整代码,如有问题欢迎交流讨论!

相关推荐
谷雨不太卷1 小时前
进程的状态码
java·前端·算法
打小就很皮...1 小时前
基于 Python + LangChain + RAG 的知识检索系统实战
前端·langchain·embedding·rag
BJ-Giser1 小时前
Cesium 烟雾粒子特效
前端·可视化·cesium
空中海1 小时前
02 ArkTS 语言与工程规范
java·前端·spring
YJlio2 小时前
7.4.5 Windows 11 企业网络连接与网络重置实战:远程访问、本地策略与故障恢复
前端·chrome·windows·python·edge·机器人·django
Slow菜鸟2 小时前
Codex CLI 教程(五)| Skills 安装指南:面向 Java 全栈工程师打造个人 ECC(V1版)
大数据·前端·人工智能
Lee川2 小时前
打字机是怎么炼成的:Chat 流式输出深度解析
前端·后端·面试
前端若水2 小时前
过渡(transition)高级:贝塞尔曲线、硬件加速
前端·css·css3
Lee川2 小时前
Token 无感刷新与 Logout:前端安全会话管理实战
前端·后端·react.js