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。
尾巴
今天的文章就到這里了,希望能給大家幫助,如果喜歡我的文章,歡迎給我點贊,評論,關注,謝謝大家!