前言:
我的项目开发的时候刚好遇到一个需要,须要在输入框输出名字的时候,弹出相应的人员列表提供抉择,而后将数据赋值给输入框。我的项目是应用iview组件的,一开始想着在自定义iview的下拉抉择,起初发现成果并不现实。为了实现性能,就在iview输入框的根底上进行了组件封装,上面就来讲下组件封装的过程。
思路:
对于组件封装,首先须要确定性能,组件的整体构造,前面再去解决组件的数据交互逻辑。
过程:
组件的构造以及款式:
话不多说,先把组件根本的构造款式贴出来。
组件布局:
<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>
既然是输出下拉,就须要一个输入框以及一个下拉列表。
款式:
.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; } } }}
写完布局以及款式之后的成果:
组件的性能逻辑:
1. 确定父子组件数据传递的props
props: { // 父组件传递的输入框初始值 value: { type: String, default: '' }, // 下拉列表的题目 dropdownTitle: { type: String, default: '您可能要找' }, // 下拉列表搜寻数据为空时的提醒 dropdownMsg: { type: String, default: '数据为空!' }, // 下拉列表的初始数组 dropdownList: { type: Array, default: () => [] }, // 输入框的提醒 placeholder: { type: String, default: '请输出名字' },}
2. 定义组件的data
data() { return { // 管制下拉列表显示 dropdownShow: false, // 管制下拉列表数据为空提醒显示 dropdownMsgShow: false, // 输入框值 inputValue: '', // 搜寻后的下拉列表,用于渲染下拉 searchDataList: [] }}
3. 下拉列表的搜寻逻辑
<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事件。
// 输入框输出处理函数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
赋值,来管制搜寻数据的提示信息。
4. 搜寻后的点击抉择解决
给下拉列表的每一项li
绑定一个点击事件handleChoose
。
// 下拉抉择处理函数handleChoose: function (val) { let _this = this; _this.inputValue = val; _this.dropdownShow = false;}
点击之后对输入框进行赋值,并暗藏下拉列表。
5. 给组件增加一个clickoutside
指令
自定义clickoutside
指令,当点击组件外的区域时暗藏下拉列表。
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>