前言:
我的项目开发的时候刚好遇到一个需要,须要在输入框输出名字的时候,弹出相应的人员列表提供抉择,而后将数据赋值给输入框。我的项目是应用 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>