今天我們來手寫一個Cascader
,也就是層聯(lián)選擇器,一般長下面這樣,通過點擊輸入框出來一個面板捷凄,可以層層選擇數(shù)據(jù),再次點擊輸入框或者外面围来,輸入框會消失跺涤。
我們先準備一份數(shù)據(jù)
options: [{
value: 'zhinan',
label: '數(shù)碼',
children: [{
value: 'shejiyuanze',
label: '手機',
children: [{
value: 'yizhi',
label: '蘋果'
}, {
value: 'fankui',
label: '華為'
}, {
value: 'xiaolv',
label: '三星'
}, {
value: 'kekong',
label: '小米'
}]
}, {
value: '',
label: '筆記本電腦',
children: [{
value: 'cexiangdaohang',
label: 'Macbook Pro'
}, {
value: 'dingbudaohang',
label: 'iMac'
}]
}]
}, {
value: 'zujian',
label: '家電',
children: [{
value: 'basic',
label: '空調(diào)',
children: [{
value: 'layout',
label: '格力'
}, {
value: 'color',
label: '美的'
}]
},
{
value: 'basic',
label: '洗衣機',
children: [{
value: 'layout',
label: '西門子'
}, {
value: 'color',
label: '松下'
}]
},]
}
]
然后創(chuàng)建一個Cascader.vue
文件
<template>
<div class="container" v-click-outside="close">
<div class="input" @click="toggle"></div>
<div class="content" v-if="visiable">
<div class="content-left">
<div v-for="(item,index) in options" :key="index">
<div @click="select(item)">{{item.label}}</div>
</div>
</div>
<div class="content-right" v-if="list.length>0">
<div v-for="(item,index) in list" :key="index">
<div>{{item.label}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import clickOutside from "../../directives/outside";
export default {
props: {
options: {
type: Array,
default: () => []
}
},
data() {
return {
visiable: false,
selected: []
};
},
computed: {
list() {
return this.selected.children ? this.selected.children : [];
}
},
methods: {
toggle() {
this.visiable = !this.visiable;
},
select(item) {
console.log(item);
this.selected = item;
},
close() {
this.visiable = false;
}
},
directives: {
clickOutside: clickOutside
},
mounted() {}
};
</script>
可以看到匈睁,我們通過一個props
來接收要展示的數(shù)據(jù),用v-for
循環(huán)展示這些數(shù)據(jù)桶错,通過計算屬性計算出點擊左側(cè)面板后右側(cè)面板要顯示的數(shù)據(jù)航唆,通過visiable
屬性搭配v-if
指令控制面板的顯示和隱藏,同時這里我們使用一個自定義指令v-click-outside
來實現(xiàn)點擊面板外面關(guān)閉面板,代碼如下
const clickOutside = {
inserted: function (el, binding) {
function hide(e) {
if (el === e.target || el.contains(e.target)) {
return
}
binding.value()
}
el._hide = hide
document.addEventListener('click', el._hide)
},
unbind(el) {
document.removeEventListener(el._hide)
}
}
export default clickOutside
我們在頁面組件引入Cascader
,將數(shù)據(jù)傳給他,這時頁面長這樣
我們點擊數(shù)碼
院刁,出現(xiàn)手機 筆記本電腦
這沒毛病糯钙,但我們點擊手機
的時候,并沒有出現(xiàn)下一層退腥。任岸。。因為我們的dom結(jié)構(gòu)只寫了兩個div狡刘,也就是class
名為content-left
和content-right
的這兩個享潜,想要顯示第三層的話,就得再增加div嗅蔬,但你們只看到了第二層剑按,而我看到了第五層。事實上我們不知道到底有多少層澜术,所以我們得用Vue
的遞歸組件艺蝴,我們再創(chuàng)建一個CascaderItem.vue
的文件,來循環(huán)右側(cè)數(shù)據(jù)
<template>
<div class="panel">
<div class="content-left">
<div v-for="(item,index) in options" :key="index">
<div @click="select(item,index)" :class="{'selected':selectIndex==index}">{{item.label}}</div>
</div>
</div>
<div class="content-right" v-if="list.length>0">
<CascaderItem :options='list'></CascaderItem>
</div>
</div>
</template>
<script>
export default {
name:'CascaderItem',
props: {
options: {
type: Array,
default: () => []
}
},
data() {
return {
selected: [],
selectIndex:null
};
},
computed: {
list() {
return this.selected.children ? this.selected.children : [];
}
},
methods: {
select(item,index) {
console.log(item);
this.selected = item;
this.selectIndex = index
},
},
mounted() {}
};
</script>
好了 現(xiàn)在我們好像能展示多層數(shù)據(jù)了瘪板,但這時我們發(fā)現(xiàn)一個問題
我們這時點擊家電吴趴,雖然第二層跟著變了,但第三層沒有變侮攀。因為目前的寫法第三層數(shù)據(jù)是由第二層數(shù)據(jù)點擊產(chǎn)生的锣枝。那么如何解決這個問題呢?我們用在
Cascader
文件中聲明一個數(shù)組 selectData
來存放我們選中的數(shù)據(jù)兰英,為此我們還需要聲明一個變量level
來表示當前層數(shù)
Cascader.vue
<template>
<div class="container" v-click-outside="close">
<div class="input" @click="toggle">{{inputVal}}</div>
<div class="content" v-if="visiable">
<CascaderItem :options='options' :selectData='selectData' :level='0' @change='change'></CascaderItem>
</div>
</div>
</template>
<script>
export default {
...
data() {
return {
...
selectData:[]
};
},
...
computed: {
inputVal(){
return this.selectData.map(item=>item.label).join('/')
}
},
methods: {
change(value,index){
this.selectData[index] = value
this.selectData.splice(index,1,value) //觸發(fā)更新
},
},
};
</script>
CascaderItem.vue
<template>
<div class="panel">
<div class="content-left">
<div v-for="(item,index) in options" :key="index">
<div @click="select(item,index)" :class="{'selected':selected==item}">{{item.label}}</div>
</div>
</div>
<div class="content-right" v-if="list && list.length>0">
<CascaderItem :options='list' :selectData='selectData' :level='level+1' @change='change' ></CascaderItem>
</div>
</div>
</template>
<script>
export default {
..
props: {
..
selectData: {
type: Array,
default: () => []
},
level:{
type:Number
}
},
computed: {
list() {
return this.selectData[this.level] && this.selectData[this.level].children
}
},
methods: {
select(item,index) {
this.selected = item;
this.selectIndex = index
this.selectData.splice(this.level+1)
this.$emit('change',item,this.level)
},
change(item,index){
this.$emit('change',item,index)
}
},
};
</script>
可以看到撇叁,點擊的時候我們把selectData
下一層之后的數(shù)據(jù)都刪除了,這就符合了我們的邏輯畦贸,我們的右側(cè)數(shù)據(jù)此時就可以通過selectData
和level
來得到了陨闹。這里我們要主要的是我們需要將level
傳給遞歸組件,并且+1
,因為每遞歸一次薄坏,他的層數(shù)就會加一趋厉,此外,我們需要監(jiān)聽遞歸組件emit
出來的事件胶坠,然后再傳遞出去君账。
我們再稍作修改,將選擇的數(shù)據(jù)傳遞出去
props:{
...
value: {
type: Array,
default: () => []
}
}
change(){
...
let selectArr = this.selectData.map(item => item.value);
console.log(selectArr);
this.$emit("input", selectArr);
}
這時沈善,我們的靜態(tài)層聯(lián)基本就完成了
接下來我們來寫異步的乡数。椭蹄。