作者:Dunizb,原文地址
这一步我们要做一下的组件的显示与隐藏,点击输入框从页面底部显示组件,点击取消或者蒙层从上到下隐藏组件,并且添加过渡动画。这其实使用定位和CSS3的transform属性即可实现。
.cl-checklist{
overflow: hidden;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
-webkit-transition: all .5s;
transition: all .5s;
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
.cl-checklist.show{
-webkit-transform: translateY(0%);
transform: translateY(0%);
}
我们还得为组件弄一个是否显示和隐藏的属性isOpen,默认为false不显示,用它来控制给组件动态添加显示和隐藏的.cl-checklist.show类。
<div class="cl-checklist" :class="{'show': isOpen}">
那在demo.vue中如何调用这个属性呢?这时候我们就不得不考虑对外提供方法了,我们可以定义一个显示和隐藏的方法来供使用者调用。
methods: {
show () {
this.isOpen = true
},
hide () {
this.isOpen = false
}
}
在demo.vue中,为输入框添加事件,然后调用组件的 show 方法
<template>
<div class="cl-div">
<div class="center">checklist demo</div>
<div>
<input type="text" @focus="openChecklist" placeholder="请选择考场">
</div>
<checklist ref="checklist" :max="2"></checklist>
</div>
</template>
<script>
import checklist from '@components/checklist/checklist'
export default {
methods: {
openChecklist () {
this.$refs['checklist'].show()
}
},
components: { checklist }
}
</script>
现在的效果如下:
4.2 隐藏组件做好了显示那隐藏就很简单了,点击取消隐藏组件,动画会原路返回,只需要为取消设置一下isOpen = false或者调用hide方法即可。
<div class="topbar">
<span class="cancel" @click="hide">取消</span>
<span class="title">选择考场</span>
<span class="confirm">完成</span>
</div>
现在效果如下
4.3 添加蒙层为了使蒙层能够覆盖整个页面,还不得不为DOM结构做一下调整
<div class="cl-checklist">
<div class="checklist" :class="{'show': isOpen}">
... ...
</div>
<!--蒙层-->
<div class="checklist-overlay" v-if="isOpen"></div>
</div>
.checklist-overlay的CSS如下
.checklist-overlay{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background: rgba(0, 0, 0, .5);
transition: all .5s;
}
对应的,DOM结构调整后,最外层的样式也要改一下
.cl-checklist{
overflow: hidden;
}
.checklist{
position: fixed;
bottom: 0;
left: 0;
z-index: 2000;
width: 100%;
background-color: #fff;
-webkit-transition: all .5s;
transition: all .5s;
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
特别注意,为.checklist增加了白色背景和z-index:2000,现在的效果如下
当然了,你也可以让点击蒙层的时候也可以隐藏组件,直接给蒙层绑定一个单击事件@click = "hide"即可。
在移动端,input会默认触发手机的虚拟键盘,如何阻止手机虚拟键盘弹起呢?目前我试过有两个方案,一个是给input添加readonly属性,另一个就是在input事件处理方法前面添加一句document.activeElement.blur()。关于这个问题的详细可以阅读我的另一篇博客《小技巧|H5禁止手机虚拟键盘弹出》
methods: {
show () {
document.activeElement.blur()
this.isOpen = true
}
}
第五步:数据渲染和向父组件传递事件
此文中子组件就是 checklist.vue ,父组件就是 demo.vue
5.1 数据渲染前面我们的数据都是写死的,现在我们来动态渲染数据,也就是循环数据了。从父组件传递数据,在子组件中接收,还得使用 Props,前面我们定义了一个 max 属性,用来控制最多选择几项,我们再添加一个 Props 属性,取名为 dataList,这是一个数组类型,并且是必须的
props: {
max: {
type: Number,
default: 0
},
dataList: {
type: Array,
require: true
}
}
在组件中传递这个 Props ,需要注意的就是,由于 HTML 特性不区分大小写,当使用 DOM 模板时,驼峰命名的 props 名称要转为短横线分隔符命名。不能dataList在组件中必须使用中划线,变成data-list 形式。
<checklist ref="checklist" :data-list="data" :max="2"></checklist>
定义 data 数据,这里的数据应该是从后端接口中来的,这里我就模拟一下数据了
data () {
return {
data: [{
label: '科目二第07考点马路',
value: '101',
address: '上海市宝山区宝安公路2009号'
},{
label: '科目二第08考点沪松公路',
value: '102',
address: '上海市闵行区沪松公路565弄128号'
},{
label: '科目二第09考点七宝',
value: '103',
address: '上海市闵行区沪松公路200号'
},{
label: '科目二第09考点世纪公园世纪公园',
value: '104',
address: ''
},{
label: '科目二第09考点世纪公园',
value: '105',
address: '上海市浦东新区世纪大道200号'
},{
label: '科目二第09考点哈哈哈哈',
value: '107'
},{
label: '科目二第09考点合川路地铁站',
value: '106',
address: '上海市合川路地铁站2号出口'
}]
}
}
最后就是渲染了,回到 checklist.vue 中,把 v-for 补上就行了
<div class="list" ref="list">
<div v-for="(item, index) in dataList" class="line-wrapper" :data-val="item.value">
<label :for="index" class="line border-1px">
<div class="l">
<div class="title">{{item.label}}</div>
<div class="address" v-if="item.address">{{item.address}}</div>
</div>
<div class="r"></div>
</label>
<input type="checkbox" :id="index" @click="selectedItem($event)"
v-model="checkboxValue" style="display:none" :value="item.value">
</div>
</div>
需要注意的就是第 3 行和第 10 行,for的值和id的值必须一致,这里最好是使用v-for的index,当然了,也可以用item.label或item.value,但不推荐这样做。
5.2 组件通信与自定义事件最后的最后,就是该处理点击“完成”后把选中的值传递给父页面了。我们已经知道从父组件向子组件通信,通过 props 传递数据就可以了,当子组件需要向父组件传递数据时,就需要用到自定义事件。v-on 指令除了可以监听 DOM 事件外,还可以用于组件之间额自定义事件
在 Vue.js 中子组件使用 $emit() 来触发事件,父组件使用 $on() 来监听子组件的事件。父组件也可以直接在子组件的自定义标签上使用 v-on 来监听子组件触发的自定义事件。
我们给确定使用@click按钮绑定一个方法,叫onConfirm
<div class="topbar">
<span class="cancel" @click="hide">取消</span>
<span class="title">选择考场</span>
<span class="confirm" @click="onConfirm">完成</span>
</div>
我们需要传递什么给父组件?选中的值,这个值应该包含一个考场 value 值(也就是考场的id),选中的考场名称,或许还需要考场的地址,我们可以把这几个值使用|符号连接这几个值一起放到单选框的 value 里面
<div class="list" ref="list">
<div v-for="(item, index) in dataList" class="line-wrapper" :data-val="item.label + '|' + item.value">
<label :for="index" class="line border-1px">
<div class="l">
<div class="title">{{item.label}}</div>
<div class="address" v-if="item.address">{{item.address}}</div>
</div>
<div class="r"></div>
</label>
<input type="checkbox" :id="index" @click="selectedItem($event)"
v-model="checkboxValue" style="display:none" :value="item.label + '|' + item.value">
</div>
</div>
注意:第 2 行的 data-val 要跟 单选框的 value 值保持一致,因为接下来的 JS 逻辑需要用到它来和单选框的 value 比较,我们来实现 onConfirm() 方法
onConfirm () {
this.isOpen = false
const checkboxValue = this.checkboxValue
const res = []
for (let i = 0; i < checkboxValue.length; i++) {
const resObj = {}
const item = checkboxValue[i].split('|')
resObj.label = item[0]
resObj.value = item[1]
res.push(resObj)
}
this.$emit('on-change', res)
}
在方法中,首选取得checkboxValue的值,然后分别取出其中的value、label 和 address 三个部分放到一个对象resObj 中,再放到 res 数组中,最后把这个数组对象作为 on-change 事件的返回值参数。
在父组件的子组件标签上我们用 @on-change 来接收
<checklist ref="checklist"
:data-list="data"
:max="2" @on-change="changeKaochangValue"></checklist>
在父组件的 data 选项中定义一个kaochangVal属性来接收,然后把选中的考场名称打印出来
<p v-for="(item, index) in kaochangVal">{{item.label}}</p>
changeKaochangValue 方法
changeKaochangValue (val) {
this.kaochangVal = val
}
现在的效果如下:
至此,这个 Checklist 组件算是完成了。
考虑通用性,假如需求需要 CheckBox 框在左边呢?这个问题其实很好解决,因为我们使用 Flexbox 布局,天然支持,只需要多加一句样式即可。这个特性应该是可以用户设置的,也就是得弄一个 props 属性来支持。
props : {
checkboxLeft: {
type: Booolean,
default: false
}
}
定义一个 checkboxLeft 属性,默认为 false 也就是 默认 checkbox 在右边,只有用户显示传递改值为 true 时 checkbox 才在左边。
前面说只需要加一个样式就可以让 checkbox 在左边了,为 .line 元素设置一个样式 class ,然后通过 checkboxLeft 这个 props 来动态绑定 class
.list .line.checkbox-left{
flex-direction: row-reverse;
}
...
<label :for="index" class="line border-1px" :class="{'checkbox-left': checkboxLeft}">
...
熟悉 Flexbox 的同学应该知道,flex-direction是控制布局的方向,row-reverse就是倒序的意思,原来是 12 排列,row-reverse 后就变成 21 排列了。
在组件上(demo.vue)设置
<checklist ref="checklist"
:data-list="data"
:max="2"
:checkbox-left="true"
@on-change="changeKaochangValue"></checklist>
显示设置 props 的checkboxLeft 为 true 即可
还可以做点什么呢?
大家可以扩展一下…