一、上下布局,点击/滑动切换
功能需求
页面上方循环获取标签信息,超出屏幕宽度时可滑动,点击标签时,下方数据随标签切换,当前选中标签样式改变;页面下方可左右滑动切换,当前展示页面数据与上方标签相对应,要求当前页面高度根据获取数据列表长度改变,当前页面无数据时,按要求展示无数据提示。
需求分析
超出屏幕宽度可滑动--使用<scroll-view>标签,标签内循环<div>展示数据(注:使用div绑定ref,使点击盒子一直显示在页面中);每个循环的标签添加点击事件,盒子index赋值给currentIndex,传入对应数据;下方页面可左右滑动--使用<swiper>标签,去掉自动滚动、分页点,swiper高度随着传入数据多少变化,且选中标签样式改变,判断当选中标签接口中不存在数据时,展示无数据盒子。
实现代码
template
<div class="tabBox">
<!--滚动条横向滚动,超过不折行-->
<scroll-view scroll-x="true" style="white-space: nowrap;">
<!--当前选中标签样式改变-->
<div ref="tab" @click="itemClick(index)" class="tab-item" v-for="(item, index) of tabList" :key="index":class="currentIndex == index ? 'chosen-item' : '' ">
{{ item.title }}
</div>
</scroll-view>
</div>
//页面下部分滑动用使用swiper
<swiper :indicator-dots="dot" :autoplay="auto" @change="slide" class="totalSwiper" :style="{height: swiperHeight+'px'}" id="swiper":current="currentIndex">
//根据标签个数循环swiper滑块个数
<swiper-item v-for="(item,index) in tabList" :key="index">
//根据当前标签下是否有数据决定swiper状态
<view class="signalSwiper" v-if="ifData">
<view class="haveData">
<view class="dataBox"v-for="(item,index) in showList":key="index">
<view class="boxLeft">
//icon是动态赋值背景图
<view class="boxIcon" :style="
{backgroundImage:'url('+item.logo+')' }">
</view>
</view>
<view class="boxRight">
<view class="rightName">
<view class="boxTitle">{{item.title}}</view>
<view class="boxDate">{{item.createTime}}</view>
</view>
<view class="rightPrice">
<view class="boxNumber">{{item.account}}</view>
<view class="boxPrice">¥{{item.currentPrice/100}}
</view>
</view>
</view>
</view>
</view>
</view>
//该标签下无数据时展示该盒子
<view class="noDataSwiper" v-if="nohaveData">
<view class="noData">
<view class="failImg"></view>
<view class="failWord">暂无内容</view>
</view>
</view>
</swiper-item>
</swiper>
data
data() {
return {
//是否展示swiper轮播点
dot: false,
//swiper是否自动切换
auto: false,
//当前选中索引
currentIndex: 0,
//上个页面携带的选中标签类型
chooseIndex: 0,
//该标签下存在数据
ifData: false,
//该标签下不存在数据
nohaveData: true,
//用户ID
userId: null,
//当前选中的订单状态
statusIndex: 0,
//当前订单状态
tabList: [{
title: '全部',
status: ''
}, {
title: '未付款',
status: 0
}, {
title: '已支付',
status: 1
},{
title: '支付失败',
status: -1
}, {
title: '已完成',
status: 2
}],
//展示当前页面优惠券信息
showList: [],
//swiper的高度
swiperHeight: null,
}
}
onLoad()
onLoad(option) {
//上一页携带跳转参数,判断要展示的订单状态
this.chooseIndex = parseInt(option.which)
},
onReady()
//动态获取该页swiper的高度,实时更新
this.$nextTick(() => {
setTimeout(() => {
//首先根据上页携带参数选择展示标签以及对应数据
this.itemClick(this.chooseIndex)
//根据当前内容高度动态赋值
this.getHeight()
}, 300);
});
methods
//获取页面高度
getHeight() {
if (this.ifData == true) { //如果该标签页有对应数据
//当前swiper页面高度等于选中盒子内数据的高度
this.$nextTick(() => {
let infos = uni
.createSelectorQuery()
.in(this)
.select('.signalSwiper');
infos.boundingClientRect(data => {
this.swiperHeight = data.height
}).exec();
})
} else if (this.nohaveData == true) { //如果该标签页没有对应数据
this.$nextTick(() => {
let infos = uni
.createSelectorQuery()
.in(this)
.select('.noDataSwiper');
infos.boundingClientRect(data => {
this.swiperHeight = window.innerHeight
}).exec();
})
}
},
// 点击哪个让哪个出现在视野中
itemClick(index) {
this.currentIndex = index
this.$refs.tab[this.currentIndex].scrollIntoView({
block: 'nearest',
inline: 'center'
})
this.showStatus(this.currentIndex)
},
slide(e) {
this.currentIndex = e.detail.current
this.$refs.tab[this.currentIndex].scrollIntoView({
block: 'nearest',
inline: 'center'
})
this.showStatus(this.currentIndex)
},
//获取当前选择的订单状态展示
showStatus(index) {
if (index == 0) {
this.statusIndex = ''
this.getDataList()
} else if (index == 1) {
this.statusIndex = 0
this.getDataList()
} else if (index == 2) {
this.statusIndex = 1
this.getDataList()
} else if (index == 3) {
this.statusIndex = -1
this.getDataList()
} else if (index == 4) {
this.statusIndex = 2
this.getDataList()
}
},
//接口中获取选中列表数据,有数据展示,无数据则展示暂无内容(接口获取数据部分省略)
getDataList() {
this.showList = res.data.data.data
if (this.showList.length != 0) {
this.nohaveData = false
this.ifData = true
this.getHeight()
} else {
this.nohaveData = true
this.ifData = false
this.getHeight()
}}
//改变swiper盒子节点高度的固定搭配
let infos = uni .createSelectorQuery().in(this).select('.对应class名称');
//高度修改
infos.boundingClientRect(data => {
this.swiperHeight = data.height
}).exec();
链接详见🔗:
uni.createSelectorQuery() | uni-app官网
style
<style lang="scss" scoped>
.tabBox {
height: 200rpx;
overflow-x: scroll;
overflow-y: auto;
display: flex;
justify-content: space-around;
align-items: center;
// 去掉滑动进度条
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
.tab-item {
height: 80rpx;
width: 160rpx;
text-align: center;
flex-shrink: 0;
display: inline-block;
margin-left: 30rpx;
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #545454;
}
.chosen-item {
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #222222;
margin-left: 30rpx;
border-bottom: #FF3B79 solid 4rpx;
}
}
.totalSwiper {
width: 100%;
text-align: center;
.signalSwiper {
width: 686rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
margin: 0 auto;
.dataBox {
width: 686rpx;
height: 144rpx;
background: #FAFAFA;
border-radius: 24rpx;
margin: 20rpx auto;
display: flex;
justify-content: space-between;
align-items: center;
.boxLeft {
margin-left: 30rpx;
.boxIcon {
width: 96rpx;
height: 96rpx;
border-radius: 16rpx;
background-size: 96rpx 96rpx;
}
}
.boxRight {
display: flex;
justify-content: space-between;
flex: 1;
margin: 0 30rpx;
.rightName {
height: 120rpx;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
.boxTitle {
font-size: 26rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #060606;
}
.boxDate {
font-size: 24rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9E9EA5;
}
}
.rightPrice {
height: 120rpx;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-end;
.boxNumber {
font-size: 24rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #777777;
}
.boxDate {
font-size: 24rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9E9EA5;
}
}
}
}
}
.noDataSwiper {
width: 686rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
margin: 0 auto;
.failImg {
width: 480rpx;
height: 420rpx;
background: url('../../static/img_placeholder_order.png');
background-size: 480rpx 420rpx;
margin: 160rpx auto;
}
.failWord {
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9999a6;
line-height: 36rpx;
}
}
}
</style>
二、左右布局,tab全部展示在页面左侧,点击切换对应分类
功能需求
全部分类展示在页面左侧,点击对应的标签,右侧出现该分类下的优惠券。仅支持点击切换。
需求分析
左侧tab栏展示接口中获取到的全部标签,点击传入选中ID,右侧展示选中ID的所有优惠券 。
实现代码
template
<view class="bottomContent">
<view class="leftContent">
<scroll-view scroll-y="true">
<view class="oneTab" v-for="(item,index) in tabList" :key="index" @click="choose(item.id,index)":class="chosenTab == item.id?'chosenTab':''">
{{item.title}}
</view>
</scroll-view>
</view>
<view class="rightContent">
<view class="singleBox" v-for="(item,index) in getApp" :key="index" @click="goApp(item.id)">
<view class="singleIcon" :style="{backgroundImage: 'url('+item.cover+')' }"></view>
<view class="singleTitle">{{item.title}}</view>
</view>
</view>
</view>
data
data() {
return {
//分类列表
tabList: [],
//选中分类
chosenTab: 0,
//选中标签
chosenId: 0,
//每个分类中获取到的数据
getApp: [],
//点击软件获取到的数据
getCoupon: [],
//软件展示列表
appList: []
}
},
methods
//选择分类tab
choose(whichType, which) {
this.chosenTab = whichType
this.chosenId = which
uni.request({
url: '获取分类下展示的apps',
method: "GET",
header: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: {
//传入对应数据
},
success: (res) => {
if (res.data.code == 200) {
this.getApp = res.data.data
}
}
})
},
//跳转到分类下面的app
goApp(whichApp) {
uni.request({
url: '跳转到选中优惠券的规格选择页',
method: "GET",
header: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: {
//传入对应数据
},
success: (res) => {
if (res.data.code) {
this.getCoupon = res.data.data
}
}
})
},
style
.bottomContent {
width: 100%;
display: flex;
// justify-content: space-between;
.leftContent {
width: 192rpx;
height: 1100rpx;
background: #F5F6F8;
.oneTab {
width: 100%;
height: 96rpx;
line-height: 96rpx;
text-align: center;
margin: 20rpx auto;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #7B7B7B;
}
.chosenTab {
width: 100%;
height: 96rpx;
line-height: 96rpx;
font-size: 26rpx;
font-family: PingFangSC-Medium, PingFang SC;
background-color: #fff;
font-weight: 500;
color: #2F2F31;
border-left: 2px solid red;
}
}
.rightContent {
width: 558rpx;
height: 50%;
margin-top: 50rpx;
display: flex;
flex-wrap: wrap;
// justify-content: space-between;
align-items: flex-start;
.singleBox {
width: 120rpx;
height: 150rpx;
margin-left: 47rpx;
margin-bottom: 15rpx;
// margin-top: 50rpx;
display: flex;
flex-direction: column;
align-items: center;
.singleIcon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
background-size: 96rpx 96rpx;
margin-bottom: 5rpx;
}
.singleTitle {
font-size: 24rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #2A2A2F;
}
}
}
}
三、上下布局,点击标签页面自动滑动到对应模块,页面内锚点定位
功能需求
页面上方循环获取标签信息,超出屏幕宽度时可滑动,点击标签时,当前选中标签样式改变,下方数据盒子随之滑动;标签对应分类盒子展示在页面正中间。
需求分析
超出屏幕宽度可滑动--使用<scroll-view>标签,标签内循环<div>展示数据(注:使用div绑定ref,使点击盒子一直显示在页面中);监听页面,当页面停止滚动时,当前页面中央展示盒子对应顶部标签高亮;点击上方标签时,使其对应数据盒子平滑滑动到当前页面中央。
实现代码
template
<div class="tabBox">
<scroll-view scroll-x="true" style="white-space: nowrap;">
<div ref="tab" class="tab-item" :class="{'isActive': index==navgatorIndex}"
@click="handleLeft(index)" v-for="(item,index) in allInfoList" :key="index">
{{ item.title }}
</div>
</scroll-view>
</div>
<view class="bottomContent">
<div class="allGoods" :id="'id'+index" v-for="(item,index) in allInfoList" :key="index">
<view class="boxTitle">{{item.title}}</view>
<view class="goodsList">
<view class="goods" v-for="(item,cindex) in allInfoList[index].goods" :key="cindex" @click="goGoods(item.id)">
<view class="goodsPic" :style="{backgroundImage:'url(' +item.cover + ')'}"></view>
<view class="goodsTitle">{{item.title}}</view>
<view class="goodsButton">
<view class="currentPrice">
<text style="font-size: 28rpx; margin-right:6rpx;">¥</text>
{{ item.currentPrice }}
</view>
<view class="originalPrice">¥{{ item.originalPrice/100 }</view>
<view class="rightBox"></view>
</view>
</view>
</view>
</div>
</view>
data
data() {
return {
//当前选中导航索引
navgatorIndex: 0,
//点击导航栏时,暂时停止监听页面滚动
listBoxState: true,
//分类商品盒子
allInfoList: [],
//每个盒子内的商品
goodsList: [],
//缓存中用户ID
userId: null,
//缓存中的手机号
phoneNumber: null,
};
},
mounted
mounted() {
let timeId;
window.addEventListener('scroll', () => {
// 页面滚动停止100毫秒后才会执行下面的函数。
clearTimeout(timeId);
timeId = setTimeout(() => {
this.scrollToTop();
}, 100);
}, true);
},
methods
// 点击导航菜单,页面滚动到指定位置
handleLeft(index) {
this.navgatorIndex = index;
this.$el.querySelector(`#id${index}`).scrollIntoView({
behavior: "smooth", // 平滑过渡
block: "end" // 下边框与视窗顶部平齐
});
this.listBoxState = false;
let timeId;
clearTimeout(timeId);
timeId = setTimeout(() => {
this.listBoxState = true;
}, 200);
},
// 监听页面元素滚动,改变导航栏选中
scrollToTop() {
// 获取视窗高度
var domHight = document.body.offsetHeight;
// dom滚动位置
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
if (this.listBoxState) { //作用是点击导航栏时,延迟这里执行。
this.allInfoList.map((v, i) => {
// 获取监听元素距离视窗顶部距离
var offsetTop = document.getElementById(`id${i}`).offsetTop - 400;
// 获取监听元素本身高度
var scrollHeight = document.getElementById(`id${i}`).scrollHeight;
// 如果 dom滚动位置 >= 元素距离视窗距离 && dom滚动位置 <= 元素距离视窗距离+元素本身高度
// 则表示页面已经滚动到可视区了。
if (scrollTop >= offsetTop && scrollTop <= (offsetTop + scrollHeight)) {
// 导航栏背景色选中
this.navgatorIndex = i;
//滑动对应选中的tab在屏幕中央展示
this.$refs.tab[this.navgatorIndex].scrollIntoView({
block: 'nearest',
inline: 'center'
})
}
})
}
},
style
.tabBox {
width: 100%;
overflow-x: scroll;
overflow-y: auto;
display: flex;
align-items: center;
position: fixed;
background-color: #FF5B4C;
// 去掉滑动进度条
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
.tab-item {
height: 80rpx;
width: 160rpx;
text-align: center;
flex-shrink: 0;
display: inline-block;
margin-left: 30rpx;
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
.isActive {
font-size: 36rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FFFFFF;
border-bottom: #FFFFFF 2px solid;
}
}
.bottomContent {
width: 750rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.allGoods {
width: 702rpx;
background: rgba(255, 239, 225, 0.3);
border-radius: 32rpx;
border: 2rpx solid rgba(255, 220, 201, 0.3);
position: relative;
margin-bottom: 70rpx;
.boxTitle {
width: 420rpx;
height: 144rpx;
background: url('../../static/img_title_1@2x.png') no-repeat;
background-size: 420rpx 144rpx;
position: absolute;
left: 142rpx;
top: -38rpx;
text-align: center;
line-height: 90rpx;
font-size: 32rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #FFFFFF;
}
.goodsList {
width: 670rpx;
display: flex;
flex-wrap: wrap;
margin: 66rpx auto 16rpx;
.goods {
width: 212rpx;
height: 328rpx;
background: #FDF7F2;
border-radius: 16rpx;
border: 1px solid #FFE0C5;
margin: 0 5rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.goodsPic {
width: 212rpx;
height: 212rpx;
background-size: 212rpx 212rpx;
border-radius: 16rpx 16rpx 0 0;
}
.goodsTitle {
font-size: 28rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #060606;
margin-left: 10rpx;
}
.goodsButton {
width: 202rpx;
height: 48rpx;
background: linear-gradient(135deg, #FF5B4C 0%, #FF3B79 100%);
border-radius: 14rpx;
margin: 8rpx auto;
display: flex;
justify-content: space-around;
align-items: center;
.currentPrice {
font-size: 40rpx;
font-family: DINAlternate-Bold, DINAlternate;
font-weight: bold;
color: #FFFFFF;
}
.originalPrice {
font-size: 22rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
text-decoration: line-through;
margin-left: -30rpx;
}
.rightBox {
width: 32rpx;
height: 32rpx;
background: url('../../static/icon_arrows_right@2x (1).png') no-repeat;
background-size: 32rpx 32rpx;
}
}
}
}
}
}