您的位置:首页 > 产品设计 > UI/UE

Vue折腾记 - (3)写一个不大靠谱的typeahead组件

2017-07-20 15:17 666 查看

前言

typeahead
在网站中的应用很多..今天跟着我来写一个不大靠谱的
typeahead
;

你能学到什么?

自定义事件

遍历的思想

功能细节的考虑

一切都挺不靠谱的…可完善的地方很多.废话不多说,看效果图

更新

2017-07-21: 完善逻辑及美化样式,所以效果图和代码都有所变动

效果图



有哪些功能点?

粗糙的模糊搜索 - 借助
indexOf


ESC
blur
事件清除输入框,没有找到匹配的情况下

Enter
默认在找到只剩下一个情况下选中

方向盘的上下(已经阻止光标的移动)选中子项,回车选中

鼠标点击选择子项

搜索框清空情况下默认不触发自定义事件值的返回

鼠标移动+键盘方向键移动位置的同步

placeholder
及遍历数据
data
支持外部传入,也就是绑定
props
;前者字符串,后者数组对象

代码

typeahead.vue

<template>
<div class="typeahead">
<div class="typeahead-header">
<input type="text" v-model="searchVal" @input="filterList(searchVal)" ref="input" :placeholder="placeholder" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter" @blur="ifNotFoundClear" @keydown.esc="ifNotFoundClear" autocomplete="off">
</div>
<transition name="el-fade-in-linear" mode="out-in">
<div class="typeahead-content" v-show="searchVal && searchVal.length">
<transition-group tag="ul" name="el-fade-in-linear" v-show="searchList && searchList.length > 0  && isExpand ">
<li v-for="(item,index) in searchList" :key="index" :class="item.active ? 'active':''" @click="selectChild(index)" @mouseenter="setActiveClass(index)" @mouseleave="setActiveClass(index)">
<a href="javascript:;">
{{item.text}}
</a>
</li>
</transition-group>
<p class="noFound" v-show="searchList && searchList.length === 0">未能查询到,请重新输入!</p>
</div>
</transition>
</div>
</template>

<script>
export default {
name: 'typeahead',
data: function () {
return {
searchVal: '', // 搜索关键字
resultVal: '', // 保存搜索到的值
searchList: [], //保存过滤的结果集
currentIndex: -1, // 当前默认选中的index,
isExpand: false
}
},
computed: {
typeaheadData () {
return this.mapData;
}
},
props: {
placeholder: {
type: String,
default: '请输入您要查询的关键字'
},
mapData: {
type: Array,
default: function () {
return [
{
text: 'wofsdf',
value: 0
},
{
text: '我是技术渣1',
value: '1'
},
{
text: '我是技术渣2',
value: '2'
},
{
text: '我是天坑',
value: '2'
},
{
text: '我是天坑,分身乏术',
value: '3'
},
{
text: '我是天坑2,分身乏术',
value: '3'
},
{
text: '我是天坑3,分身乏术',
value: '3'
}
]
}
}
},
methods: {
filterList (searchVal) { // 过滤数组
if (this.searchVal === '') {
this.ifNotFoundClear();
} else {
this.searchList = []; // 清空列表
let tempArr = []; // 一个临时数组
this.currentIndex = -1; // 重置index
this.typeaheadData && this.typeaheadData.forEach(item => {
if (item.text.indexOf(searchVal.trim()) !== -1) {
tempArr.push(item);
}
});
this.searchList = tempArr; // 为什么要一个临时数组,不然每次push都会触发视图更新..数据量一大....
this.isExpand = true;
}
},
ifNotFoundClear () { // 若是结果集长度为0就证明没有找到...赋值为空
if (this.searchList.length === 0) {
this.searchVal = '';
this.isExpand = false;
}
this.searchList.forEach(item => {
item.active = false;
})

},
selectChild (index) {
// 鼠标点击选择子项
this.searchList.forEach((item, innerIndex) => {
if (index === innerIndex || item.active) {
this.searchVal = item.text;
this.resultVal = item.value;
this.isExpand = false;
}
this.$set(item, 'active', false);
})
this.$emit('selectValue', { text: this.searchVal, value: this.resultVal });
},
selectChildWidthArrowDown () {
// 判断index选中子项
if (this.currentIndex < this.searchList.length) {
this.currentIndex++;
this.searchList.forEach((item, index) => {
this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
})
}
},
selectChildWidthArrowUp () {
// 判断index选中子项
if (this.currentIndex > 0) {
this.currentIndex--;
this.searchList.forEach((item, index) => {
this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
})
}
},
selectChildWidthEnter () {

// 若是结果集只有一个,则默认选中
if (this.searchList.length === 1) {
this.searchVal = this.searchList[0].text;
this.resultVal = this.searchList[0].value;
this.isExpand = false;
this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
} else {
// 若是搜索的内容完全匹配到项内的内容,则默认选中
this.searchList.forEach(item => {
if (this.searchVal === item.text || item.active === true) {
this.searchVal = item.text;
this.resultVal = item.value;
this.isExpand = false;
this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
}
})
}
},
setActiveClass (index) {
this.searchList.forEach((item, innerIndex) => {
if (index === innerIndex) {
this.$set(item, 'active', true);
this.currentIndex = index;  // 这句话是用来修正index,,就是键盘上下键的索引,不然会跳位
} else {
this.$set(item, 'active', false)
}
})
}
}
}
</script>

<style scoped lang="scss">
.el-fade-in-linear-enter-active,
.el-fade-in-linear-leave-active,
.fade-in-linear-enter-active,
.fade-in-linear-leave-active {
transition: opacity .2s linear;
}

.el-fade-in-enter,
.el-fade-in-leave-active,
.el-fade-in-linear-enter,
.el-fade-in-linear-leave,
.el-fade-in-linear-leave-active,
.fade-in-linear-enter,
.fade-in-linear-leave,
.fade-in-linear-leave-active {
opacity: 0;
}

.typeahead {
position: relative;
background-color: #fff;
a {
color: #333;
text-decoration: none;
padding: 5px;
}
ul {
list-style: none;
padding: 6px 0;
margin: 0;
overflow: visible;
li {
display: block;
width: 100%;
padding: 5px;
font-size: 14px;
padding: 8px 10px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #48576a;
height: 36px;
line-height: 1.5;
box-sizing: border-box;
cursor: pointer;
&.active {
background-color: #20a0ff;
a {
color: #fff;
}
}
}
}
input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #bfcbd9;
box-sizing: border-box;
color: #1f2d3d;
font-size: inherit;
height: 36px;
line-height: 1;
outline: 0;
padding: 3px 10px;
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
width: 100%;
display: inline-block;
}
.typeahead-header,
.typeahead-content {
width: 100%;
}
.typeahead-content {
position: absolute;
border-radius: 2px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
box-sizing: border-box;
margin: 5px 0;
}

.noFound {
text-align: center;
}
}
</style>


总结

自此,一个挺粗糙的模糊搜索组件就完成了;

希望此文对于正在阅读的您有所收获~~

有更好的方案或者实现方法的可以留言…谢谢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息