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。
尾巴
今天的文章就到这里了,希望能给大家帮助,如果喜欢我的文章,欢迎给我点赞,评论,关注,谢谢大家!
相关文章:

uniapp下拉选择组件
uniapp下拉选择组件 背景实现思路代码实现配置项使用尾巴 背景 最近遇到一个这样的需求,在输入框中输入关键字,通过接口查询到结果之后,以下拉框列表形式展现供用户选择。查询了下uni-app官网和项目中使用的uv-ui库,没找到符合条…...

高斯数据库创建函数的语法
CREATE FUNCTION 语法格式 •兼容PostgreSQL风格的创建自定义函数语法。 CREATE [ OR REPLACE ] FUNCTION function_name ( [ { argname [ argmode ] argtype [ { DEFAULT | : | } expression ]} [, …] ] ) [ RETURNS rettype [ DETERMINISTIC ] | RETURNS TABLE ( { column_…...

【.NET Core】你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟
你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟 文章目录 你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟一、概述二、CallerMemberNameAttribute类三、CallerFilePathAttribute 类四、CallerLineNumberAttribute 类…...

ubuntu删除opencv
要完全删除OpenCV 3.4.5版本,你可以按照以下步骤进行操作: 卸载OpenCV库: 首先,你需要卸载OpenCV 3.4.5版本。可以使用以下命令卸载OpenCV库: sudo apt-get purge libopencv*这将删除OpenCV库及其相关文件。 删除O…...

K8s源码分析(二)-K8s调度队列介绍
本文首发在个人博客上,欢迎来踩! 本次分析参考的K8s版本是 文章目录 调度队列简介调度队列源代码分析队列初始化QueuedPodInfo元素介绍ActiveQ源代码介绍UnschedulableQ源代码介绍**BackoffQ**源代码介绍队列弹出待调度的Pod队列增加新的待调度的Podpod调…...

OpenGL ES 面试高频知识点(二)
说说纹理常用的采样方式? 最邻近点采样(GL_NEAREST)和双线性采样(GL_LINEAR)。 GL_NEAREST 采样是 OpenGL 默认的纹理采样方式,OpenGL 会选择中心点最接近纹理坐标的那个像素,纹理放大的时候会有锯齿感或者颗粒感。 **GL_LINEAR 采样会基于纹理坐标附近的纹理像素,计…...

2024第十六届“中国电机工程学会杯”数学建模A题B题思路分析
文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间:2024…...

面向对象的三大特性:封装、继承、多态
一、封装 封装是面向对象的核心思想。是以类为载体,将对象的属性和行为封装起来,对外隐藏其实现细节。 封装保证了类内部数据结构的完整性,使得外部(使用该类的用户)不能轻易地直接操作此数据结构,只能执…...

目标检测YOLO实战应用案例100讲-基于深度学习的交通场景多尺度目标检测算法研究与应用(中)
目录 3.4 实验结果与分析 深度融合注意力跨尺度复合空洞残差交通目标检测算法...

前端GET请求下载后端返回数据流文件,并且处理window.open方法跳转白屏方法
平时常用导出都是用window.open方法 点击跳转连接:使用 window.open 下载 const downError 地址?&参数${参数|| }; const downError Url/xxx/xxx?&orgId${orgId || };window.open(downError, "_self");//调用window.open方法导出 而使用…...

SD321放大器3V输入电流电压保护二极管25C电源电流
Sd 321运算放大器可以在单电源或双电源电压下工作, 可以使用最坏情况下的非反相单位增益连接来适应。如 具有真微分输入,并且保持在线性模式,输入共模电压 果放大器必须驱动较大的负载电容,则应使用较大的闭 为0。Vpc-这种放大器可…...

geoserver SQL注入、Think PHP5 SQL注入、spring命令注入
文章目录 一、geoserver SQL注入CVE-2023-25157二、Think PHP5 SQL注入三、Spring Cloud Function SpEL表达式命令注入(CVE-2022-22963) 一、geoserver SQL注入CVE-2023-25157 介绍:GeoServer是一个开源的地理信息系统(GIS&#…...

scrapy的入门
今天我们先学习一下scrapy的入门,Scrapy是一个快速的高层次的网页爬取和网页抓取框架,用于爬取网站并从页面中提取结构化的数据。 1. scrapy的概念和流程 1.1 scrapy的概念 我们先来了解一下scrapy的概念,什么是scrapy: Scrapy是一个Python编写的开源网络爬虫框架…...

大数据Scala教程从入门到精通第七篇:Scala在IDEA中编写Hello World
一:Scala在IDEA中编写Hello World 想让我们的idea支持scala的编写,需要安装一个插件。...

设计模式之数据访问对象模式
在Java编程的浩瀚星海中,有一个模式低调却强大,它像是一位默默无闻的超级英雄,支撑起无数应用的数据脊梁——那就是数据访问对象(DAO, Data Access Object)模式!想象一下,如果你能像操纵魔法一样…...

Spring aop切面编程
Spring aop切面编程 如何使用利用AuditAction创建切入点 如何使用 Aspect // 1. 创建一个类,用Aspect注解标记它,表明这是一个切面类。 Component public class LoggingAspect {// 2. 定义切点:在通知方法上,使用切点表达式来指定…...

如何更好地使用Kafka? - 事先预防篇
要确保Kafka在使用过程中的稳定性,需要从kafka在业务中的使用周期进行依次保障。主要可以分为:事先预防(通过规范的使用、开发,预防问题产生)、运行时监控(保障集群稳定,出问题能及时发现&#…...

如何解决 IPA 打包过程中的 “Invalid Bundle Structure“ 错误
哈喽,大家好呀,淼淼又来和大家见面啦,咱们行业内的应该都知道,在开发 iOS 应用时,将应用打包成 IPA 文件是常见的步骤之一。最近很多小伙伴们说在打包过程中,有时会遇到 "Invalid Bundle Structure&qu…...

Vuex:Vue.js 的状态管理库
一、Vuex 简介 Vuex 是 Vue.js 的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 的出现解决了组件间共享状态的问题,使得状态管理变得简单、可预测和可维护。 二、Vuex 核心概…...

【简单介绍下Sass】
🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…...

IM 是什么?
在当今数字化的时代,即时通讯(IM)已经渗透到人们的日常生活和企业的工作环境中。IM技术的快速i发展为人们提供了一种高效、便捷的沟通方式,不仅推动了社会的信息化进程,也提升了企业的协同效率和竞争力。 作为企业级I…...

俄罗斯方块的代码实现
文章目录 首先是头文件的引入部分接下来是一些预处理指令接下来定义了两个结构体:接下来是全局变量g_hConsoleOutput,用于存储控制台输出句柄。之后是一系列函数的声明最后是main函数源码 首先是头文件的引入部分 包括stdio.h、string.h、stdlib.h、tim…...

出海企业哪种组网方案更省事?
对于出海企业而言,建立跨地区的数据传输和协同工作至关重要,以提升运营效率。因此,网络构建变得迫在眉睫。通过构建企业组网,企业能够加强与海外分支、客户和合作伙伴之间的联系,加速海外业务的发展。 然而,…...

triton编译学习
一 流程 Triton-MLIR: 从DSL到PTX - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/671434808Superjomns blog | OpenAI/Triton MLIR 迁移工作简介https://superjom...

源码知识付费系统,在线教学平台需要优化什么?
在线教育关于广大的关注者而言属于快捷度非常高的传达途径,尤其是白日没有过多时间的上班族或学习繁忙的学生,均能够通过可靠的在线教育完结自己的目的。如此巨大的市场潜力使得以在线教育为主的公司数量呈现出直线上升的趋势,很多的在线教育…...

后端常用技能:解决java项目前后端传输数据中文出现乱码、问号问题
0. 问题背景 最近做一个解析数据的小工具,本地运行时都正常,发布到服务器上后在导出文件数据时发现中文全部变成了问号,特此记录下问题解决的思路和过程 1. 环境 java 1.8 springboot 2.6.13 额外引入了fastjson,commons-csv等…...

SpringBoot中使用MongoDB
目录 搭建实体类 基本的增删改查操作 分页查询 使用MongoTemplate实现复杂的功能 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> 在ap…...

【TS】入门
创建项目 vscode自动编译ts 生成配置文件 tsc --init 然后发现终端也改变了:...

Apache ECharts
Apache ECharts介绍: Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 官网地址:https://echarts.apache.org/zh/index.html Apache ECh…...

超详细的胎教级Stable Diffusion使用教程(四)
这套课程分为五节课,会系统性的介绍sd的全部功能和实操案例,让你打下坚实牢靠的基础 一、为什么要学Stable Diffusion,它究竟有多强大? 二、三分钟教你装好Stable Diffusion 三、小白快速上手Stable Diffusion 四、Stable dif…...