前言
之前那個typeahead寫的太早,不滿足當前的業務需求
而且有些瑕疵,還有也不方便傳入資料和響應資料..
於是就推倒了重來,寫了個V2的版本
看圖,多了一些細節的考慮;
精簡了實現的邏輯程式碼
效果圖
實現的功能
- 滑鼠點選下拉框之外的區域關閉下拉框
- 支援鍵盤上下鍵選擇,支援滑鼠選擇
- 支援列表過濾搜尋
- 支援外部傳入列表JSON格式的對映
- 支援placeholder的傳入
- 選中物件的響應(
.sync
vue2.3的元件通訊的語法糖) - 箭頭icon的對映,感覺作用不大,移除了
你能學到什麼
見仁見智哈
用法
<
select-search style="max-width:195px" placeholder="請選擇廣告主" :asyncData.sync="adHostData" :mapData="adHostDataList" :mapDataFormat="{label:'userName',value:'userId'
}">
<
/select-search>
複製程式碼
asyncData
:響應的資料,也就是選中的..回來是一個物件mapData
: 搜尋的列表資料,肯定是外部傳入了…mapDataFormat
: 列表值對映
程式碼
- 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="關鍵字" 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:;
">
{{item[mapDataFormat.label]
}
} <
/a>
<
/li>
<
/transition-group>
<
p class="noFound" v-show="typeaheadData &
&
typeaheadData.length === 0">
未能查詢到,請重新輸入!<
/p>
<
/div>
<
/transition>
<
/div>
<
/div>
<
/template>
<
script>
export default {
name: 'selectSearch', data: function () {
return {
placeholderValue: '',// 給看到選擇內容的 isExpand: false, searchVal: '', // 搜尋關鍵字 resultVal: '', // 儲存搜尋到的值 searchList: [], //儲存過濾的結果集 currentIndex: -1, // 當前預設選中的index,
}
}, computed: {
mapFormatData () {
// 外部有傳入格式的時候對映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: '--請選擇--'
}, emptyText: {
type: String, default: '暫無資料'
}, mapData: {
// 外部傳入的列表資料 type: Array, default: function () {
return []
}
}, mapDataFormat: {
// 對映傳入資料的格式 type: Object, default: function () {
return {
label: 'text', value: 'value', extraText: 'extraText'
}
}
}, asyncData: {
// 實時響應的值 type: [Object, String], default: function () {
return {
}
}
}
}, methods: {
showHideMenu (e) {
// 點選其他區域關閉下拉選單 if (e) {
if (this.$refs.selectSearch &
&
this.$refs.selectSearch.contains(e.target)) {
this.isExpand = true;
} else {
this.isExpand = false;
}
}
}, resetDefaultStatus () {
// 清除所有選中狀態 this.searchVal = '';
this.currentIndex = -1;
this.typeaheadData.map(item =>
{
this.$delete(item, 'active');
})
}, setActiveClass (index) {
// 設定樣式活動類 this.typeaheadData.map((item, innerIndex) =>
{
if (index === innerIndex) {
this.$set(item, 'active', true);
this.currentIndex = index;
// 這句話是用來修正index,就是鍵盤上下鍵的索引,不然會跳位
} else {
this.$set(item, 'active', false)
}
})
}, selectChildWidthArrowDown () {
// 判斷index選中子項 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選中子項 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 () {
// 若是結果集只有一個,則預設選中 if (this.typeaheadData.length === 1) {
this.$emit('update:asyncData', this.typeaheadData[0]);
// emit響應的值 this.placeholderValue = this.typeaheadData[0][this.mapDataFormat.label];
} else {
// 若是搜尋的內容完全匹配到項內的內容,則預設選中 this.typeaheadData.map(item =>
{
if (this.searchVal === item[this.mapDataFormat.label] || item.active === true) {
this.$emit('update:asyncData', item);
// emit響應的值 this.placeholderValue = item[this.mapDataFormat.label];
}
})
} this.isExpand = false;
}, selectChild (index) {
// 滑鼠點選選擇子項 this.typeaheadData.map((item, innerIndex) =>
{
if (index === innerIndex || item.active) {
this.placeholderValue = item[this.mapDataFormat.label];
this.$emit('update:asyncData', item);
// emit響應的值
}
});
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>
複製程式碼
總結
對一些小夥伴若是有些許幫助,就是這文章最大的價值;
有更好的實現方式可以往下面留言,謝謝