微信小程序、uniapp实现可下拉可输入框

PC端可以用的组件库很多,尤其是element ui几乎可以满足绝大部分使用场景,但是在微信小程序、uniapp编译的移动端中,能用的组件库却很少。所以封装了部分组件,以下是一个可手输也可以下拉选择的组件实现,效果图如下,附源码。

在原生微信小程序(微信开发者工具)中使用:

inputSelect.js文件代码:

Component({
  properties: {
    options: {
      type: Array,
      value: [],
    },
    label:{ 
      type: String,
      value:'name'
    },
    value:{
      type: String,
      value:'value'
    },
    placeholder:{
      type: String,
      value:'请选择'
    },
    defaultValue:{
      type: String,
      value:''
    }
  },
  data: {
    result:[], //转换后的候选项数据
    selection:"selection",//选中样式
    inputValue:"", //输入框的值
    isShow:false,
    index:null,// 选中的下标
    inputFocus: false,//输入框是否有焦点
  },
  observers:{ //监听数据变化
    'inputValue': function(value){
    },
    'options':function(value){
      this.setData({
        result: value
      })
    }
  },
  methods: {
    optionTap(e) {
      var that = this;
      var resuleObj ={flag: true}; //传递父组件的值.flag 表示是否是新增的 . true是新增,false不是新增
      this.data.index = e.target.dataset.index;
      this.setData({
        inputValue: that.data.result[that.data.index][that.data.label]
      });
      
      //选中的id
      var id = this.data.result[this.data.index].id;
      for(var i = 0; i < this.data.options.length; i++){
        if(this.data.options[i].id == id){
          this.data.options[i].selection = true;
          resuleObj.id = this.data.options[i].id;
          resuleObj.flag = false;
        }else{
          this.data.options[i].selection = false;
        }
      }

      this.setData({
        isShow: false,
        result: this.data.options
      });

      resuleObj.value = that.data.inputValue
      //调用父组件方法,并传参
      this.triggerEvent("change",  resuleObj);
    },

    openClose() {
      //如果是获取到焦点的状况下,就不关闭下拉选项
      if(this.data.inputFocus && this.data.isShow){
        return;
      }
      var that = this;
      this.setData({
        isShow: !that.data.isShow
      });
      if(!this.data.isShow){
        this.closeSetInputValue();
      }
      //只要操作当前项,就是获取到当前项的焦点
      this.triggerEvent("focus", {value: true});
    },
    // 此方法供父组件调用
    close() {
      this.setData({
        isShow: false
      })
      this.closeSetInputValue();
    },
    closeSetInputValue(){ //通过close和openClose方法隐藏选项时,设置inputValue的值
      var that = this;
      var inputValue = this.data.inputValue;
      //如果为空,直接返回
      if(!inputValue){
        return;
      }
      //返回的数据结构
      var resuleObj ={flag: true};
      for(var i = 0; i < this.data.options.length; i++){
        if(this.data.options[i][this.data.label] == inputValue){
          this.data.options[i].selection = true;
          resuleObj.id = this.data.options[i].id;
          resuleObj.flag = false;
        }else{
          this.data.options[i].selection = false;
        }
      }
      resuleObj.value = that.data.inputValue;
      //调用父组件方法,并传参
      this.triggerEvent("change",  resuleObj);
    },
    inputFocus(){
      this.setData({
        inputFocus: true
      })
    },
    inputBlur(){
      this.setData({
        inputFocus: false
      })
    },
    bindinput(e){
      var keyWord = e.detail.value;
      this.data.inputValue = e.detail.value;
      var tempresult = [];
      if(keyWord){
        var obj = {id: -1};
        obj[this.data.label] = keyWord;
        tempresult.push(obj);
      }
      for(var i = 0; i < this.data.options.length; i++){
        if(this.data.options[i][this.data.label] == keyWord){
          this.data.options[i].selection = true;
          tempresult.push(this.data.options[i]);
          tempresult.splice(0,1);
          continue;
        }
        if(this.data.options[i][this.data.label].indexOf(keyWord) != -1){
          this.data.options[i].selection = false;
          tempresult.push(this.data.options[i]);
        }
      }
      this.setData({
        result:tempresult
      });
    }
  },
  lifetimes: {
    attached() {
      // 属性名称转换, 如果不是 { id: '', name:'' } 格式,则转为 { id: '', name:'' } 格式
      let result = []
      if (this.data.key !== 'id' || this.data.text !== 'name' || this.data.text !== 'yes' ) {       
        for (let item of this.data.options) {
          let { [this.data.key]: id, [this.data.text]: name, [this.data.selection]: selection } = item
          result.push({ id, name, selection })
        }
      }
      this.setData({
        result: result
      })
    }
  }
})

inputSelect.json文件代码

{
  "component": true,
  "usingComponents": {}
}

inputSelect.wxml文件代码

<view class="select-box">
  <view class="{{isShow? 'select-current-open': 'select-current'}}" catchtap="openClose">
    <input  bindinput="bindinput" bindfocus="inputFocus" bindblur="inputBlur" class="current-name" placeholder="{{placeholder}}" value="{{defaultValue?defaultValue:inputValue}}"></input>
  </view>
  <view class="option-list" wx:if="{{isShow}}" catchtap="optionTap" style="overflow-y: auto;overflow-x: hidden;max-height: 200px;font-size: 14px;">
    <text  
      wx:for="{{result}}"
      wx:key="id"
      data-index="{{index}}"
      class="option {{item.selection ? 'selection':''}}"
      >{{item[label]}}</text>
  </view>
</view>

inputSelect.wxss文件代码

.select-box {
  position: relative;
  width: 100%;
  /* font-size: 14px; */
}

.select-current {
  position: relative;
  width: 100%;
  padding: 0 20px 0 6px;
  border: 1rpx solid #ddd;
  border-radius: 1px;
  box-sizing: border-box;
  line-height: 32px;

}

.select-current::after {
  position: absolute;
  display: block;
  right: 10px;
  top: 15px;
  content: '';
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-top: 5px solid #999;
}

.select-current-open {
  position: relative;
  width: 100%;
  padding: 0 20px 0 6px;
  border: 1rpx solid #ddd;
  border-radius: 1px;
  box-sizing: border-box;
  line-height: 32px;
}

.select-current-open::after {
  position: absolute;
  display: block;
  right: 10px;
  top: 10px;
  content: '';
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-bottom: 5px solid #999;
}

.selection{
  color:#00BBFF;
}

.current-name {
  display: block;
  width: 85%;
  height: 32px;
  word-wrap: normal;
  overflow: hidden;
}

.option-list {
  position: absolute;
  left: 0;
  width: 100%;
  border-radius: 6rpx;
  box-sizing: border-box;
  z-index: 99;
  border: 1px solid #ddd;
  border-top: none;
  background-color: #fff;
}

.option {
  display: block;
  width: 100%;
  line-height: 32px;
  height: 32px;
  border-bottom: 1px solid #eee;
  padding: 0 6px;
}

.option:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

在uniapp中使用:

input-select.vue文件代码:

<template>
    <view class="select-box">
        <view :class="isShow ? 'select-current-open' : 'select-current'" @tap.stop.prevent="openClose">
            <input @input="bindinput"  @blur="inputBlur" class="current-name" :placeholder="placeholder" v-model="inputValue" />
        </view>
        <view class="option-list" v-if="isShow" @tap.stop.prevent="optionTap" style="overflow-y: auto; overflow-x: hidden; max-height: 200px">
            <text :data-index="index" :class="'option ' + (item.selection ? 'selection' : '')" v-for="(item, index) in result" :key="item.id">{{ item[label] }}</text>
        </view>
    </view>
</template>

<script>
export default {
    data() {
        return {
            result: [],
            //转换后的候选项数据
            selection: 'selection',
            //选中样式
            inputValue: '',
            //输入框的值
            isShow: false,
            index: null,
            // 选中的下标
            inputFocus: false //输入框是否有焦点
        };
    },

    props: {
        options: {
            type: Array,
            default: () => []
        },
        label: {
            type: String,
            default: 'name'
        },
        value: {
            type: String,
            default: ''
        },
        placeholder: {
            type: String,
            default: '请选择'
        }
    },

    watch: {
        //监听数据变化
        inputValue: function (value) {},
		options: function (value) {
			this.result = value
			},
		value: {
		      handler(newValue, oldVal) {
		        this.inputValue = newValue
		      },
		      immediate: true
		    }
    },

    methods: {
        attached() {
            // 属性名称转换, 如果不是 { id: '', name:'' } 格式,则转为 { id: '', name:'' } 格式
            let result = [];
            if (this.key !== 'id' || this.text !== 'name' || this.text !== 'yes') {
                for (let item of this.options) {
                    let { [this.key]: id, [this.text]: name, [this.selection]: selection } = item;
                    result.push({
                        id,
                        name,
                        selection
                    });
                }
            }
			this.result = result
        },

        optionTap(e) {
            let that = this;
            let resuleObj = {
                flag: true
            }; //传递父组件的值.flag 表示是否是新增的 . true是新增,false不是新增
            this.index = e.target.dataset.index;
			this.inputValue = that.result[that.index][that.label]

            //选中的id
            var id = this.result[this.index].id;
            for (var i = 0; i < this.options.length; i++) {
                if (this.options[i].id == id) {
                    this.options[i].selection = true;
                    resuleObj.id = this.options[i].id;
                    resuleObj.flag = false;
                } else {
                    this.options[i].selection = false;
                }
            }
			this.isShow = false
			this.result = this.options
            resuleObj.value = that.inputValue;
            //调用父组件方法,并传参
            this.$emit('change', {
                detail: resuleObj
            });
        },

        openClose() {
            //如果是获取到焦点的状况下,就不关闭下拉选项
            if (this.inputFocusFun && this.isShow) {
                return;
            }
            var that = this;
			this.isShow = !that.isShow
            if (!this.isShow) {
                this.closeSetInputValue();
            }
            //只要操作当前项,就是获取到当前项的焦点
            this.$emit('focus', {
                detail: {
                    value: true
                }
            });
        },

        // 此方法供父组件调用
        close() {
			this.isShow = false
            this.closeSetInputValue();
        },

        closeSetInputValue() {
            //通过close和openClose方法隐藏选项时,设置inputValue的值
            let that = this;
            let inputValue = this.inputValue;
            //如果为空,直接返回
            if (!inputValue) {
                return;
            }
            //返回的数据结构
            let resuleObj = {
                flag: true
            };
            for (let i = 0; i < this.options.length; i++) {
                if (this.options[i][this.label] == inputValue) {
                    this.options[i].selection = true;
                    resuleObj.id = this.options[i].id;
                    resuleObj.flag = false;
                } else {
                    this.options[i].selection = false;
                }
            }
            resuleObj.value = that.inputValue;
            //调用父组件方法,并传参
            this.$emit('change', {
                detail: resuleObj
            });
        },

        inputFocusFun() {
			this.inputFocus = true
        },

        inputBlur() {
			this.inputFocus = false
        },

        bindinput(e) {
            var keyWord = e.detail.value;
            this.inputValue = e.detail.value;
            var tempresult = [];
            if (keyWord) {
                var obj = {
                    id: -1
                };
                obj[this.label] = keyWord;
                tempresult.push(obj);
            }
            for (var i = 0; i < this.options.length; i++) {
                if (this.options[i][this.label] == keyWord) {
                    this.options[i].selection = true;
                    tempresult.push(this.options[i]);
                    tempresult.splice(0, 1);
                    continue;
                }
                if (this.options[i][this.label].indexOf(keyWord) != -1) {
                    this.options[i].selection = false;
                    tempresult.push(this.options[i]);
                }
            }
			this.result = tempresult
        }
    },

    mounted() {
        // 处理小程序 attached 生命周期
        this.attached();
    },

    created: function () {}
};
</script>
<style>
.select-box {
    position: relative;
    width: 100%;
    font-size: 17px;
}

.select-current {
    position: relative;
    width: 100%;
    padding: 0 20px 0 6px;
    border: 1rpx solid #ddd;
    border-radius: 1px;
    box-sizing: border-box;
    line-height: 32px;
}

.select-current::after {
    position: absolute;
    display: block;
    right: 10px;
    top: 15px;
    content: '';
    width: 0;
    height: 0;
    border: 4px solid transparent;
    border-top: 5px solid #999;
}

.select-current-open {
    position: relative;
    width: 100%;
    padding: 0 20px 0 6px;
    border: 1rpx solid #ddd;
    border-radius: 1px;
    box-sizing: border-box;
    line-height: 32px;
}

.select-current-open::after {
    position: absolute;
    display: block;
    right: 10px;
    top: 10px;
    content: '';
    width: 0;
    height: 0;
    border: 4px solid transparent;
    border-bottom: 5px solid #999;
}

.selection {
    color: #00bbff;
}

.current-name {
    display: block;
    width: 85%;
    height: 32px;
    word-wrap: normal;
    overflow: hidden;
}

.option-list {
    position: absolute;
	font-size: 14px;
    left: 0;
    width: 100%;
    border-radius: 6rpx;
    box-sizing: border-box;
    z-index: 99;
    border: 1px solid #ddd;
    border-top: none;
    background-color: #fff;
}

.option {
    display: block;
    width: 100%;
    line-height: 32px;
    height: 32px;
    border-bottom: 1px solid #eee;
    padding: 0 6px;
}

.option:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值