建議在電腦上閱讀此文,所有源代碼在文章最後css
2017.11.30更新:本案例有了更優雅更簡單的實現方案了,能夠比較看一下兩種實現方式,具體請看文末源碼裏的checklist2.0.vue文件html
2017.11.25更新章節:0.3 規劃APIvue
本文教你如何寫一個移動端的 Checklist 組件,使用 vue 單文件形式開發,適合 Vue.js 新手。同時此文很是長,最好跟着文章步驟邊看邊寫。本文說些什麼,或者你能收穫什麼?。git
前置知識
什麼是Checklist
第零步:分析與準備github
3.3 最多可選擇幾項app
閱讀此文前您最好有如下知識的基礎:
.vue
單文件和 Vue.js 組件知識有基本的認識什麼是Checklist組件?咱們先來看一下市面上已經有的UI框架的Checklist長什麼樣
weui | Mint UI |
---|---|
![]() |
![]() |
而這種組件的一個典型場景是移動端的購物車列表,打開你的京東、淘寶購物車看看,功能是否是很像呢?
本文寫一個什麼樣的 Checklist 組件?這個組件來自於我司真實項目,剛開始我也是用的 Mint-UI 來作,後來業務升級需求變動 Mint-UI 就不適合了,因而我就本身擼了一個,特整理出來此文,而且咱們儘可能把他作的通用、靈活一點。咱們的 Checklist 以下:
咱們的 | 京東購物車列表 |
---|---|
![]() |
![]() |
在動手擼代碼以前,咱們先來仔細分析一下業務需求和功能點,這個組件是展現考場和考場地址,考場地址沒有就不顯示,最多選三個,選了三個後其餘的要禁用等,數據是根據考試科目和所在城市動態獲取,當列表數據不少咱們還得給它一個最大高度讓列表可滾動等,從圖中得出如下功能點:
等等....
一個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 | 隱藏組件 |
再來分析一下 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>
咱們先把基本模塊寫出來,用背景顏色區分一下,寫完後再把背景顏色去掉,我平常寫頁面都是這樣,這樣能夠清晰的看到模塊邊界在哪裏。
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>
效果以下:
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
來佈局,作的沒如今的好,這種過後用文章的形式來複盤和輸出可以讓本身更清楚的認識怎麼更好的去組織代碼,這也是我堅持輸出的緣由。
這個比較簡單,在這個部分直接一塊兒給擼了吧。
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); }
效果以下:
咱們先來回顧如下第零部多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
設死高度,這個高度應該由內容撐開,由於咱們要考慮沒有地址信息的時候的展現。
爲了方便展現選中狀態,咱們複製一行.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格式,同時咱們先把背景去掉,效果以下:
這個小圖標最好使用僞元素來實現
.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; }
實現適配移動端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像素邊框問題》
實現滾動很簡單,只要給父級元素也就是咱們代碼中的.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;
屬性,設置該屬性是爲了適配在移動端下滾動不平滑的問題,如今的效果以下:
這一步是整個組件的一個核心也是重點難點,這一步寫的好、寫的巧就會對後面的邏輯交互簡化不少。咱們藉助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一次,再次點擊則會從數組中移除。效果以下:
如今是能夠選中了,可是如何給選中的項加上選中的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
找到上一個兄弟節點,給他綁定.selected
class便可
用戶是能夠設置最多選擇幾項的,因而 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個。
當咱們選擇了兩個的時候其餘的選擇項就應該灰掉(禁用),那麼就要監控 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; }
這一步咱們要作一下的組件的顯示與隱藏,點擊輸入框從頁面底部顯示組件,點擊取消或者蒙層從上到下隱藏組件,而且添加過渡動畫。這其實使用定位和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>
如今的效果以下:
作好了顯示那隱藏就很簡單了,點擊取消隱藏組件,動畫會原路返回,只須要爲取消設置一下isOpen = false
或者調用hide
方法便可。
<div class="topbar"> <span class="cancel" @click="hide">取消</span> <span class="title">選擇考場</span> <span class="confirm">完成</span> </div>
如今效果以下
爲了使蒙層可以覆蓋整個頁面,還不得不爲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
前面咱們的數據都是寫死的,如今咱們來動態渲染數據,也就是循環數據了。從父組件傳遞數據,在子組件中接收,還得使用 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
,但不推薦這樣作。
最後的最後,就是該處理點擊「完成」後把選中的值傳遞給父頁面了。咱們已經知道從父組件向子組件通訊,經過 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 便可
你們能夠擴展一下...