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 便可

還能夠作點什麼呢?

你們能夠擴展一下...


完整源碼

相關文章
相關標籤/搜索