Vue實(shí)現(xiàn)typeahead組件功能(非??孔V)
前言
之前那個(gè)typeahead寫的太早,不滿足當(dāng)前的業(yè)務(wù)需求。
而且有些瑕疵,還有也不方便傳入數(shù)據(jù)和響應(yīng)數(shù)據(jù)..
于是就推倒了重來(lái),寫了個(gè)V2的版本
看圖,多了一些細(xì)節(jié)的考慮;精簡(jiǎn)了實(shí)現(xiàn)的邏輯代碼
效果圖

實(shí)現(xiàn)的功能
1: 鼠標(biāo)點(diǎn)擊下拉框之外的區(qū)域關(guān)閉下拉框
2: 支持鍵盤上下鍵選擇,支持鼠標(biāo)選擇
3: 支持列表過(guò)濾搜索
4: 支持外部傳入列表JSON格式的映射
5: 支持placeholder的傳入
6: 選中對(duì)象的響應(yīng)(.sync vue2.3的組件通訊的語(yǔ)法糖)
7: 箭頭icon的映射,感覺(jué)作用不大,移除了
用法
<select-search
style="max-width:195px"
placeholder="請(qǐng)選擇廣告主"
:asyncData.sync="adHostData"
:mapData="adHostDataList"
:mapDataFormat="{label:'userName',value:'userId'}">
</select-search>
- asyncData:響應(yīng)的數(shù)據(jù),也就是選中的..回來(lái)是一個(gè)對(duì)象
- mapData : 搜索的列表數(shù)據(jù),肯定是外部傳入了…
- mapData : 列表值映射
代碼
selectSearch.vue
<template>
<div class="select-search" v-if="typeaheadData" ref="selectSearch" @click.native="showHideMenu($event)">
<div class="select-header">
<input type="text" autocomplete="off" readonly :placeholder="placeholder" :value="placeholderValue" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
<i class="fzicon " :class="isExpand?'fz-ad-jiantou1':'fz-ad-jiantou'"></i>
</div>
<div class="select-body" v-if="isExpand && typeaheadData">
<input type="text" placeholder="關(guān)鍵字" v-model="searchVal" autocomplete="off" @keydown.esc="resetDefaultStatus" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
<transition name="el-fade-in-linear" mode="out-in">
<div class="typeahead-filter">
<transition-group tag="ul" name="el-fade-in-linear" v-show="typeaheadData.length>0">
<li v-for="(item,index) in typeaheadData" :key="index" :class="item.active ? 'active':''" @mouseenter="setActiveClass(index)" @mouseleave="setActiveClass(index)" @click="selectChild(index)">
<a href="javascript:;" rel="external nofollow" >
{{item[mapDataFormat.label]}}
</a>
</li>
</transition-group>
<p class="noFound" v-show="typeaheadData && typeaheadData.length === 0">未能查詢到,請(qǐng)重新輸入!</p>
</div>
</transition>
</div>
</div>
</template>
<script>
export default {
name: 'selectSearch',
data: function () {
return {
placeholderValue: '',// 給看到選擇內(nèi)容的
isExpand: false,
searchVal: '', // 搜索關(guān)鍵字
resultVal: '', // 保存搜索到的值
searchList: [], //保存過(guò)濾的結(jié)果集
currentIndex: -1, // 當(dāng)前默認(rèn)選中的index,
}
},
computed: {
mapFormatData () { // 外部有傳入格式的時(shí)候映射mapData
return this.mapData.map(item => {
item[this.mapDataFormat.value] = item[this.mapDataFormat.value];
return item;
});
},
typeaheadData () {
let temp = [];
if (this.searchVal && this.searchVal === '') {
return this.mapFormatData;
} else {
this.currentIndex = -1; // 重置特殊情況下的索引
this.mapFormatData.map(item => {
if (item[this.mapDataFormat.label].indexOf(this.searchVal.toLowerCase().trim()) !== -1) {
temp.push(item)
}
return item;
})
return temp;
}
}
},
props: {
placeholder: {
type: String,
default: '--請(qǐng)選擇--'
},
emptyText: {
type: String,
default: '暫無(wú)數(shù)據(jù)'
},
mapData: { // 外部傳入的列表數(shù)據(jù)
type: Array,
default: function () {
return []
}
},
mapDataFormat: { // 映射傳入數(shù)據(jù)的格式
type: Object,
default: function () {
return {
label: 'text',
value: 'value',
extraText: 'extraText'
}
}
},
asyncData: { // 實(shí)時(shí)響應(yīng)的值
type: [Object, String],
default: function () {
return {}
}
}
},
methods: {
showHideMenu (e) { // 點(diǎn)擊其他區(qū)域關(guān)閉下拉列表
if (e) {
if (this.$refs.selectSearch && this.$refs.selectSearch.contains(e.target)) {
this.isExpand = true;
} else {
this.isExpand = false;
}
}
},
resetDefaultStatus () { // 清除所有選中狀態(tài)
this.searchVal = '';
this.currentIndex = -1;
this.typeaheadData.map(item => {
this.$delete(item, 'active');
})
},
setActiveClass (index) { // 設(shè)置樣式活動(dòng)類
this.typeaheadData.map((item, innerIndex) => {
if (index === innerIndex) {
this.$set(item, 'active', true);
this.currentIndex = index; // 這句話是用來(lái)修正index,就是鍵盤上下鍵的索引,不然會(huì)跳位
} else {
this.$set(item, 'active', false)
}
})
},
selectChildWidthArrowDown () {
// 判斷index選中子項(xiàng)
if (this.currentIndex < this.typeaheadData.length) {
this.currentIndex++;
this.typeaheadData.map((item, index) => {
this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
})
}
},
selectChildWidthArrowUp () {
// 判斷index選中子項(xiàng)
if (this.currentIndex > 0) {
this.currentIndex--;
this.typeaheadData.map((item, index) => {
this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
})
}
},
selectChildWidthEnter () {
// 若是結(jié)果集只有一個(gè),則默認(rèn)選中
if (this.typeaheadData.length === 1) {
this.$emit('update:asyncData', this.typeaheadData[0]); // emit響應(yīng)的值
this.placeholderValue = this.typeaheadData[0][this.mapDataFormat.label];
} else {
// 若是搜索的內(nèi)容完全匹配到項(xiàng)內(nèi)的內(nèi)容,則默認(rèn)選中
this.typeaheadData.map(item => {
if (this.searchVal === item[this.mapDataFormat.label] || item.active === true) {
this.$emit('update:asyncData', item); // emit響應(yīng)的值
this.placeholderValue = item[this.mapDataFormat.label];
}
})
}
this.isExpand = false;
},
selectChild (index) {
// 鼠標(biāo)點(diǎn)擊選擇子項(xiàng)
this.typeaheadData.map((item, innerIndex) => {
if (index === innerIndex || item.active) {
this.placeholderValue = item[this.mapDataFormat.label];
this.$emit('update:asyncData', item); // emit響應(yīng)的值
}
});
this.isExpand = false;
},
},
mounted () {
window.addEventListener('click', this.showHideMenu);
},
beforeDestroy () {
window.removeEventListener('click', this.showHideMenu);
},
watch: {
'isExpand' (newValue) {
if (newValue === false) {
this.resetDefaultStatus();
}
}
}
}
</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;
}
.noFound {
text-align: center;
}
.select-search {
position: relative;
z-index: 1000;
a {
color: #333;
text-decoration: none;
padding: 5px;
}
ul {
list-style: none;
padding: 6px 0;
margin: 0;
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
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;
}
}
}
}
.select-header {
position: relative;
border-radius: 4px;
border: 1px solid #bfcbd9;
outline: 0;
padding: 0 8px;
>input {
border: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
outline: 0;
box-sizing: border-box;
color: #1f2d3d;
font-size: inherit;
height: 36px;
line-height: 1;
}
>i {
transition: all .3s linear;
display: inline-block;
position: absolute;
right: 3%;
top: 50%;
transform: translateY(-50%);
}
}
.select-body {
position: absolute;
border-radius: 2px;
background-color: #fff;
box-sizing: border-box;
margin: 5px 0;
padding: 8px;
width: 100%;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
>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;
&:focus {
outline: 0;
border-color: #20a0ff;
}
}
}
}
</style>
總結(jié)
以上所述是小編給大家介紹的Vue實(shí)現(xiàn)typeahead組件功能(非??孔V),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Vue+ElementUI容器無(wú)法鋪滿網(wǎng)頁(yè)的問(wèn)題解決
這篇文章主要介紹了Vue+ElementUI容器無(wú)法鋪滿網(wǎng)頁(yè)的問(wèn)題解決,文章通過(guò)圖文結(jié)合的方式給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-08-08
基于vue實(shí)現(xiàn)swipe輪播組件實(shí)例代碼
本篇文章主要介紹了基于vue實(shí)現(xiàn)swipe輪播組件實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Vite的常見(jiàn)配置選項(xiàng)詳細(xì)說(shuō)明
相信大部分兄弟都體驗(yàn)過(guò)Vite了,都知道它很快,在學(xué)習(xí)的時(shí)候,官網(wǎng)上的各種配置也是看的眼花繚亂,不知道哪些是必要掌握的,下面這篇文章主要給大家介紹了關(guān)于Vite常見(jiàn)配置選項(xiàng)的相關(guān)資料,需要的朋友可以參考下2024-09-09
使用Vue純前端實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼并實(shí)現(xiàn)倒計(jì)時(shí)
在實(shí)際的應(yīng)用開(kāi)發(fā)中,涉及用戶登錄驗(yàn)證、密碼重置等場(chǎng)景時(shí),通常需要前端實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的功能,以提升用戶體驗(yàn)和安全性,以下是一個(gè)簡(jiǎn)單的前端實(shí)現(xiàn),演示了如何在用戶點(diǎn)擊發(fā)送驗(yàn)證碼按鈕時(shí)觸發(fā)短信驗(yàn)證碼的發(fā)送,并開(kāi)始一個(gè)倒計(jì)時(shí)2024-04-04
vue3+elementplus前端生成圖片驗(yàn)證碼完整代碼舉例
在開(kāi)發(fā)過(guò)程中有時(shí)候需要使用圖片驗(yàn)證碼進(jìn)行增加安全強(qiáng)度,在點(diǎn)擊圖片時(shí)更新新的圖片驗(yàn)證碼,記錄此功能,以便后期使用,這篇文章主要給大家介紹了關(guān)于vue3+elementplus前端生成圖片驗(yàn)證碼的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue通過(guò)vue-lazyload實(shí)現(xiàn)圖片懶加載的代碼詳解
這篇文章主要給大家介紹了vue通過(guò)vue-lazyload實(shí)現(xiàn)圖片懶加載,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02

