话很少说,先把组件基本的结构样式贴出来。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>