話很少說,先把組件基本的結構樣式貼出來。vue
組件佈局:node
<template> <div class="selectInput"> <div class="input-box"> <Input type="text" /> </div> <div class="input-dropdown"> <div class="dropdown-title"> 您可能須要查找 </div> <div class="dropdown-content"> <ul class="content-list"> <li class="list-item"> <span class="item-avatar">李</span> <span class="item-name">李四</span> </li> </ul> <!-- 沒有數據時的提示信息 --> <div class="content-msg"> 暫無數據 </div> </div> </div> </div> </template>
既然是輸入下拉,就須要一個輸入框以及一個下拉列表。express
樣式:數組
.selectInput { position: relative; .input-dropdown { min-width: 200px; position: absolute; top: 35px; left: 0; border: 1px solid #cfcfcf; border-radius: 5px; .dropdown-title { padding: 5px 10px; color: #e1e1e1; background-color: #f8f8f8; } .dropdown-content { .content-list { margin: 0; padding: 0; .list-item { margin: 0; padding: 5px 10px ; list-style: none; &:hover { cursor: pointer; background-color: #f7f5f5; } .item-avatar { display: inline-block; width: 30px; height: 30px; line-height: 30px; color: #ffffff; background-color: #b6c4de; border-radius: 50%; text-align: center; margin-right: 10px; } .item-name { display: inline-block; color: #666666; } } } .content-msg { padding: 10px; color: #cccccc; } } } }
寫完佈局以及樣式以後的效果:
less
props: { // 父組件傳遞的輸入框初始值 value: { type: String, default: '' }, // 下拉列表的標題 dropdownTitle: { type: String, default: '您可能要找' }, // 下拉列表搜索數據爲空時的提示 dropdownMsg: { type: String, default: '數據爲空!' }, // 下拉列表的初始數組 dropdownList: { type: Array, default: () => [] }, // 輸入框的提示 placeholder: { type: String, default: '請輸入名字' }, }
data() { return { // 控制下拉列表顯示 dropdownShow: false, // 控制下拉列表數據爲空提示顯示 dropdownMsgShow: false, // 輸入框值 inputValue: '', // 搜索後的下拉列表,用於渲染下拉 searchDataList: [] } }
<template> <div class="selectInput"> <div class="input-box"> <Input :placeholder="placeholder" v-model="inputValue" @input.native="handleInput" type="text" /> </div> <div v-show="dropdownShow" class="input-dropdown"> <div class="dropdown-title"> {{ dropdownTitle }} </div> <div class="dropdown-content"> <ul class="content-list"> <li v-for="(item, index) in searchDataList" :key="index" @click="handleChoose(item.name)" class="list-item"> <span class="item-avatar">{{ item.avatar }}</span> <span class="item-name">{{ item.name }}</span> </li> </ul> <!-- 沒有數據時的提示信息 --> <div v-show="dropdownMsgShow" class="content-msg"> {{ dropdownMsg }} </div> </div> </div> </div> </template>
經過dropdownShow去控制下拉列表的顯示隱藏,給輸入框綁定一個inputValue值和一個input事件。iview
// 輸入框輸入處理函數 handleInput: debounce(function () { let _this = this; _this.searchDataList = []; if (_this.inputValue === '') { _this.dropdownShow = false; } else { _this.dropdownList.map(v => { if (v.name.indexOf(_this.inputValue) >= 0) { _this.searchDataList.push(v); } }); _this.searchDataList.length > 0 ? _this.dropdownMsgShow = false : _this.dropdownMsgShow = true; _this.dropdownShow = true; } })
handleInput
我作了防抖處理,在這個函數裏面經過監聽輸入框輸入事件,判斷輸入框的inputValue
是否爲空,若爲空則直接隱藏下拉列表。不爲空則循環迭代從父組件傳遞過來的dropdownList
,並將符合條件的item
存進searchDataList
,而後在組件中經過v-for
渲染出數據。
最後經過判斷searchDataList
的長度給dropdownMsgShow
賦值,來控制搜索數據的提示信息。ide
給下拉列表的每一項li
綁定一個點擊事件handleChoose
。函數
// 下拉選擇處理函數 handleChoose: function (val) { let _this = this; _this.inputValue = val; _this.dropdownShow = false; }
點擊以後對輸入框進行賦值,並隱藏下拉列表。佈局
clickoutside
指令自定義clickoutside
指令,當點擊組件外的區域時隱藏下拉列表。this
directives: { // 自定義指令用於處理點擊組件區域以外的click事件 clickoutside: { bind(el, binding, vnode) { function documentHandler(e) { if (el.contains(e.target)) { return false; } if (binding.expression) { binding.value(e); } } el.__vueClickOutSize__ = documentHandler; document.addEventListener("click", documentHandler); }, unbind(el, binding) { document.removeEventListener("click", el.__vueClickOutSize__); delete el.__vueClickOutSize__; }, }, },
給指令綁定一個關閉事件handleClose
。
// 下拉列表隱藏處理函數 handleClose: function() { let _this = this; if (_this.dropdownShow) { if (_this.searchDataList.length === 1) { _this.inputValue = _this.searchDataList[0].name; } else { _this.inputValue = ''; } } _this.dropdownShow = false; },
在這個函數裏我作了一個處理,當點擊的時候,搜索列表有數據時,會默認選中第一個,不然清空輸入框。
關於函數防抖以及clickoutside
,網上有大佬發了一些關於這些的文章,我在這裏就不進行贅述了。
至此,組件封裝完成,組件的大致思路是這樣子,具體的邏輯處理能夠根據實際狀況進行相應的調整。
最後附上整個組件的代碼:
調用代碼:
<template> <div id="blog"> <select-input :dropdownList="personnelList"></select-input> </div> </template> <script> /* 引入輸入下拉組件 */ import selectInput from './component/selectInput'; export default { name: 'blog', components: { selectInput }, data() { return { personnelList: [ { avatar: '張', name: '張三' }, { avatar: '李', name: '李四' }, { avatar: '王', name: '王五' }, { avatar: '趙', name: '趙六' }, { avatar: '李', name: '李師師' } ] } }, methods: { }, created() { }, mounted() { } } </script> <style lang="less" scoped> #blog { padding: 50px; width: 500px; margin: 100px auto 0; background: #ffffff; height: 500px; } </style>
組件代碼:
<template> <div v-clickoutside="handleClose" class="selectInput"> <div class="input-box"> <Input :placeholder="placeholder" v-model="inputValue" @input.native="handleInput" type="text" /> </div> <div v-show="dropdownShow" class="input-dropdown"> <div class="dropdown-title"> {{ dropdownTitle }} </div> <div class="dropdown-content"> <ul class="content-list"> <li v-for="(item, index) in searchDataList" :key="index" @click="handleChoose(item.name)" class="list-item"> <span class="item-avatar">{{ item.avatar }}</span> <span class="item-name">{{ item.name }}</span> </li> </ul> <div v-show="dropdownMsgShow" class="content-msg"> {{ dropdownMsg }} </div> </div> </div> </div> </template> <script> // 引入函數防抖 import { debounce } from "@/plugins/util/debounce"; export default { name: 'selectInput', directives: { // 自定義指令用於處理點擊組件區域以外的click事件 clickoutside: { bind(el, binding, vnode) { function documentHandler(e) { if (el.contains(e.target)) { return false; } if (binding.expression) { binding.value(e); } } el.__vueClickOutSize__ = documentHandler; document.addEventListener("click", documentHandler); }, unbind(el, binding) { document.removeEventListener("click", el.__vueClickOutSize__); delete el.__vueClickOutSize__; }, }, }, props: { // 父組件傳遞的輸入框初始值 value: { type: String, default: '' }, // 下拉列表的標題 dropdownTitle: { type: String, default: '您可能要找' }, // 下拉列表搜索數據爲空時的提示 dropdownMsg: { type: String, default: '數據爲空!' }, // 下拉列表的初始數組 dropdownList: { type: Array, default: () => [] }, // 輸入框的提示 placeholder: { type: String, default: '請輸入名字' }, }, data() { return { // 控制下拉列表顯示 dropdownShow: false, // 控制下拉列表數據爲空提示顯示 dropdownMsgShow: false, // 輸入框值 inputValue: '', // 搜索後的下拉列表,用於渲染下拉 searchDataList: [] } }, methods: { // 下拉列表隱藏處理函數 handleClose: function() { let _this = this; if (_this.dropdownShow) { if (_this.searchDataList.length === 1) { _this.inputValue = _this.searchDataList[0].name; } else { _this.inputValue = ''; } } _this.dropdownShow = false; }, // 輸入框輸入處理函數 handleInput: debounce(function () { let _this = this; _this.searchDataList = []; if (_this.inputValue === '') { _this.dropdownShow = false; } else { _this.dropdownList.map(v => { if (v.name.indexOf(_this.inputValue) >= 0) { _this.searchDataList.push(v); } }); _this.searchDataList.length > 0 ? _this.dropdownMsgShow = false : _this.dropdownMsgShow = true; _this.dropdownShow = true; } }), // 下拉選擇處理函數 handleChoose: function (val) { let _this = this; _this.inputValue = val; _this.dropdownShow = false; } }, created() { }, mounted() { } } </script> <style lang="less" scoped> .selectInput { position: relative; .input-dropdown { min-width: 200px; position: absolute; top: 35px; left: 0; border: 1px solid #cfcfcf; border-radius: 5px; .dropdown-title { padding: 5px 10px; color: #e1e1e1; background-color: #f8f8f8; } .dropdown-content { .content-list { margin: 0; padding: 0; .list-item { margin: 0; padding: 5px 10px ; list-style: none; &:hover { cursor: pointer; background-color: #f7f5f5; } .item-avatar { display: inline-block; width: 30px; height: 30px; line-height: 30px; color: #ffffff; background-color: #b6c4de; border-radius: 50%; text-align: center; margin-right: 10px; } .item-name { display: inline-block; color: #666666; } } } .content-msg { padding: 10px; color: #cccccc; } } } } </style>