1 效果展示
2 组件源码
<template>
<div class="page">
<div
class="mask"
@click="close"
/>
<div :class="['cascader', 'animation', isVisible ? 'moveInBottom' : 'moveOutBottom']">
<div class="title">
{{ title }}
<i
v-if="leftIcon"
:class="['back', {'arrow': leftIcon === 'arrow', 'cross': leftIcon === 'cross'}]"
@click="close"
/>
</div>
<ul class="nav-list">
<template v-for="(item, index) in (selectedOptions.length + 1)">
<li
:key="index"
:class="['nav-list-item', {'active': index === tabIndex, 'more-hidden': moreHidden}]"
@click="changeIndex(index)"
>
{{ (selectedOptions[index] && selectedOptions[index][fieldNames.text]) || placeholder }}
</li>
</template>
</ul>
<div
class="select-content"
:style="styleObj"
>
<ol
class="select-list"
v-for="(item1, index1) in pickerDataArr" :key="index1"
ref="selectListRefs"
>
<template v-for="(item2, index2) in item1">
<li
:key="index2"
class="select-list-item"
@click="select(item2)"
>
<span class="text">{{ item2[fieldNames.text] }}</span>
<i
v-if="selectedOptions.includes(item2)"
class="choose-active"
/>
</li>
</template>
</ol>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'cascaderPicker',
props: {
options: {
type: Array,
default: () => []
},
title: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请选择'
},
moreHidden: {
type: Boolean,
default: false
},
level: {
type: Number,
default: 3
},
fieldNames: {
type: Object,
default: () => ({
text: 'text',
value: 'value',
chlidren: 'children'
})
},
leftIcon: {
type: String,
default: 'arrow'
},
},
data() {
return {
tabIndex: 0,
selectedOptions: [],
pickerDataArr: [],
isVisible: false,
styleObj: {
width: '',
transform: '',
transitionDuration: '0.3s'
}
}
},
created () {
this.pickerDataArr.push(this.options)
this.isVisible = true
},
mounted () {
this.$nextTick(() => {
this.styleObj.width = this.level * this.$refs['selectListRefs'][0].offsetWidth + 'px'
})
},
watch: {
tabIndex () {
this.styleObj.transform = `translateX(-${this.tabIndex * this.$refs['selectListRefs'][0].offsetWidth}px)`
}
},
methods: {
changeIndex (index) {
this.tabIndex = index
},
select (item) {
this.selectedOptions.splice(this.tabIndex, this.selectedOptions.length, item)
if (!(item[this.fieldNames.children] && item[this.fieldNames.children].length) || this.tabIndex + 1 >= this.level) {
this.isVisible = false
setTimeout(() => {
this.$emit('finish', this.selectedOptions)
}, 300)
return
}
this.$refs['selectListRefs'].forEach((ele, index) => {
if (index > this.tabIndex) {
ele.scrollTop = 0
}
})
this.tabIndex++
this.pickerDataArr.splice(this.tabIndex, this.pickerDataArr.length, item[this.fieldNames.children])
},
close () {
this.isVisible = false
setTimeout(() => {
this.$emit('close')
}, 300)
}
}
}
</script>
<style lang="less" scoped>
.page{
height: 100%;
}
.mask{
position: fixed;
right: 0;
left: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .7);
z-index: 99;
}
.cascader{
position: fixed;
right: 0;
left: 0;
bottom: 0;
height: 60%;
background-color: #fff;
border-radius: 16px 16px 0 0;
display: flex;
flex-direction: column;
z-index: 999;
.title{
position: relative;
padding: 20px 0;
font-size: 18px;
line-height: 18px;
text-align: center;
.back{
position: absolute;
top: 19px;
left: 20px;
width: 20px;
height: 20px;
}
.arrow{
background: url('./images/icon-arrow.png') no-repeat;
background-size: 100%;
}
.cross{
background: url('./images/icon-close.png') no-repeat;
background-size: 100%;
}
}
.nav-list{
display: flex;
padding: 0 20px;
font-size: 16px;
text-align: center;
.nav-list-item{
padding: 14px 0;
margin-right: 16px;
max-width: 64px;
}
.nav-list-item:last-child{
margin-right: 0;
}
.active{
position: relative;
color: #1752ff;
}
.active::after{
position: absolute;
left: 0;
bottom: 0;
display: block;
content: '';
width: 100%;
height: 3px;
background-color: #1752ff;
}
.more-hidden{
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.select-content{
flex: 1;
overflow: hidden;
display: flex;
.select-list{
height: 100%;
width: 100vw;
overflow-y: auto;
font-size: 16px;
.select-list-item{
padding: 12px 20px;
display: flex;
align-items: center;
justify-content: space-between;
.text{
max-width: 264px;
}
.choose-active{
width: 20px;
height: 20px;
background: url('./images/icon-choose.png') no-repeat;
background-size: 100%;
}
}
}
.line{
position: relative;
}
.line::after{
position: absolute;
left: 0;
bottom: 0;
display: block;
content: '';
width: 100%;
height: 1px;
background-color: #ccc;
transform: scaleY(0.5);
}
.select-list::-webkit-scrollbar{
display: none;
}
}
}
.animation{
animation-duration: 0.3s;
animation-fill-mode: forwards;
}
.moveInBottom{
animation-name: moveInBottom;
}
.moveOutBottom{
animation-name: moveOutBottom;
}
@keyframes moveInBottom{
0%{
transform: translateY(100%);
opacity: 0;
}
100%{
transform: translateY(0);
opacity: 1;
}
}
@keyframes moveOutBottom{
0%{
transform: translateY(0);
opacity: 1;
}
100%{
transform: translateY(100%);
opacity: 0;
}
}
</style>
3 使用说明
- 可以参考 vue实现简易版级联选择(一),组件调用方式保持不变。