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>