三级联动开发(非城市,类似美团)
工作要求,需要写一个三级联动,最终写的代码,效果如下
1.默认情况展示附近(无子列表),全部分类(有子列表),离我最近(无子列表),筛选(无子列表),
2.点击每个tabItem展示下拉框,如下图
3.点击元素反显在tab上,如果没有子列表弹窗收回
如果有子列表,点击每一项出现它对应的子列表
基本的情况就是这样,下面上代码,这个tabbar是根据mandmoblie的下拉组件改造的(dropMenu)
数据结构
defaultList: [
{
type: '附近',
firstList: [{ title: '附近',level: '1', }, { title: '500m',level: '1', }, { title: '1000m',level: '1', }, { title: '3000m',level: '1', }, { title: '5000m',level: '1', }],
},
{
type: '全部分类',
firstList: [
{
title: '旅游出差',
level: '1',
childList: [
{
title: '旅游',
level: '2',
childList: [
{
title: '去哪',
level: '3',
},
{
title: '飞猪',
level: '3',
},
],
},
{
title: '出差',
level: '2',
childList: [
{
title: '飞机',
level: '3',
},
{
title: '火箭',
level: '3',
},
],
},
],
},
{
title: '百货零售',
level: '1',
childList: [
{
title: '家装建材',
level: '2',
childList: [
{
title: '家装',
level: '3',
},
{
title: '建材',
level: '3',
},
],
},
{
title: '食品鲜花',
level: '2',
childList: [
{
title: '食品',
level: '3',
},
{
title: '鲜花',
level: '3',
},
],
},
{
title: '服饰箱包',
level: '2',
childList: [
{
title: '服饰',
level: '3',
},
{
title: '箱包',
level: '3',
},
],
},
],
},
],
},
{
type: '离我最近',
firstList: [{ title: '离我最近',level: '1', }, { title: '智能排序',level: '1', }, { title: '精品推荐',level: '1', }],
},
{
type: '筛选',
firstList: [{ title: '筛选',level: '1', }, { title: '优惠券',level: '1', }, { title: '优惠买单',level: '1', }],
},
],
最外面引用组件
<car-cascader :defaultList="defaultList" ref="cascaderItem"></car-cascader>
组件代码(下拉部分的代码)carCascader
<template>
<div class="md-drop-menu">
<!-- tabbar -->
<div class="md-drop-menu-bar">
<div
class="bar-item"
:class="{
active: isPopupShow[index],
}"
v-for="(item, index) in tabList"
:key="index"
@click="tabClick(item, index)"
>
<span>
{{ `${item.length > 5 ? item.slice(0, 5) + '...' : item}` }}
</span>
</div>
</div>
<!-- popup -->
<md-popup v-model="isPopupShow[activeTabIndex]" position="top" @show="$_onListShow" @hide="$_onListHide">
<div class="md-drop-menu-list">
<cascaderItem :defaultList="defaultList" :activeTabIndex="activeTabIndex" @passVal="passVal" />
</div>
</md-popup>
</div>
</template>
<script>
import Popup from '@mandmobile/popup';
import cascaderItem from './carCascader-item.vue';
export default {
name: 'carCascader',
props: {
defaultList: {
type: Array,
default() {
return [];
},
},
},
components: {
[Popup.name]: Popup,
cascaderItem,
},
data() {
return {
tabList: [],
isPopupShow: [], // popup是否展示
activeTabIndex: 0, // popup默认展示的索引
};
},
mounted() {
// console.log(this.defaultList,'defaultList')
this.infoIsPopupShow();
this.getTabList();
},
methods: {
passVal(clickItem) {
this.tabList = this.tabList.map((ite, ind) => (ind == this.activeTabIndex ? clickItem.title : ite));
if (!clickItem.childList) {
this.$set(this.isPopupShow, this.activeTabIndex, false);
}
},
// 获取tabList数组
getTabList() {
this.defaultList.forEach((gettabItem) => {
this.tabList.push(gettabItem.type);
});
},
// tab每一项点击
tabClick(item, index) {
if (!this.isPopupShow[index]) {
this.infoIsPopupShow();
// tab的每一项添加样式
this.$set(this.isPopupShow, index, true);
// 对应的popup展示
this.activeTabIndex = index;
} else {
this.$set(this.isPopupShow, index, false);
}
},
// popup是否展示
infoIsPopupShow() {
this.tabList.forEach((tabItem, tabindex) => {
this.$set(this.isPopupShow, tabindex, false); // 每一个tab都设成false
});
},
// popup展示
$_onListShow() {
this.$_setScroller();
this.$emit('show');
},
// popup隐藏
$_onListHide() {
this.$emit('hide');
},
$_setScroller() {
const boxer = this.$el ? this.$el.querySelector('.md-popup-box') : null;
if (boxer && boxer.clientHeight >= this.$el.clientHeight) {
this.scroller = '.md-drop-menu-list';
} else {
return '';
}
},
},
};
</script>
<style lang="stylus" scoped>
.md-drop-menu {
font-family: PingFangSC-Regular;
position: relative;
height: drop-menu-height;
box-sizing: border-box;
font-size: drop-menu-font-size;
font-weight: drop-menu-font-weight;
.md-drop-menu-bar {
position: relative;
display: flex;
height: 100%;
font-size: 24px !important;
font-weight: 700 !important;
background: #F4F4F4;
color: #443B50;
z-index: 2000;
.bar-item {
display: flex;
flex: 1;
margin: 0;
align-items: center;
justify-content: center;
span {
position: relative;
padding-right: 30px;
&:after {
content: '';
position: absolute;
right: 0;
top: calc(50% - 0.07rem);
width: 0.24rem;
height: 0.14rem;
background: url('./assets/images/arrow1.png') no-repeat;
background-size: contain;
transition: transform 0.3s ease-in-out-quint;
}
}
&.active {
color: #904EF4;
span:after {
transform: rotate(180deg);
}
}
}
}
.md-popup {
top: drop-menu-height;
right: 0;
bottom: 0;
left: 0;
position: absolute;
height: 100vh;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
pointer-events: none;
.md-drop-menu-list {
width: 100%;
max-height: 500px;
background: drop-menu-list-bg;
box-sizing: border-box;
text-align: left;
border-radius: 0 0 16px 16px;
}
}
}
</style>
组件当中子组件的代码,carCascader-item.vue
<template>
<div class="cascader-list">
<div v-for="(item, index) in newArr" :key="index" id="listBox">
<div
v-for="(subItem, subIndex) in item"
:key="subIndex"
@click="subItemClick(subItem, subIndex)"
class="item"
:class="[{ clickedColor: subItem.isShowChild, clickedBackground: subItem.isShowChildBack }]"
>
{{ subItem.title }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
defaultList: {
type: Array,
default() {
return [];
},
},
activeTabIndex: {
type: Number,
default: -1,
},
},
data() {
return {
newArr: [], // 初始数据处理
firstFloor: [], // 第一层的数据
secondFloor: [], // 第二层的数据
thirdFloor: [], // 第三层的数据
isShowChild: false, // 是否展示字体颜色
isShowChildBack: false, // 是否显示背景色
saveSecondFloor:[], // 存储第二层数组
saveThirdFloor:[],// 存储第三层数组
};
},
watch: {
// 监听索引,当索引改变,重新获取列表,并且将之前的列表清空
activeTabIndex: function (newval, oldval) {
this.activeTabIndex = newval;
this.newArr = [];
this.handleDefaultList();
if(this.activeTabIndex == 1){
this.newArr.push(this.secondFloor,this.thirdFloor)
}
},
},
mounted() {
this.handleDefaultList();
},
methods: {
// 处理初始数据
handleDefaultList() {
this.firstFloor = this.defaultList[this.activeTabIndex].firstList;
this.newArr.push(this.firstFloor);
},
// 点击事件
subItemClick(subItem, subIndex) {
this.$emit('passVal',subItem)
// 点击添加样式
if (subItem.hasOwnProperty('isShowChild')) {
subItem.isShowChild = true;
subItem.isShowChildBack = true;
} else {
this.$set(subItem, 'isShowChild', true);
this.$set(subItem, 'isShowChildBack', true);
}
// 点击同级其他同级元素样式去掉
if (subItem.isShowChild || subItem.isShowChildBack) {
let handleArr = [];
// 判断等级
if (subItem.level == 1) {
// 如果等级是1,切掉二三级
this.newArr.splice(1, 2);
handleArr = this.firstFloor;
} else if (subItem.level == 2) {
// 如果等级是2,切掉第三级
this.newArr.splice(2, 1);
handleArr = this.secondFloor;
} else {
handleArr = this.thirdFloor;
}
// 同级样式去重
handleArr.forEach((fitem, findex) => {
const isSameColor = fitem.hasOwnProperty('isShowChild') && fitem.isShowChild && subIndex !== findex;
const isSameBack = fitem.hasOwnProperty('isShowChildBack') && fitem.isShowChildBack && subIndex !== findex;
if (isSameColor) {
fitem.isShowChild = false;
}
if (isSameBack) {
fitem.isShowChildBack = false;
}
});
}
// 判断等级并且判断是否有子集
if (subItem.level == 1 && subItem.childList) {
// 父元素下的子元素和父元素背景色相同
subItem.childList.forEach((item) => {
this.$set(item, 'isShowChildBack', true);
});
this.secondFloor = subItem.childList;
this.newArr.push(this.secondFloor);
}
if (subItem.level == 2 && subItem.childList) {
subItem.childList.forEach((item) => {
this.$set(item, 'isShowChildBack', true);
});
this.thirdFloor = subItem.childList;
this.newArr.push(this.thirdFloor);
}
},
},
};
</script>
<style lang="stylus" scoped>
.cascader-list {
overflow: hidden;
overflow-y: scroll;
max-height: 500px;
display: flex;
font-size: 24px;
color: #655A72;
justify-content: flex-start;
#listBox {
width: 100%;
}
.item {
height: 100px;
line-height: 100px;
padding-left: 40px;
}
.clickedColor {
color: #904ef4;
}
.clickedBackground {
background: #f5f5f7;
}
}
</style>