uniapp下拉选择组件

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高度rpxitemHeight: {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.lengthlet signleH = len < this.max ? this.itemHeight * len : this.itemHeight * this.maxif (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 = truethis.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 = resultif(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.mzph.cn/web/10479.shtml

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

相关文章

前端开发工程师——ajax

express框架 终端输入 npm init --yes npm i express 请求报文/响应报文 // 1.引入express const express require(express);// 2.创建应用对象 const app express();// 3.创建路由规则 // request:是对请求报文的封装 // response&#xff1a;是对响应报文的封装 app.get(…

【御控物联】Java JSON结构转换、JSON协议转换、JSON属性互换(15):对象To数组——转换映射方式

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

Vue3自定义封装音频播放组件(带拖拽进度条)

Vue3自定义封装音频播放组件&#xff08;带拖拽进度条&#xff09; 描述 该款自定义组件可作为音频、视频播放的进度条&#xff0c;用于控制音频、视频的播放进度、暂停开始、拖拽进度条拓展性极高。 实现效果 具体效果可以根据自定义内容进行位置调整 项目需求 有播放暂停…

python实现pip一键切换国内镜像源脚本分享

本文主要分享一个自己写的pip一键切换国内镜像源python脚本 import subprocess# pip 国内镜像源加速 source_urls [{"name": "默认镜像源", "url": ""},{"name": "清华大学镜像源(推荐使用)", "url": …

【MATLAB源码-第206期】基于matlab的差分进化算法(DE)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 差分进化算法&#xff08;Differential Evolution, DE&#xff09;是一种有效的实数编码的进化算法&#xff0c;主要用于解决实值函数的全局优化问题。本文将详细介绍差分进化算法的背景、原理、操作步骤、参数选择以及实际应…

返回分类信息(带层级)

文章目录 1.前端展示分类管理信息1.目前项目架构2.启动前后端项目1.启动mysql容器2.启动后端 renren-fast3.启动前端1.界面2.用户名密码都是admin 3.创建分类管理菜单1.菜单管理 -> 新增 -> 新增目录2.刷新3.能够新增菜单的原因是前端脚手架与renren-fast后端脚手架通信&…

全面理解BDD(行为驱动开发):转变思维方式,提升软件质量

在传统的软件开发流程中&#xff0c;开发人员和测试人员的工作通常是相互独立的。开发人员负责编写代码&#xff0c;测试人员负责找出代码中的问题。然而&#xff0c;这种方法可能导致沟通不足&#xff0c;而且会浪费时间和资源。为了解决这些问题&#xff0c;出现了一种新的开…

Mask2former代码详解

1.整体流程 Mask2former流程如图所示&#xff0c;对于输入图片&#xff0c;首先经过Resnet等骨干网络获得多层级特征&#xff0c;对于获得的多层级特征&#xff0c;一个方向经过pixel decoder(基于DetrTransformerEncoderLayer)得到per-pixel embedding,另外一个方向经过transf…

matlab的imclose()详解

J imclose(I,SE) J imclose(I,nhood) 说明 J imclose(I,SE) 使用结构元素 SE 对灰度或二值图像 I 执行形态学闭运算。形态学闭运算是先膨胀后腐蚀&#xff0c;这两种运算使用相同的结构元素。 J imclose(I,nhood) 对图像 I 执行闭运算&#xff0c;其中 nhood 是由指定结…

mac监听 linux服务器性能可视化(Grafana+Promethus+Node_exporter)

Grafana和promethus(普罗米修斯)的安装和使用 监控系统的Prometheus类似于一个注册中心&#xff0c;我们可以只需要配置一个Prometheus,而在其他服务器&#xff0c;只需要安装node_exporter,它们的数据流转就是通过exporter采集数据信息&#xff0c;然后告诉prometheus它的位置…

分布式链路追踪 Zipkin+Sleuth(8)

项目的源码地址 Spring Cloud Alibaba 工程搭建&#xff08;1&#xff09; Spring Cloud Alibaba 工程搭建连接数据库&#xff08;2&#xff09; Spring Cloud Alibaba 集成 nacos 以及整合 Ribbon 与 Feign 实现负载调用&#xff08;3&#xff09; Spring Cloud Alibaba Ribbo…

BUUCTF[PWN]

BUUCTF[PWN] 题目&#xff1a;warmup_csaw_2016 地址&#xff1a;warmup_csaw_2016ida打开&#xff0c;进main函数&#xff1a;gets函数的栈溢出&#xff1a;给出了sub_40060D函数的地址直接&#xff0c;溢出到sub_40060D的地址即可&#xff1a; from pwn import *p remote…

[Cmake Qt]找不到文件ui_xx.h的问题?有关Qt工程的问题,看这篇文章就行了。

前言 最近在开发一个组件&#xff0c;但是这个东西是以dll的形式发布的界面库&#xff0c;所以在开发的时候就需要上层调用。 如果你是很懂CMake的话&#xff0c;ui_xx.h的文件目录在 ${CMAKE_CURRENT_BINARY_DIR} 下 然后除了有关这个ui_xx.h&#xff0c;还有一些别的可以简…

Verlog-流水灯-FPGA

Verlog-流水灯-FPGA 引言&#xff1a; ​ 随着电子技术的飞速发展&#xff0c;现场可编程门阵列&#xff08;FPGA&#xff09;已成为电子设计自动化&#xff08;EDA&#xff09;领域中不可或缺的组件。FPGA以其高度的灵活性和可定制性&#xff0c;广泛应用于通信、图像处理、工…

go-zero整合asynq实现分布式定时任务

本教程基于go-zero微服务入门教程&#xff0c;项目工程结构同上一个教程。 go-zero微服务入门教程&#xff08;点击进入&#xff09; 本教程主要实现go-zero整合asynq实现分布式定时任务。 本文源码&#xff1a;https://gitee.com/songfayuan/go-zero-demo &#xff08;教程源…

外卖点餐单店+多店自由切换小程序源码系统全功能版 带完整的安装代码包以及搭建部署教程

近年来&#xff0c;外卖市场持续火爆&#xff0c;但许多餐饮商家在接入外卖平台时面临着诸多困扰。高昂的平台费用、复杂的操作流程以及数据安全隐患等问题&#xff0c;让商家们倍感压力。为了解决这些问题&#xff0c;小编给大家分享一款集单店与多店管理于一体的外卖点餐系统…

ACM实训冲刺第四天

【碎碎念】最近的任务有点繁重&#xff0c;所以考虑到实际情况&#xff0c;视频学习决定放置一段时间&#xff0c;重点是学校的实训练习题&#xff0c;对于我而言&#xff0c;目标不是优秀/良好&#xff0c;综合考虑我的实际情况&#xff0c;保佑我及格、顺利通过就可&#xff…

通过自建镜像方式搭建RabbitMQ集群

通过自建镜像方式搭建RabbitMQ集群 1. 应用准备1.1 应用目录结构1.2 配置文件1.2.1 .erlang.cookie1.2.2 hosts1.2.3 rabbitmq.conf1.2.4 rabbitmq-env.conf 2. 编写DockerFile2.1 将所有本地文件拷贝到工作目录2.2 拷贝文件到源目录&增加执行权限2.3 安装Erlang & rab…

Leedcode题目:移除链表元素

题目&#xff1a; 这个题目就是要我们将我们的链表中的值是val的节点删除。 我们题目提供的接口是 传入了指向一个链表的第一个节点的指针&#xff0c;和我们要删除的元素的值val&#xff0c;不只要删除第一个&#xff0c; 思路 我们这里可以创建一个新的链表&#xff0c;…

【C++】学习笔记——模板进阶

文章目录 十一、模板进阶1. 非类型模板参数2. 按需实例化3. 模板的特化类模板的特化 4. 模板的分离编译 未完待续 十一、模板进阶 1. 非类型模板参数 模板参数分为类型形参和非类型形参 。类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的…