背景
这一年内公司的三个项目都是使用Vue2 + ElementUI来进行开发的,在项目启动开发的时候,技术总监要求能封装成组件的那就必须是已经封装好的组件;但是在实际开发过程中,面对变幻莫测的需求变更,无论是基础的通用组件还是业务组件都需要具备可拓展,可以满足一个项目一个系统的普适功能需求。
对于后台管理类项目,输入框组件是一个不可或缺的组件元素。这里就有多个场景是围绕下拉组件展开的。
场景
1:可输入可下拉选择,两者既可以独立也可以混合在一起使用
首先我们需要了解ElementUI的Select选择器是非常接近于满足该场景的,的确我们开启Select选择器的可搜索以及创建条目功能之后即可实现组件的可输入,可下拉选择,但是它最终的效果却是我们在输入框输入完数据之后,下拉面板就会出现我们输入的数据,我们还需要选择下拉面板里面出现的我们选择的数据之后才可以最终将数据赋值给最终变量,该方案如果是遇到不较真的产品经理还勉强说的过去,毕竟功能是实现了,但是如果遇到强势的项目经理,那么在她眼里,这个需求,这个功能就是没有做好。
既然没有现成的组件,那么砸门就自己造一个
首先我们对既定组件进行分析,该组件在表现形式层面分为输入框和下拉面板,所以第一步是完成组件基本表现元素搭建
<div class="custom-select">
<el-input>
<i slot="suffix" class="el-input__icon el-icon-arrow-down"></i>
</el-input>
<transition name="el-zoom-in-top">
<div class="custom-select-dropdown" ref="dropdown">
<ul class="custom-select-list">
<li></li>
</ul>
</div>
</transition>
</div>
<style scoped>
.custom-select {
position: relative;
}
.custom-select-dropdown {
position: absolute;
left: 0;
top: 100%;
width: 100%;
z-index: 10;
background-color: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
min-height: 60px;
}
.el-zoom-in-top-enter-active,
.el-zoom-in-top-leave-active {
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
.el-zoom-in-top-enter,
.el-zoom-in-top-leave-to {
opacity: 0;
transform: translateY(-10px);
}
.custom-select-list {
margin: 0;
padding: 6px 0;
list-style: none;
}
.custom-select-list > li {
padding: 0 20px;
line-height: 36px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #606266;
}
.custom-select-list > li:hover {
background-color: #f5f7fa;
}
.custom-select-list > li.is-selected {
background-color: #f5f7fa;
font-weight: bold;
}
li{
font-size: 16px;
}
</style>
第二步是补齐功能点
功能点一:下拉面板的展开和收起是正常可使用的
首先下拉面板是会重复显示的,所以我们使用v-show对面板进行显隐控制,接着我们在data函数方法里面定义变量dropdownVisible并设置默认值为false,并在输入框末尾箭头处添加点击事件对面板进行显隐切换
//在点击除下拉面板的其他区域时候,我们也需要对面板进行隐藏,这个时候我们需要在mounted生命周期里面监听点击事件
mounted() {
document.addEventListener("click", (e) => {
if (!this.$el.contains(e.target)) {
this.dropdownVisible = false;
}
});
}
//当然要记得在页面关闭的时候对监听事件进行销毁,避免内存泄漏
beforeDestroy() {
document.removeEventListener("click");
}
功能点二:支持输入,输入的数据我们需要实时获取得到,这里使用v-model
,双向绑定获取数据
这里直接在el-input上面使用v-model绑定一个变量,并在data函数方法里面声明即可
功能点三:下拉面板的选择项需要通过请求后端接口返回字典数据进行渲染,并且支持可设置请求不同的字典项
通常情况下,下拉组件的下拉选择项数据都是后端统一一个接口,并通过接收前端传过来的不同字典名称提供不同的数据,当然也不排除有特殊情况,所以我们需要封装一个方法,判断是否是通过字典名称来请求后端数据还是通过调用该组件的页面
async getDropData(){
// 如果是有配置字典,那么请求字典接口
// 如果是没有,那么就根据接口去请求数据
let res
if(this.option.dictName){
res = await getDict(this.option.dictName)
}else{
res = await getTrendsDict(this.option.interface)
}
this.optionsList=[{nameShow: ''}]
this.optionsList.splice(1,0,...res)
}
在mounted
里面进行调用
mounted(){
this.getDropData()
}
功能点四:支持设置输入框提示,支持设置禁用
这里的实现主要是组件内部接受一个配置项对象,在引用该组件的页面那里对配置项进行配置之后传递给到组件内部即可
组件外部
vpfOptions:{
dictName:"FUNCY-DICT",
interface:"/search/dropList",
placeholder:'请输入',
disabled:false
}
组件内部
props: {
option:Object
},
接着在对应功能点区域引用即可
功能点五:支持自定义v-model
,达到可以搭配表单进行数据校验
首先在组件内部使用model
在监听输入框输入数据以及选择数据的时候,调用组件事件传参
通过以上配置,在引用组件的地方正常使用即可
最终效果
输入数据并打印:
进行表单校验:
完整未优化基础组件代码
<template>
<div class="custom-select">
<el-input
v-model="inputValue"
@focus="handleFocus"
:placeholder="option.placeholder"
:disabled="option.disabled"
@input="handleInput"
>
<!-- 展开收起切换 -->
<i slot="suffix" class="el-input__icon el-icon-arrow-down" @click="toggleDropdown"></i>
</el-input>
<transition name="el-zoom-in-top">
<div v-show="dropdownVisible" class="custom-select-dropdown" ref="dropdown">
<ul class="custom-select-list">
<li
v-for="(option, index) in optionsList"
:key="index"
@click="handleOptionClick(option)"
:class="{ 'is-selected': isSelected(option) }"
>
{{ option.nameShow }}
</li>
</ul>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "CustomSelect",
model:{
prop:'myValue',
event:'myInput'
},
props: {
option:Object
},
data() {
return {
dropdownVisible: false,
inputValue:'',
optionsList:[{nameShow: ""}],
normal:false
};
},
methods: {
// 鼠标聚焦输入框
handleFocus() {
if(this.normal){
this.toggleDropdown();
}
},
handleInput(val) {
// 把输入的数据添加到下拉列表数组里面
this.optionsList[0].nameShow = val
// 输入的同时下拉面板出现
if(this.normal){
this.dropdownVisible = true;
}
// 调用模糊查询接口更新下拉数据
this.getDropData()
this.$emit('myInput', val)
},
async getDropData(){
// 如果是有配置字典,那么请求字典接口
// 如果是没有,那么就根据接口去请求数据
// let res
// if(this.option.dictName){
// res = await getDict(this.option.dictName)
// }else{
// res = await getTrendsDict(this.option.interface)
// }
// this.optionsList=[{nameShow: ''}]
// this.optionsList.splice(1,0,...res)
// // const params = {payUserName:this.inputValue}
// const res = [{nameShow:'xiaohui'},{nameShow:'xiaomei'},{nameShow:'xiaoli'},]
// // 清空上一次查询数据,保持this.optionsList数据为最新
// this.optionsList=[{nameShow: ''}]
// this.optionsList.splice(1,0,...res)
// console.log("数据打印",this.optionsList);
},
toggleDropdown() {
if (!this.option.disabled) {
this.dropdownVisible = !this.dropdownVisible;
}
},
handleOptionClick(option) {
console.log("打印选择的数据",option);
this.inputValue = option.nameShow;
this.dropdownVisible = false;
this.$emit('myInput', option.nameShow)
this.$emit("change", option);
},
isSelected(option) {
return option === this.inputValue;
},
},
mounted() {
document.addEventListener("click", (e) => {
if (!this.$el.contains(e.target)) {
this.dropdownVisible = false;
}
});
},
beforeDestroy() {
document.removeEventListener("click");
},
};
</script>
<style scoped>
.custom-select {
position: relative;
}
.custom-select-dropdown {
position: absolute;
left: 0;
top: 100%;
width: 100%;
z-index: 10;
background-color: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
min-height: 60px;
}
.el-zoom-in-top-enter-active,
.el-zoom-in-top-leave-active {
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
.el-zoom-in-top-enter,
.el-zoom-in-top-leave-to {
opacity: 0;
transform: translateY(-10px);
}
.custom-select-list {
margin: 0;
padding: 6px 0;
list-style: none;
}
.custom-select-list > li {
padding: 0 20px;
line-height: 36px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #606266;
}
.custom-select-list > li:hover {
background-color: #f5f7fa;
}
.custom-select-list > li.is-selected {
background-color: #f5f7fa;
font-weight: bold;
}
li{
font-size: 16px;
}
</style>