template层
<template>
<div class="select-options" v-click-outside="hideList">
<input class="input-screen" v-if="searchable" type="text" ref="currentInput" :placeholder="placeholder" v-model="currentVal" :style="inputStyle" @focus="inputFocus" @input="changeOptionDatas">
<div class="input-screen div-screen" :class="{'placeholder-color': (placeholder && !currentVal)}" v-else :style="inputStyle" ref="currentInput" @click="inputFocus">
{{ (placeholder && !currentVal) ? placeholder : currentVal }}
</div>
<i class="icon-caret-down"></i>
<ul class="options-wrap" :class="{show: showOptions}" ref="currentOptions">
<template v-if="originalOptions && originalOptions.length">
<li class="options-list" v-for="(item, i) in originalOptions" :class="{active: itemKey ? item[itemKey] == currentVal : item == currentVal}" :key="i" @click="selectOptions(item)">
{{ itemKey ? item[itemKey] : item }}
</li>
</template>
<li class="options-list" v-else>
暂无数据
</li>
</ul>
</div>
</template>
script层
这里 props 有几个参数需要注意:
1、value:直接在组件身上v-model可实现双向绑定
2、placeholder:提示词
3、options:数据
4、itemKey:是否可取数据的某个字段
5、inputStyle:input输入框的样式
6、searchable:可输入筛选属性
注意:popup 需要去找之前到文章,名字为 显示弹出框工具函数
<script>
import { popup } from "@/utils/index"
export default {
data() {
return {
currentVal: '',
showOptions: false,
originalOptions: [],
antiShakeTimer: null, // 防抖延迟计时器
}
},
props: {
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
options: {
type: Array,
default: _ => []
},
itemKey: {
type: String,
default: ''
},
inputStyle: {
type: Object,
default: _ => { }
},
// 可搜索
searchable: {
type: Boolean,
default: false
}
},
created() {
this.currentVal = this.value;
this.originalOptions = this.options;
},
methods: {
selectOptions(item) {
let text = (this.itemKey ? item[this.itemKey] : item);
this.$emit('input', text);
this.$emit('on-change', text);
this.showOptions = false;
},
inputFocus() {
if (!this.showOptions) {
let input_dom = this.$refs.currentInput;
let options_dom = this.$refs.currentOptions;
popup(this, input_dom, options_dom, '', true);
this.showOptions = true;
this.$emit('on-focus', '');
} else {
this.showOptions = false;
}
},
hideList() {
this.showOptions = false;
},
changeOptionDatas() {
this.antiShakeTimer && clearTimeout(this.antiShakeTimer);
this.antiShakeTimer = setTimeout(_ => {
this.originalOptions = this.options.filter(v => {
console.log(v[this.itemKey].indexOf(this.currentVal))
return (this.itemKey ? v[this.itemKey].indexOf(this.currentVal) != -1 : v.indexOf(this.currentVal) != -1)
})
this.$emit('input', this.currentVal);
}, 300)
}
},
watch: {
value(newVal, oldVal) {
this.currentVal = newVal
},
options(newVal, oldVal) {
this.originalOptions = newVal
},
}
}
</script>
样式层
我这里使用的是less
<style lang="less" scoped>
.select-options {
width: 100%;
height: 100%;
position: relative;
text-align: center;
.icon-caret-down {
color: #747474;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
.input-screen {
border: 0;
width: 100%;
padding-right: 14px;
&.div-screen {
cursor: pointer;
}
&.placeholder-color {
color: rgb(197, 200, 206);
}
}
.options-wrap {
z-index: 99;
position: absolute;
width: 100%;
max-height: 30vh;
overflow: auto;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
border-radius: 4px;
visibility: hidden;
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(.8);
transition: opacity .3s, transform .3s, visibility .3s;
&.show {
visibility: visible;
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
.options-list {
color: #333;
border-bottom: 1px solid #E3E3E3;
background: #fff;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #f3f3f3;
}
&.active {
background: #337DFF;
color: #fff;
}
}
}
}</style>