目录
背景
实现思路
代码实现
配置项
使用
尾巴
背景
最近遇到一个这样的需求,在输入框中输入关键字,通过接口查询到结果之后,以下拉框列表形式展现供用户选择。查询了下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:字体大小。
使用
页面引入down-select.vue,然后参考第二节代码实现中的demo。
尾巴
今天的文章就到这里了,希望能给大家帮助,如果喜欢我的文章,欢迎给我点赞,评论,关注,谢谢大家!