最近的项目中需要用到,所以自己手写了一个,拿出来分享。
首先,我们最后实现的是一个支持一级、二级、三级乃至多级联动的可复用组件,照例先看一下效果图,第一张是二级联动,第二张是三级联动。是我们实现的最终效果。
本文用到的知识点
- Vue组件绑定的v-model,参考:Vue在组件(非表单控件)上使用v-model双向数据绑定@郝晨光
- Vue的组件通信
- Vue的插槽
- 对Vue的API有一定的了解
- Vue的nextTick方法
- 对原生JS的拖拽有一定的功底
功能分析
- 暴露在最外层的,是显示在外部的被选择的文字,当点击的时候弹出选择器
- 内部的级联选择器的数量由外部决定,级联选择器的选项由外部决定
- 实现数据的双向绑定,内部修改外部改变,外部修改内部改变
- 确定按钮保存当前选中,取消按钮取消本次操作并返回上次选中
- 尽可能的只实现功能,暴露在外部的按钮的样式由外部操作
- 当点击时动画弹出选择器,确定和取消以及点击外部时收起选择器
定稿实现方式
- 由于级联选择器和选项的数量都由外部决定,所以直接拆分为三个组件(外层包裹、级联选择器、级联选项),全部暴露给开发者
- 对于外层包裹实现动画显示隐藏,以及数据双向绑定,对于级联选择器实现拖拽,数据修改及时发布、对于级联选项实现数据渲染
正式开始
先看一下我们最终使用的时候,是如何使用的吧
{{item.name}}
{{item.name}}
// script
// 数据是后台请求的,我这里就不贴出来了
data() {
address: {
province: '',
city: ''
}
},
computed: {
showAddress() {
let province = this.provinceList.find(item => item.id===this.address.province) || {name:'选择省'};
let city = this.cityList.find(item => item.id===this.address.city) || {name:'选择市'};
return `${province.name} - ${city.name}`;
}
}
对于我们的最终效果来说,是不是很简单呢?
当然,如果我这里写三个h-wrapper的话,我们自然而然就变成了三级联动
首先需要先定义最外层的slector组件
最外层的slector组件用来暴露在外部显示的文字,以及控制级联选择器的显示隐藏,在每一次开始一个新的功能的时候,我们应该先完成重点的功能,重点功能完成之后,再去修改一些样式包括交互效果
- 在实施整个组件之前,其实我对最外层的selector组件并没有做太多的事情,因为我们的级联选择器是需要定位的,所以无论外层什么样子,都不会对我们的功能造成影响
- 一起来看一下最终的h-selector组件的模板
{{showValue || defaultShowValue}}
取消
{{title}}
确定
对于这个模板来说,需要解释的地方很少,我都写在注释里边
- 接着,我们看一下最终的逻辑部分
首先需要重点提一下的是在mounted方法中,我们使用了this.$on方法,订阅了一个在当前组件内并没有发布的事件,这个事件,我定义在了h-wrapper这个组件中,稍后可以看到
其余的地方没有太复杂的功能和逻辑,就不一一解释
接着定义最主要的部分,就是我们的wrapper组件
- 模板部分
-
-
请选择
-
-
在模板中,需要重点关注的其实就是两个style,以及四个事件,当然了,还有插槽的位置,我这里使用了一个小技巧,在原始位置直接写好了四个option,并且其中一个还显示请选择,用来保证可选项的位置永远都可以处在最中间的位置
- 逻辑部分(重中之重)
关于逻辑的部分我全部写在了注释中,请仔细研读
在逻辑中,我通过操作activeIndex这个索引值来动态的修改数据中ul的位移,使得当前永远显示的是对应索引与option高度计算出的位置
通过touchstart、touchmove、touchend三个事件来操作元素的位置与滑动
通过watch侦听对应属性,并实时触发事件,使得级联选择器发生改变,达到内外同步
通过mounted与updated钩子函数来保证当前的级联选择器属性会随着刷新而刷新
通过slot插槽来显示外部传入的option选项
最后是option组件
对于option组件来说,并没有多少内容,它只需要负责显示数据,以及让级联选择器可以正确的获取到值即可
{{value}}
样式问题
首先当前这个级联选择器的样式,我没有做太多的处理,但是已经很好看了有没有!
注意: 在外部显示的内容,h-selector-show并没有做过多的样式处理,将样式设置完全交予外部处理,保证你可以根据你的需求来设置样式
注意:由于我们开发的是移动端的demo,而移动端对固定定位fixed并不是很支持,所以我样式方面使用了absolute绝对定位,造成的问题就是在当前级联选择器的外层最好不要有任何的定位元素,否则会造成影响
.h-selector {
letter-spacing: 1px;
font-size: 16px;
width: 100%;
height: 100%;
.h-selector-show {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 0 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.h-selector-container {
position: absolute;
z-index: 999;
left: 0;
bottom: 0;
width: 100%;
background: #fff;
}
.h-selector-layer {
position: absolute;
background: rgba(0, 0, 0, 0.3);
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 2;
}
ul, li {
margin: 0;
padding: 0;
list-style: none;
}
.h-selector-header {
display: flex;
align-items: center;
height: 40px;
justify-content: space-between;
padding: 0 30px;
.h-selector-header-cancel {
color: #e9aa14;
}
.h-selector-header-confirm {
color: #508aff;
}
}
.h-selector-content {
display: flex;
width: 100%;
position: relative;
}
.h-selector-wrapper {
flex: 1;
overflow: hidden;
& + .h-selector-wrapper {
border-left: 1px solid #ddd;
}
}
.h-selector-option {
line-height: 60px;
height: 60px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.h-selector-bg {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(180deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.7)), linear-gradient(0deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.4));
backface-visibility: hidden;
pointer-events: none;
background-repeat: no-repeat;
background-position: top, bottom;
background-size: 100% 100px;
}
.h-selector-move-enter-to, .h-selector-move-leave {
transform: translate3d(0, 0, 0);
}
.h-selector-move-enter-active, .h-selector-move-leave-active {
transition: transform .6s;
}
.h-selector-move-enter, .h-selector-move-leave-to {
transform: translate3d(0, 100%, 0);
}
.h-selector-fade-enter-to, .h-selector-fade-leave {
opacity: 1;
}
.h-selector-fade-enter-active, .h-selector-fade-leave-active {
transition: opacity .4s;
}
.h-selector-fade-enter, .h-selector-fade-leave-to {
opacity: 0;
}
}
最终,我们就已经成功的打造了一款移动端的级联选择器,可以实现一级二级三级联动,当然了,多级也没有任何问题,但是在移动端,我建议最多到三级联动,否则的话影响用户体验感