为了账号安全,请及时绑定邮箱和手机立即绑定

Vue.js新手教学|如何写一个Checklist组件(下)

标签:
Vue.js


作者:Dunizb,原文地址

第四步:组件显示隐藏
4.1 显示组件

这一步我们要做一下的组件的显示与隐藏,点击输入框从页面底部显示组件,点击取消或者蒙层从上到下隐藏组件,并且添加过渡动画。这其实使用定位和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"即可。

4.4 移动端input输入框阻止弹起手机虚拟键盘

在移动端,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 即可


还可以做点什么呢?
大家可以扩展一下…

完整源码

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消