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

首发于个人博客:dunizb.com 原文连接:blog.dunizb.com/2017/11/18/…javascript

建议在电脑上阅读此文,完整源码地址看文章末尾css

2017.11.30更新:本案例有了更优雅更简单的实现方案了,能够比较看一下两种实现方式,具体请看文末源码里的checklist2.0.vue文件html

2017.11.25更新章节:0.3 规划APIvue

本文教你如何写一个移动端的 Checklist 组件,使用 vue 单文件形式开发,适合 Vue.js 新手。同时此文很是长,最好跟着文章步骤边看边写。本文说些什么,或者你能收获什么?。java

  1. 一步步从0开始有节奏的编写 Checklist 组件
  2. 在编码过程当中进行功能分析
  3. 涉及一些移动端适配的问题
  4. Vue.js组件方面的一些知识

目录

前置知识
什么是Checklist
第零步:分析与准备git

  • [0.1 业务需求和功能分析](#0.1 业务需求和功能分析)
  • [0.2 规划API](#0.2 规划API)
  • [0.3 初始化项目](#0.3 初始化项目)

第一步:实现组件骨架和基础结构github

  • [1.1 实现基本骨架](#1.1 实现基本骨架)
  • [1.2 实现topbar](#1.2 实现topbar)
  • [1.3 实现操做提示栏](#1.3 实现操做提示栏)

第二步:实现list列表结构web

  • [2.1 实现基本骨架](#2.1 实现基本骨架)
  • [2.2 实现checkbox选框](#2.2 实现checkbox选框)
  • [2.3 地址信息前面的小图标](#2.3 地址信息前面的小图标)
  • [2.4 适配移动端1像素边框](#2.4 适配移动端1像素边框)
  • [2.5 列表滚动](#2.5 列表滚动)

第三步:实现选择CheckBox的交互功能后端

  • [3.1 实现原理与v-model](#3.1 实现原理与v-model)
  • [3.2 加个选中样式](#3.2 加个选中样式)
  • [3.3 最多可选择几项](#3.3 最多可选择几项)
    • [3.3.1 使用 props 传递数据](#3.3.1 使用 props 传递数据)
    • [3.3.2 Watch选项、refs的使用](#3.3.2 Watch选项、refs的使用)

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

  • [4.1 显示组件](#4.1 显示组件)
  • [4.2 隐藏组件](#4.2 隐藏组件)
  • [4.3 添加蒙层](#4.3 添加蒙层)
  • [4.4 移动端input输入框阻止弹起手机虚拟键盘](#4.4 移动端input输入框阻止弹起手机虚拟键盘)

第五步:数据渲染和向父组件传递事件

  • [5.1 数据渲染](#5.1 数据渲染)
  • [5.2 组件通讯与自定义事件](#5.2 组件通讯与自定义事件)

第六步:扩展和完善

前置知识

阅读此文前您最好有如下知识的基础:

  1. 对 Vue.js 的.vue单文件和 Vue.js 组件知识有基本的认识
  2. CSS 的 Flexbox 布局知识。

什么是Checklist

什么是Checklist组件?咱们先来看一下市面上已经有的UI框架的Checklist长什么样

weui Mint UI
weui
Mint-UI

而这种组件的一个典型场景是移动端的购物车列表,打开你的京东、淘宝购物车看看,功能是否是很像呢?

本文写一个什么样的 Checklist 组件?这个组件来自于我司真实项目,刚开始我也是用的 Mint-UI 来作,后来业务升级需求变动 Mint-UI 就不适合了,因而我就本身撸了一个,特整理出来此文,而且咱们尽可能把他作的通用、灵活一点。咱们的 Checklist 以下:

咱们的 京东购物车列表
咱们的

第零步:分析与准备

0.1 业务需求和功能分析

在动手撸代码以前,咱们先来仔细分析一下业务需求和功能点,这个组件是展现考场和考场地址,考场地址没有就不显示,最多选三个,选了三个后其余的要禁用等,数据是根据考试科目和所在城市动态获取,当列表数据不少咱们还得给它一个最大高度让列表可滚动等,从图中得出如下功能点:

  1. 显示隐藏和过渡动画
  2. 列表的地址一行无关紧要,行高自适应
  3. 选中状态和禁用状态
  4. 选择了几个和最多选几个的提示文字(咱们叫他操做提示栏吧,为了方便后面都这么叫),为了通用性,最多选几个应该作成可配置选项
  5. 列表可滚动以及列表最大高度,最大高度也能够作成可配置项
  6. 头部bar,标题、取消、肯定应该也是无关紧要,可作成可配置项
  7. 选中的项的值应该是一个数组
  8. 选中后点击完成按钮应该把选中的值发送给父组件
  9. checkbox选框能够在左边也能够在右边

等等....

0.2 规划API

一个Vue组件的 API 只来自 props、events 和 slots,肯定好这 3 部分的命名、规则,剩下的逻辑即便初版没作好,后续也能够迭代完善。可是 API 若是没有设计好,后续再改对使用者成本就很大了。

根据以上功能分析能够初步得以下出一些 API,咱们能够先把各个 API 先写到组件中(此部份内容属于新增,本文并无先把 API 写到组件中,Props 的maxHeight 本文也没有实现,大家能够本身考虑实现如下)

Props

属性名 说明 类型 是否必须 默认值
max 最多选择几项 Number 0
dataList 数据 Array []
maxHeight 控件最大高度 Number 300(px)
checkboxLeft 选框是否在左边 Boolean false

Events

事件名 说明 参数/返回值
on-change 点击肯定以后触发的事件 Object

Methods

方法名 说明
show 显示组件
hide 隐藏组件

0.3 初始化项目

再来分析一下 DOM 结构该怎么划分,这样有利于编码时的大局观

画的有点丑,手头没有什么好用的图片标注工具,用的 Mac 原生标注工具

通过上面的的分析,咱们就知道这个组件要作些什么了,接下来咱们就开始撸代码,首先咱们先把组件基本外观和架子作出来。

首先,咱们新建一个checklist.vue文件:

<template>
    <div class="cl-checklist">
      checklist
    </div>
</template>
<script> </script>
<style scoped> </style>
复制代码

为了便于开发时测试和观察,咱们还得建一个demo.vue文件,在demo.vue中引入咱们的checklist.vue组件:

<template>
  <div class="cl-div">
    <div class="center">checklist demo</div>
    <div>
      <input type="text" placeholder="请选择考场">
    </div>
    <checklist></checklist>
  </div>
</template>
<script> import checklist from '@components/checklist/checklist' export default { components: { checklist } } </script>
<style scoped> .center{ text-align: center; font-size: 18px; } </style>
复制代码

第一步:实现组件骨架和基础结构

1.1 实现基本骨架

咱们先把基本模块写出来,用背景颜色区分一下,写完后再把背景颜色去掉,我平常写页面都是这样,这样能够清晰的看到模块边界在哪里。 checklist.vue

<template>
  <div class="cl-checklist">
    <div class="topbar"></div>
    <div class="desc">您已选中0个,最多可选3个</div>
    <div class="list">
    </div>
  </div>
</template>
<script> </script>
<style scoped> .topbar{ height: 30px; background-color: #d0000e; } .desc{ padding: 10px 15px 0 0; font-size: 14px; text-align: right; color: #fff; background-color: #0d2e44; } .list{ height: 300px; background-color: #00b4ff; } </style>
复制代码

效果以下:

1.2 实现topbar

topbar有三个元素,如何选择布局方式呢?能够看出,取消、完成按钮是左右对齐,中间title是居中对齐的。咱们能够选择传统的浮动布局,使用三个div,好比叫:

<div class="cancel">取消</div>
<div class="title">选择考场</div>
<div class="confirm">肯定</div>
复制代码

这样须要给div宽度,给取消、肯定左右对齐,title居中对齐。NO!NO!NO!太麻烦了!咱们使用Flexbox布局,后面我都将使用Flexbox布局。来看看Flexbox如何轻松解决这个布局。

HTML:

<div class="topbar">
   <span class="cancel">取消</span>
   <span class="title">选择考场</span>
   <span class="confirm">完成</span>
</div>
复制代码

CSS:

.topbar{
    display: -webkit-flex;
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 45px;
    font-size: 16px;
    padding: 0 13px;
    border-bottom: 1px solid rgb(217,217,217);
}
.topbar .cancel{
    color: rgb(159,159,159);
}
.topbar .confirm{
    color: rgb(46,166,242);
}
复制代码

咱们使用justify-content: space-between让他们水平两端对齐,而后align-items: center垂直居中对齐,再给个左右padding便可。效果以下:

我在项目中用的是display:inline-block来布局,作的没如今的好,这种过后用文章的形式来复盘和输出可以让本身更清楚的认识怎么更好的去组织代码,这也是我坚持输出的缘由。

1.3 实现操做提示栏

这个比较简单,在这个部分直接一块儿给撸了吧。 HTML

<template>
  <div class="cl-checklist">
    <div class="topbar">
      <span class="cancel">取消</span>
      <span class="title">选择考场</span>
      <span class="confirm">完成</span>
    </div>
    <div class="desc">您已选中0个,最多可选3个</div>
    <div class="list">
    </div>
  </div>
</template>
复制代码

CSS

.desc{
  height: 30px;
  line-height: 30px;
  padding-right: 10px;
  font-size: 14px;
  text-align: right;
  color: rgb(159,159,159);
}
复制代码

效果以下:

第二步:实现list列表结构

2.1 实现基本骨架

咱们先来回顾如下第零部多DOM结构的分析:

能够看到咱们把DOM结构整体划分为左右结构,而后左边的又分为上下结构,左边的checkbox水平垂直居中

咱们先把结构基本勾勒出来 HTML:

<-- 省略上面的代码 -->
<div class="list">
  <div class="line">
     <div class="l">
        <div class="title">科目二第07考点马路</div>
        <div class="address">上海市宝山区保安公路2009号</div>
     </div>
     <div class="r"></div>
  </div>
</div>
<-- 省略下面的代码 -->
复制代码

CSS:

.list{
    height: 300px;
    font-size: 14px;
    padding: 10px 13px;
    background-color: #00b4ff;
  }
.list .line {
    display: -webkit-flex;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    background-color: #4caf50;
  }
.list .line .l{
    display: -webkit-flex;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    width: 90%;
    background-color: #d0000e;
  }
.list .line .r{
    width: 20px;
    height: 20px;
    background-color: #0d2e44;
}
复制代码

效果以下:

接下来就是完善了,以及右边的checkbox圆圈。注意,咱们不能给.line设死高度,这个高度应该由内容撑开,由于咱们要考虑没有地址信息的时候的展现。

2.2 实现checkbox选框

为了方便展现选中状态,咱们复制一行.line,设置这一行为选中状态,也就是加一个.selected的class,而后咱们对.selected写选中状态的样式

<div class="list">
      <div class="line">
        <div class="l">
          <div class="title">科目二第07考点马路</div>
          <div class="address">上海市宝山区保安公路2009号</div>
        </div>
        <div class="r"></div>
      </div>
      <div class="line selected">
        <div class="l">
          <div class="title">科目二第07考点马路</div>
          <div class="address">上海市宝山区保安公路2009号</div>
        </div>
        <div class="r"></div>
      </div>
</div>
复制代码

咱们继续CSS:

.list .line .r{
    width: 20px;
    height: 20px;
    margin: 0 5px;
    -webkit-border-radius: 50%;
    border-radius: 50%;
    border:1px solid #9e9e9e;
    background-color: #fff;
    position: relative;
    z-index: 0;
  }
 .list .line.selected .l .title{
    color: #1799fa;
  }
  .list .line.selected .r{
    border: 1px solid #1799fa;
    background-color: #1799fa;
  }
  .list .line.selected .r::before{
    content: ' ';
    position: absolute;
    top: 4px;
    left: 4px;
    width: 12px;
    height: 12px;
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAPCAYAAAALWoRrAAAA90lEQVQ4ja3TMSuFURgH8GeQRGIwy6BkUbIYlHwBu0Umi8VkMVlMJoMvIcNdDAYlJuULWCSESBSLwc9we/P2eu715t6nznKe//Or0zknEF1YS3jAHRa7Aa7iy0/ddAquVUC47ARcT8APLPwX3PC73jGPCPRiGSvoqwFuJuAb5opMoFFqnmCwDbiVgK+YLecCn5XQGYYScDsBXzBTzQb2k/A5hkvBnSTzhOnsRIEBHCdDFxjBbtJ7xFQGFmigH0fJ8HOyd4/JVmAZDc2bP0yQct1ioh1YRYvn1cg0XGP8LzBDC/igAl5hrA7YCg30YE/zl5xitC6I+AYJmBaJbbKurAAAAABJRU5ErkJggg==");
    background-repeat: no-repeat;
    background-size: contain;
    background-position: center center;
    z-index: 1;
}
复制代码

这里为了避免依赖图片,咱们把勾的图片编码成base64格式,同时咱们先把背景去掉,效果以下:

2.3 地址信息前面的小图标

这个小图标最好使用伪元素来实现

.list .line .l .address{
    color: rgb(159,159,159);
    position: relative;
    padding-left: 15px;
  }
  .list .line .l .address::before{
    content: ' ';
    display: inline-block;
    position: absolute;
    width: 15px;
    height: 15px;
    top: 2px;
    left: 0;
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAABu0lEQVRIiaXUzU8TQRjH8U9fpAkXIZqItHghxEgietuD/gP84R420QSDcCDxIq6KaCoJYCuaeJhdu93OlILfpEnnmWd++7zMM63d3V0RuljHQ9xFr7SP8QNfUOBPlmUzB5tsYBtLkb0eHpS/JzjEcd2hs7W1Vf1v4TkeoxMLu0EHa0VRLBdFcTIYDEC75vCsjO6mbGCnWlSCg1uKVTzK83xAqGFHqFmMX3iP7+X6HjbF67ud5/nnLvomXaxziVcY1WxDfMQLLDf8e+i3sZaIbq8hVjHC28SZtTZWIhs/TdKM8S3xsZW2eLrjOWIVMcFeO2Ik1Kc1R6xltoYI1+Y8Yl8Sxi7Funinz9s4SRzaEa/vKp4mzpx0hSHfjGzewUt8EpoA98voUuUoujgT7tdqxKEl3NN+QqDOMMuys6opRwscuI4jJrP8VYjytgxLjanX5uA/BA+qh7YuOBQadFOKLMv+Zde82Ae4uoHYlUZmTcGx8KwvymGWZVNjGhu9DzhdQOy09J0iNct75qd+VfrMkBIcYX+O4L74a5MUJHT8OGI/Nuc2zBOEd7iorS9KW5LrBH/jjZDeCK9LW5K/QatiGcsSFOsAAAAASUVORK5CYII=");
    background-repeat: no-repeat;
    background-size: contain;
    background-position: 0;
}
复制代码

2.4 适配移动端1像素边框

实现适配移动端1px边框,主要是根据设备的dpr来对边框进行缩放处理,CSS写法以下:

.border-1px{
    position: relative;
}
.border-1px::after{
    display: block;
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    border-bottom: 1px solid rgb(217,217,217);
    content: ' ';
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5) {
    .border-1px::after {
      -webkit-transform: scaleY(0.7);
      transform: scaleY(0.7);
    }
}
@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
    .border-1px::after {
      -webkit-transform: scaleY(0.5);
      transform: scaleY(0.5);
    }
}
复制代码

而后咱们在须要的地方设置.border-1px的样式

<div class="list">
      <div class="line border-1px">
        <div class="l">
          <div class="title">科目二第07考点马路</div>
          <div class="address">上海市宝山区保安公路2009号</div>
        </div>
        <div class="r"></div>
      </div>
      <div class="line border-1px selected">
        <div class="l">
          <div class="title">科目二第07考点马路</div>
          <div class="address">上海市宝山区保安公路2009号</div>
        </div>
        <div class="r"></div>
      </div>
</div>
复制代码

咱们在每一行的.line元素上添加border-1px,效果以下:

更多移动端1像素边框问题能够参看《移动端1像素边框问题》

2.5 列表滚动

实现滚动很简单,只要给父级元素也就是咱们代码中的.list元素设置一个高度,在这里咱们的数据多少不必定,因此咱们最好只设置一个最大高度max-height便可,同时须要给最外层DIV也就是.cl-checklist设置overflow:hidden。咱们先复制不少行来进行测试。 CSS:

.cl-checklist{
    overflow: hidden;
}
.list{
    /*height: 300px;*/
    max-height: 300px;
    font-size: 14px;
    padding: 10px 13px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch; 
    overflow-scrolling: touch;
    /*background-color: #00A2E6;*/
}
复制代码

注意overflow-scrolling: touch;属性,设置该属性是为了适配在移动端下滚动不平滑的问题,如今的效果以下:

第三步:实现选择CheckBox的交互功能

3.1 实现原理与v-model

这一步是整个组件的一个核心也是重点难点,这一步写的好、写的巧就会对后面的逻辑交互简化不少。咱们借助HTML的原生功能特性来实现:

<label for="xxx"><input id="xxx" type="checkbox" value=""></label>
复制代码

这个标签组合能够实现点击<label>包裹起来的范围的时候触发checkbox,根据这个原理咱们改造一下HTML代码

<div class="desc">您已选中 <span>{{checkboxValue.length}}</span> 个,最多可选<span>3</span></div>
    <div class="list">
      <div class="line-wrapper">
        <label for="1" class="line border-1px">
          <div class="l">
            <div class="title">科目二第07考点马路</div>
            <div class="address">上海市宝山区保安公路2009号</div>
          </div>
          <div class="r"></div>
        </label>
        <input type="checkbox" id="1" v-model="checkboxValue" style="display:none" value="1">
      </div>
      <div class="line-wrapper">
        <label for="2" class="line border-1px">
          <div class="l">
            <div class="title">科目二第07考点马路</div>
            <div class="address">上海市宝山区保安公路2009号</div>
          </div>
          <div class="r"></div>
        </label>
        <input type="checkbox" id="2" v-model="checkboxValue" style="display:none" value="2">
      </div>
      <div class="line-wrapper">
        <label for="3" class="line border-1px">
          <div class="l">
            <div class="title">科目二第07考点马路</div>
            <div class="address">上海市宝山区保安公路2009号</div>
          </div>
          <div class="r"></div>
        </label>
        <input type="checkbox" id="3" v-model="checkboxValue" style="display:none" value="3">
      </div>
</div>
复制代码

主要是把div.line的元素变成<label>元素,而后在外面再包裹一个div.line-wrapper,在<label>后面加一个checkbox标签。同时让checkbox不可见咱们能够给checkbox设置display:none或者给外围的div.line-wrapper设置overflow:hidden均可以,这里我使用display:none

Vue.js 提供了 v-model 指令,用于在表单类元素上双向绑定数据,例如在输入框上使用时,输入的内容会实时映射到绑定的数据上。单选按钮在单独使用时不须要 v-model ,直接使用 v-bind 绑定一个布尔类型的值,为真时选中,为否时不选中,若是是组合使用来实现互斥效果时就须要 v-model 配合 value 来使用。

这里给checkbox用 v-model 指令绑定了一个checkboxValue,这个值必须是一个数组,而后Vue.js 会帮咱们自动每次变动数组:

export default {
    data () {
      return {
        checkboxValue: []
      }
    }
}
复制代码

在操做提示栏里咱们给当前选择了几个设置成了checkboxValue的长度,这样以后咱们来试试,能够发现每次选择一个都会往数组中push一次,再次点击则会从数组中移除。效果以下:

3.2 加个选中样式

如今是能够选中了,可是如何给选中的项加上选中的CSS呢,想来想去也是个麻烦事,不过得借助JS来实现了,不知道广大网友有没有牛逼方法。

咱们在checkbox标签上绑定一个事件selectedItem

<div class="line-wrapper">
   <label for="1" class="line border-1px">
       <div class="l">
            <div class="title">科目二第07考点马路</div>
            <div class="address">上海市宝山区保安公路2009号</div>
       </div>
       <div class="r"></div>
    </label>
    <input type="checkbox" id="1" @click="selectedItem($event)" v-model="checkboxValue" style="display:none" value="1">
</div>
复制代码
methods: {
     selectedItem (event) {
        const labelNode = event.target.previousElementSibling
        const classList = labelNode.classList
        classList.contains('selected') ? classList.remove('selected') : classList.add('selected')
    }
}
复制代码

点击的时候获取Event事件对象,而后经过previousElementSibling找到上一个兄弟节点,给他绑定.selectedclass便可

3.3 最多可选择几项

3.3.1 使用 props 传递数据

用户是能够设置最多选择几项的,因而 Vue.js 的 Props 功能派上用场了。在 Vue.js 组件中,使用选项 Props 来声明须要从父级接收的数据,props 的值能够是两种,一种是字符串,一种是对象,这里咱们使用对象,一个Number对象。

先定义 props

props: {
   max: {
       type: Number,
       default: 0
    }
 }
复制代码

咱们定义了一个max属性,它的类型是Number类型,默认值是 0 。而后咱们把max加到操做提示中

<div class="desc">您已选中 <span>{{checkboxValue.length}}</span> 个,最多可选<span>{{max}}</span></div>
复制代码

而后咱们就能够给组件传递max属性了,如今转到demo.vue文件:

<template>
  <div class="cl-div">
    <div class="center">checklist demo</div>
    <div>
      <input type="text" placeholder="请选择考场">
    </div>
    <checklist :max="2"></checklist>
  </div>
</template>
复制代码

咱们给max设置为2,也就是最多选择2个。

3.3.2 Watch选项、$refs的使用

当咱们选择了两个的时候其余的选择项就应该灰掉(禁用),那么就要监控 data 选项里的checkboxValue的长度了,这时候咱们须要用到Vue.js的 watch 选项,watch 是一个对象,键是须要观察的表达式,值是对应回调函数。值也能够是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用$watch(),遍历 watch 对象的每个属性。

注意,不该该使用箭头函数来定义 watcher 函数 (例如searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级做用域的上下文,因此 this 将不会按照指望指向 Vue 实例,this.updateAutocomplete 将是 undefined

咱们经过监听 data 选项里的checkboxValue,来判断它的长度,若是它的长度恰好已经和设置的max属性相等了,就给其余添加.disabled这个class,同事给input checkbox添加disabled属性。

watch: {
   checkboxValue (val) {
      const listDom = this.$refs['list']
      const lines = listDom.querySelectorAll('line-wrapper')
      if (val.length === this.max) {
        let item = null
        for (let i = 0; i < lines.length; i++) {
          item =lines[i]
          if (val.indexOf(lines[i].dataset.val) === -1) {
            item.children[0].classList.add('disabled')
            item.querySelector('input[type="checkbox"]').setAttribute('disabled', 'disabled')
          }
      }
   } else {
        let item = null
        for (let i = 0; i <lines.length; i++) {
          item =lines[i]
          if (item.children[0].classList.contains('disabled')) {
            item.children[0].classList.remove('disabled')
            item.querySelector('input[type="checkbox"]').removeAttribute('disabled')
          }
        }
      }
   }
}
复制代码

这个须要配合Vue.js 的 $refs来作,在HTML中的.list节点上设置ref = 'list',也就是为了方便选择这个DOM节点,固然你用传统的document.querySelector来选择.list节点也是没问题的。

<div class="list" ref="list">
复制代码

上面代码的第9行,判断是否当前DOM是否在选中的数组中,拿的是checkboxValue的数组项和一个自定义属性值比较,这个自定义属性叫data-val,他的值跟input checkbox的 value 值保持一致,这个val自定义属性设置在.list-wrapper节点上是为了方便DOM查找,减小DOM查找层数,否则就须要获取input checkbox的 value 值来比较。

设置.disabled的CSS以下:

.list .line.disabled .l .title{
    color: #9e9e9e;
}
.list .line.disabled .r{
    border: 1px solid #9e9e9e;
    background-color: #9e9e9e;
}
复制代码

第四步:组件显示隐藏

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-forindex,固然了,也能够用item.labelitem.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 便可

还能够作点什么呢?

你们能够扩展一下...


完整源码

相关文章
相关标签/搜索