uniapp下拉选择组件

news2024/12/30 1:12:34

uniapp下拉选择组件

  • 背景
  • 实现思路
  • 代码实现
  • 配置项
  • 使用
  • 尾巴

背景

最近遇到一个这样的需求,在输入框中输入关键字,通过接口查询到结果之后,以下拉框列表形式展现供用户选择。查询了下uni-app官网和项目中使用的uv-ui库,没找到符合条件的组件。唯一一个有点类似的就是uni官方下拉框组件,但是不支持input组件,所以我们自己来实现一个。

我们先上一张图镇楼,提供了多种模式和使用场景的情况
在这里插入图片描述

实现思路

那么实现这样一个组件要有哪些注意点了?我大概罗列了一下:
1、下拉框默认是不显示的,要知道是在哪个位置显示
2、初始显示的组件不能定死,可以是button,可以是view,或者是input,要灵活
3、提供丰富的自定义配置
要解决第一和第二个问题,我们的自定义组件需要使用插槽,插槽用来放我们的初始显示组件,来灵活适配各种组件。然后在插槽下面来通过绝对定位来显示下拉框组件,默认隐藏,然后暴露出显示函数给外部。

代码实现

接下来就是代码实现环节了,有了思路直接按思路撸代码就行。
在这里插入图片描述
知道你们不喜欢啰嗦BB,哈哈,直接上代码
(down-select.vue)组件代码:

<template>
	<view class='layout-column'>
		<view id="parent" style="width:fit-content;">
			<slot></slot>
		</view>
		<view
			:style="'width:'+slotW+';max-height: '+getListContentHei+'rpx;z-index: 9999;position: absolute;margin-top:'+slotH+';'+(isShow ? '' : 'display:none;')"
			:class="(dataList.length > 0 ? 'data-box-shadow ' : 'data-box ') + animate">
			<block v-if="dataList.length > 0">
				<view class="data-box-scroll"
					:style="'height:'+dataList.length*(itemHeight-1)+'rpx;max-height: '+max*(itemHeight-1)+'rpx;'">
					<text v-for="(item,index) in dataList" :key="item[identifier]"
						:class="'layout-row less-center list-item '+(item.enable === false ? '' : 'active')"
						:style="'color:'+(item.enable === false ? '#dedede' : (checkedIndex.indexOf(index) >= 0 ? itemSelectColor : itemColor))+';font-size:'+itemFontsize+'rpx;'"
						@click="handleListItem(index, item)">{{item[showKey]}}</text>
				</view>
				<view class="layout-row opera-btns less-center" v-if="mode == 'multiple'">
					<view class="opera-cancel layout-row center" @click='handelCancel'>取消</view>
					<view class="opera-sure layout-row center" @click='handelSure'>确定</view>
				</view>
			</block>
			<view v-else :style="'width:'+slotW+';'" class='status-text'>暂无数据</view>
		</view>
		<view class="mask" v-show="isShow" @click="handelCancel"></view>
	</view>
</template>

<script>
	export default {
		name: "down-select",
		props: {
			//要显示的字段
			showKey: {
				type: String,
				default: '',
			},
			mode: {
				type: String,
				default: 'single', //multiple
				// default: 'multiple'
			},
			dataList: {
				type: Array,
				default: []
			},
			//选中的列表,用作显示列表是展示已选中项
			checkedDataList: {
				type: Array,
				default: []
			},
			//最多展示几项后开始滑动
			max: {
				type: Number,
				default: 4
			},
			//数据项每个item高度rpx
			itemHeight: {
				type: Number,
				default: 80
			},
			//唯一标识符字段,用来比对选中项和维持v-for列表中的key,不填此项无选中效果
			identifier: {
				type: String,
				default: ''
			},
			itemSelectColor: {
				type: String,
				default: '#00aaff'
			},
			itemColor: {
				type: String,
				default: 'black'
			},
			itemFontsize: {
				type: Number,
				default: 30
			}
		},
		computed: {
			getListContentHei() {
				let len = this.dataList.length
				let signleH = len < this.max ? this.itemHeight * len : this.itemHeight * this.max
				if (this.mode == 'single') {
					return len <= 0 ? this.itemHeight : signleH
				} else {
					return len <= 0 ? this.itemHeight : (signleH + this.itemHeight)
				}
			}
		},
		watch: {
			dataList: {
				handler: function(newVal, oldVal) {
					if (this.checkedDataList.length >= 0 && this.identifier) {
						this.checkedIndex = []
						this.checkedDataList.forEach(ele => {
							let index = newVal.findIndex(ele1 => ele[this.identifier] === ele1[this
								.identifier])
							if (index >= 0) {
								this.checkedIndex.push(index)
							}
						})
					}
				},
				immediate: true, // 组件创建时立即触发
				deep: true // 对象内部属性变化时也触发
			},
			checkedDataList: {
				handler: function(newVal, oldVal) {
					if (newVal.length >= 0 && this.identifier) {
						this.checkedIndex = []
						newVal.forEach(ele => {
							let index = this.dataList.findIndex(ele1 => ele[this.identifier] === ele1[this
								.identifier])
							if (index >= 0) {
								this.checkedIndex.push(index)
							}
						})
					}
				},
				immediate: true, // 组件创建时立即触发
				deep: true // 对象内部属性变化时也触发
			}
		},
		mounted() {
			this.$nextTick(() => {
				uni.createSelectorQuery().in(this).select('#parent').boundingClientRect(res => {
					if (res.width) {
						this.slotW = `${res.width}px`
						this.slotH = `${res.height+5}px`
					}
				}).exec()
			})
		},
		data() {
			return {
				slotW: '0px',
				slotH: '0px',
				isShow: false,
				checkedIndex: [],
				animate: '',
				//传进来选中项,后又改成未选中并确认,多选模式生效
				checkedDels: []
			};
		},
		methods: {
			open() {
				if (this.checkedDataList.length >= 0 && this.identifier) {
					this.checkedIndex = []
					this.checkedDataList.forEach(ele => {
						let index = this.dataList.findIndex(ele1 => ele[this.identifier] === ele1[this
							.identifier])
						if (index >= 0) {
							this.checkedIndex.push(index)
						}
					})
				}
				this.isShow = true
				this.animate = 'show-animate'
			},
			close() {
				this.animate = 'hide-animate'
				this.checkedIndex = []
				this.checkedDels = []
				this.isShow = false
			},
			handleListItem(index, obj) {
				if(obj.enable === false){
					return
				}
				if (this.mode === 'single') {
					this.checkedIndex = []
					this.checkedIndex.push(index)
					this.handelSure()
				} else {
					let sindex = this.checkedIndex.indexOf(index)
					if (sindex >= 0) {
						if (this.identifier) {
							//判断未选中的项在传进来的已选项中是否存在
							let contain = this.checkedDataList.filter(ele => ele[this.identifier] === this.dataList[index][
								this.identifier
							])
							if (contain.length > 0) {
								//传进来的已选项中是否存在选择为未选中的内容
								let contain1 = this.checkedDels.filter(ele => ele[this.identifier] === contain[0][this
									.identifier
								])
								if (contain1.length <= 0) {
									this.checkedDels.push(contain[0])
								}
							}
						}
						this.checkedIndex.splice(sindex, 1);
					} else {
						if (this.identifier) {
							let contain2 = this.checkedDels.filter(ele => ele[this.identifier] === this.dataList[index][
								this.identifier
							])
							if (contain2.length > 0) {
								let tempIndex = this.checkedDels.findIndex(ele => ele[this.identifier] === this.dataList[
									index][this.identifier])
								if (tempIndex >= 0) {
									this.checkedDels.splice(tempIndex, 1)
								}
							}
						}
						this.checkedIndex.push(index)
					}
				}
			},
			handelCancel() {
				this.close()
				this.$emit('cancelDimss', '')
			},
			handelSure() {
				let results = []
				if (this.checkedIndex.length <= 0) {
					uni.showToast({
						title: '请选择至少一项',
						icon: 'none'
					});
					return
				}

				this.checkedIndex.forEach(ele => {
					if (this.dataList[ele]) {
						results.push(this.dataList[ele])
					}
				})
				//将本次选中结果清除
				this.checkedIndex = []
				this.$emit('resultBack', results, this.checkedDels)
				this.close()
			}
		}
	}
</script>

<style scoped>
	.active {}

	.active:active {
		opacity: 0.6;
	}

	.layout-row {
		display: flex;
		flex-direction: row;
	}

	.layout-column {
		display: flex;
		flex-direction: column;
	}

	/* 整体方向居中 */
	.center {
		align-items: center;
		justify-content: center;
	}

	/* 主轴方向居中 */
	.main-center {
		justify-content: center;
	}

	/* 侧轴方向居中 */
	.less-center {
		align-items: center;
	}

	.data-box-scroll {
		width: 100%;
		overflow-y: scroll;
	}

	.data-box-scroll::-webkit-scrollbar {
		display: none
	}

	.data-box {
		background: white;
		border-radius: 8rpx;
		box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
		z-index: 9999;
	}

	.data-box-shadow {
		background: white;
		border-radius: 8rpx;
		box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
		z-index: 9999;
	}

	.list-item {
		width: 100%;
		height: 80rpx;
		margin-left: 20rpx;
		margin-right: 20rpx;
		border-bottom: 1rpx solid #D8DFEC;
		text-align: right;
	}

	.opera-btns {
		width: 100%;
		height: 80rpx;
		justify-content: flex-end;
	}

	.opera-cancel {
		width: 100rpx;
		height: 50rpx;
		background-color: white;
		box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
		border-radius: 5rpx;
		font-size: 26rpx;
	}

	.opera-sure {
		width: 100rpx;
		height: 50rpx;
		background-color: #58a2e4;
		box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
		border-radius: 5rpx;
		font-size: 26rpx;
		color: white;
		margin-right: 30rpx;
		margin-left: 30rpx;
	}

	.status-text {
		text-align: center;
		font-size: 28rpx;
		font-weight: 600;
		color: #c2c2c2;
		padding-top: 20rpx;
		padding-bottom: 20rpx;
	}

	.mask {
		position: fixed;
		background: transparent;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		z-index: 1;
	}

	.show-animate {
		animation-name: open;
		animation-duration: 1s;
		animation-iteration-count: 1;
	}

	@keyframes open {
		0% {
			height: 0rpx;
		}

		100% {
			height: 100%;
		}
	}

	.hide-animate {
		animation-name: close;
		animation-duration: 1s;
		animation-iteration-count: 1;
		animation-fill-mode: forwards;
	}

	@keyframes close {
		0% {
			height: 100%;
		}

		100% {
			height: 0rpx;
		}
	}
</style>

(test.vue)测试页面代码:

<template>
	<view class="content">
		<downSelect ref='selectRef' showKey="name" mode='single' :dataList="list" @resultBack="result">
			<view class="select-btn" @click="handleClick">单选默认配置</view>
		</downSelect>
		<view>{{select.name}}</view>

		<downSelect ref='selectRef1' showKey="name" mode='single' itemColor='red' :dataList="list1" @resultBack="result1">
			<view class="select-btn1" @click="handleClick1">单选字体设置,是否可点击</view>
		</downSelect>
		<view>{{select1.name}}</view>

		<downSelect ref='selectRef2' showKey="name" mode='multiple' identifier="id" :dataList="list2"
			:max="5" :itemFontsize='20' :checkedDataList="select2" @resultBack="result2">
			<view class="select-btn2" @click="handleClick2">多选字体大小设置,超出设置项后滑动设置</view>
		</downSelect>
		<view v-for="item in select2">{{item.name}}</view>
		
		<downSelect ref='selectRef3' showKey="name" mode='single'>
			<view class="select-btn2" @click="handleClick3">空数据</view>
		</downSelect>
		
		<downSelect ref='selectRef4' showKey="name" mode='single' :dataList="list4" @resultBack="result4">
			<input class="select-btn1" placeholder="请输入" @input="handleInput"/>
		</downSelect>
		<view>{{select4.name}}</view>
	</view>

</template>

<script>
	import downSelect from "@/components/down-select.vue"
	export default {
		components: {
			downSelect
		},
		data() {
			return {
				select: {},
				select1: {},
				select2: [],
				select4: {},
				list: [{
					name: '选项一'
				}, {
					name: '选项二'
				}],
				list1: [{
					name: '选项一',
					enable: false
				}, {
					name: '选项二'
				}, {
					name: '选项三'
				}, {
					name: '选项四'
				}, {
					name: '选项五'
				}],
				list2: [{
					id: '0',
					name: '选项一'
				}, {
					id: '1',
					name: '选项二'
				}, {
					id: '2',
					name: '选项三'
				}, {
					id: '3',
					name: '选项四'
				}, {
					id: '4',
					name: '选项五'
				}, {
					id: '5',
					name: '选项六'
				}],
				list4: []
			}
		},
		onLoad() {
			
		},
		methods: {
			handleInput(e){
				this.$refs.selectRef4.open()
				//这里模拟接口访问获取数据
				setTimeout(()=> {
					this.list4 = [{
						name: '选项一'
					}, {
						name: '选项二'
					}]
				},2000)
			},
			result(result) {
				this.select = result[0]
			},
			result1(result){
				this.select1 = result[0]
			},
			result2(result, dels = []){
				// this.select2 = result
				if(this.select2.length <= 0){
				  this.select2.push(...result)
				}else {
				  result.forEach(ele => {
				    let contain = this.select2.filter(ele1 => ele.id === ele1.id)
				    if(contain.length <= 0){
				      this.select2.push(ele)
				    }
				  })
				}
				if(dels.length > 0){
				  dels.forEach(ele => {
				    let index = this.select2.findIndex(ele1 => ele1.id === ele.id)
				    if(index >= 0){
				      this.select2.splice(index, 1)
				    }
				  })
				}
			},
			result4(result){
				this.select4 = result[0]
			},
			handleClick(){
				this.$refs.selectRef.open()
			},
			handleClick1() {
				this.$refs.selectRef1.open()
			},
			handleClick2 () {
				this.$refs.selectRef2.open()
			},
			handleClick3 () {
				this.$refs.selectRef3.open()
			}
		},
	}
</script>

<style>
	.content {
		width: 690rpx;
		margin: 25rpx;
	}

	.select-btn {
		width: 690rpx;
		height: 60rpx;
		display: flex;
		flex-direction: row;
		align-items: center;
		justify-content: center;
		border: 1rpx solid #e5e5e5;
		border-radius: 15rpx;
	}

	.select-btn1 {
		width: 490rpx;
		height: 60rpx;
		display: flex;
		margin-top: 100rpx;
		flex-direction: row;
		align-items: center;
		justify-content: center;
		border: 1rpx solid #e5e5e5;
		border-radius: 15rpx;
	}

	.select-btn2 {
		width: 690rpx;
		height: 60rpx;
		display: flex;
		margin-top: 100rpx;
		flex-direction: row;
		align-items: center;
		justify-content: center;
		border: 1rpx solid #e5e5e5;
		border-radius: 15rpx;
	}
</style>

以上代码都可以直接拷贝使用,无其他关联项,vue2、vue3、小程序、h5、APP都可用。

配置项

1、showKey:传进来的数组单个对象在下拉框中要展示的值,比如传的dataList为[{name: ‘韩梅梅’}],那showKey传name就行。
2、mode:下拉框是单选还是多选模式,见镇楼效果图。
3、dataList:传入的数据列表,列表为对象,多选时对象需要指定唯一标识。
4、checkedDataList:多选选中的列表数据。
5、max:多条数据时最多显示几项后可以滑动。
6、identifier:唯一标识符,用作列表key和多选模式显示已选数据项时的标识。
7、itemSelectColor:多选模式选中后的字体颜色。
8、itemColor:字体颜色。
9、itemFontsize:字体大小。

以上配置项在代码层面没做严格限制,不同模式混用可能会有bug,如果发现可以留言。配置项不满足你需求,可以自行增删。

使用

页面引入down-select.vue,然后参考第二节代码实现中的demo。

尾巴

今天的文章就到这里了,希望能给大家帮助,如果喜欢我的文章,欢迎给我点赞,评论,关注,谢谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1663705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水表智能抄表系统是什么?

水表智能抄表系统是一种现代化水资源保护专用工具&#xff0c;它利用先进的物联网、云计算和大数据剖析&#xff0c;完成了智能抄表、实时监控系统、数据分析等作用&#xff0c;大大提高了水务管理的效率和精确性。 1.功能特点 1.1远程控制自动抄表 传统水表抄水表方法采用人…

Fcos源码训练编译问题

训练fcos代码时出现问题 ImportError: cannot import name ‘_C’ 原因是没有对代码进行编译 运行python setup.py develop --no-deps进行代码编译 编译过程中出现报错&#xff1a; fcos_core/csrc/cuda/ROIAlign_cuda.cu:5:10: fatal error: THC/THC.h: No such file or dire…

Linux学习笔记7---仿STM32自建寄存器库

为了开发方便&#xff0c;ST 官方为 STM32F103 编写了一个叫做 stm32f10x.h 的文件&#xff0c;在这个文件里面定义了 STM32F103 所有外设寄存器。而有些芯片是没有这种寄存器库的&#xff0c;在没有的情况下要学会自己建立一个寄存器库。NXP 官方并没有为 I.MX6UL 编写类似 st…

半小时搞懂STM32面经知识——GPIO

1.GPIO 1.1 什么是GPIO&#xff1f; 通用输入输出端口&#xff0c;用于与外设进行数字信号通信。 1.2 GPIO有几种工作模式&#xff1f;请描述它们各自的特点和用途 输入输出各有四种 输入&#xff1a;浮空&#xff0c;上拉&#xff0c;下拉&#xff0c;模拟 输出&#xff1…

N1077B keysight 是德 光/电时钟恢复设备,参数

Keysight N1077B是一款光/电时钟恢复设备&#xff0c;支持115 MBd至24 GBd的数据速率范围&#xff0c;适用于多模和单模光信号以及电信号。该设备能够处理PAM4和NRZ两种类型的数据信号&#xff0c;并提供符合标准的时钟恢复功能。 型 号&#xff1a;N1077B/A 名 称&#xff1a…

Python自动化测试面试题 —— Selenium篇!

Selenium中有几种等待 隐形等待/智能等待 dr.implicitly_wait() 显性等待 WebDriverWait 强制等待 time.sleep() Selenium中有哪些定位方式 8种 tag 三大基本属性 id/name/class_name 链接 link text/partial link text 高级 css selector/xpath 弹框怎么处理 4种弹…

论文精读-Transformer(Attention is All You Need)

文章目录 论文精读-Transformer&#xff08;Attention is All You Need)1.Transformer 整体结构2. Transformer 的输入2.1 单词 Embedding2.2 位置 Embedding 3. Self-Attention&#xff08;自注意力机制&#xff09;3.1 Self-Attention 结构3.2 Q, K, V 的计算3.3 Self-Attent…

Android NDK开发(一)生成指定平台的ndk及根据native接口生成jni接口

为了初步了解Android NDK开发&#xff0c;本文记录了Android Studio使用过程中的部分内容。 本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习&#xff0c;梳理总结后写下文章&#xff0c;对音视频相关内容感兴趣的读者&#xff0c;可以点击观看课程…

Android Studio(AS)使用别人的项目与gradle包并运行项目

一、问题描述 在进行AS开发时&#xff0c;我们可能会使用到别人的项目&#xff0c;但发现别人把项目发给我们后会发现gradle项目同步失败o(≧口≦)o&#xff0c;此时计有三&#xff1a; 1.横行霸道、豪取抢夺&#xff1a;直接空降到项目人那里&#xff0c;强他的电脑占为己有…

在 Navicat 17 创建一个数据字典

即将于 5 月 13 日发布的 Navicat 17&#xff08;英文版&#xff09;添加了许多令人兴奋的新功能。其中之一就是数据字典工具。它使用一系列 GUI 指导你完成创建专业质量文档的过程&#xff0c;该文档为跨多个服务器平台的数据库中的每个数据元素提供描述。在今天的博客中&…

微信小程序开发【Coffee Shopping】(1)

1.环境准备 微信开发者工具&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 前端常用网站集合&#xff1a;http://www.wwp666.cn/ 微信小程序开发文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/framework/quicksta…

Prompt|Kimi高阶技巧,99%的人都不知道

大家好&#xff0c;我是无界生长。 今天分享一条咒语&#xff0c;轻松让Kimi帮你生成流程图&#xff0c;学会了的话&#xff0c;点赞收藏起来吧&#xff01; 效果展示 我们演示一下让kimi帮忙绘制 关注微信公众号“无界生长”的流程图&#xff0c;最终效果图如下所示 效果还不…

基本QinQ

拓扑图 配置 开启LLDP功能&#xff0c;查看是否能通过QinQ隧道透传 sysname AR1 # lldp enable # interface GigabitEthernet0/0/0.10dot1q termination vid 10ip address 12.1.1.1 255.255.255.0 arp broadcast enable # sysname AR2 # lldp enable # interface GigabitE…

未来办公新方式--智能体与程序完美配合

Agent AI智能体的未来 工作中&#xff0c;有时候我们就像是在不停地踩着缝纫机&#xff0c;重复地做着那些单调乏味的任务&#xff0c;不仅耗时费力&#xff0c;还特别容易出错。可是&#xff0c;咱们现在可是生活在数字化时代啊&#xff01;这时候&#xff0c;Python编程语言…

SwinIR: Image Restoration Using Swin Transformer

ICCV2021 workshophttps://github.com/JingyunLiang/SwinIR 问题引入 将swim transformer使用到图像恢复任务当中&#xff0c;因为卷积存在不能建模长距离依赖以及使用相同的卷积核来恢复不同的图像区域&#xff1b;并不是首个将transformer引入图像恢复中的方法&#xff0c;…

python 常见错误 ModuleNotFoundError: No module named ‘requests‘

我们经常会遇到类似错误 ModuleNotFoundError: No module named ‘requests‘ ModuleNotFoundError: No module named bs4 现在记录一下解决方案。 File--》Settings--》Project--》Python interpreter》点击&#xff0b; 》输入requests--》选中requests--》点击右下角inst…

Linux虚拟主机如何设置错误页面

设置一个定制的404页面对网站有多种好处。首先&#xff0c;它能够提升用户体验&#xff0c;当用户访问错误的网址或不存在的页面时&#xff0c;定制的404页面能够友好地提醒用户并给予导航或提示。其次&#xff0c;404页面可以帮助留住访问者&#xff0c;让他们在意外情况下不至…

【系统架构师】-案例篇(六)MVC模式与XML

某商业银行欲开发一套个人银行系统&#xff0c;为用户提供常见的金融服务&#xff0c;包括转账、查询、存款变更和个人信息管理等功能。该软件除了业务需求外&#xff0c;还有一些特殊的表现层需求&#xff1a; A&#xff0e;根据用户级别的不同&#xff0c;界面和可用功能是不…

使用 Valgrind 检测内存泄漏

Valgrind 是一个编程工具&#xff0c;用于内存调试、内存泄漏检测以及性能分析。Valgrind 工具集中的 Memcheck 是用于检测内存管理和线程错误的主要工具。 参考&#xff1a;https://blog.csdn.net/weixin_44046545/article/details/138417524 1、安装 Valgrind sudo apt-ge…

手机在网状态多方面重要性

手机在网状态的重要性体现在多个方面&#xff0c;它是现代社会中人们保持联系、获取信息以及进行日常活动不可或缺的一部分。以下是一些关于手机在网状态重要性的详细解释&#xff1a; 通信联系&#xff1a; 手机是在现代社会中进行通信联系的主要工具。当手机处于在网状态时&…