实现效果如下:
实现左右联动的菜单列表,主要依靠scroll-view的是三个属性:
scroll-top:设置竖向滚动条位置(可视区域最顶部到scroll-view顶部的距离);
scroll-into-view:值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素;
bindscroll:滚动时触发,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}
结构图示:
wxml:
选择项目
选择技师
优惠券
{{item.type}}
{{item.type}}
{{itemName.name}}
{{tag}}
¥{{itemName.price}}
-
{{itemName.count?itemName.count:0}}
+
...
...
js:
var app = getApp();
Page({
//右侧分类的高度累加数组
//比如:[洗车数组的高度,洗车+汽车美容的高度,洗车+汽车美容+精品的高度,...]
heightArr: [],
//记录scroll-view滚动过程中距离顶部的高度
distance: 0,
/**
* 页面的初始数据
*/
data: {
currentTab: 0, //选择项目、选择技师、优惠券
currentLeft: 0, //左侧选中的下标
selectId: "item0", //当前显示的元素id
scrollTop: 0, //到顶部的距离
serviceTypes: [], //项目列表数据
staffList: [],
coupons: []
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
this.request();
},
//请求列表数据
request() {
app.HttpClient.request({url: "services"}).then((res) => {
console.log(res);
this.setData({
serviceTypes: res.data.serviceTypes,
staffList: res.data.staffList,
coupons: res.data.coupons
});
this.selectHeight();
})
},
//选择项目左侧点击事件 currentLeft:控制左侧选中样式 selectId:设置右侧应显示在顶部的id
proItemTap(e) {
this.setData({
currentLeft: e.currentTarget.dataset.pos,
selectId: "item" + e.currentTarget.dataset.pos
})
},
//计算右侧每一个分类的高度,在数据请求成功后请求即可
selectHeight() {
let that = this;
this.heightArr = [];
let h = 0;
const query = wx.createSelectorQuery();
query.selectAll('.pro-box').boundingClientRect()
query.exec(function(res) {
res[0].forEach((item) => {
h += item.height;
that.heightArr.push(h);
})
console.log(that.heightArr);
// [160, 320, 1140, 1300, 1570, 1840, 2000]
// 160:洗车标题高度50px,item的高度110,洗车只有一个item,所以50+110*1=160px;
// 320: 汽车美容标题高度50px,只有一个item,再加上洗车的高度,所以50+110*1+160=320px;
// ...
})
},
//监听scroll-view的滚动事件
scrollEvent(event) {
if (this.heightArr.length == 0) {
return;
}
let scrollTop = event.detail.scrollTop;
let current = this.data.currentLeft;
if (scrollTop >= this.distance) { //页面向上滑动
//如果右侧当前可视区域最底部到顶部的距离 超过 当前列表选中项距顶部的高度(且没有下标越界),则更新左侧选中项
if (current + 1 < this.heightArr.length && scrollTop >= this.heightArr[current]) {
this.setData({
currentLeft: current + 1
})
}
} else { //页面向下滑动
//如果右侧当前可视区域最顶部到顶部的距离 小于 当前列表选中的项距顶部的高度,则更新左侧选中项
if (current - 1 >= 0 && scrollTop < this.heightArr[current - 1]) {
this.setData({
currentLeft: current - 1
})
}
}
//更新到顶部的距离
this.distance = scrollTop;
}
})
数据结构:
如果你还想实现从其他页面,点击按钮跳转到当前页面,并且列表滚动到指定项,此项在可视区域的第一个展示:
//优惠券点击事件
couponTap(e) {
let item = e.currentTarget.dataset.item;
if (item.limitServiceName) {
this.setData({
currentTab: 0
})
this.scrollTo(item.limitServiceName);
}
},
//滚动到指定名称的某一项(通过列表的商品name来判断,也可以用id或者其他的,只要是列表项的唯一标志)
scrollTo(name) {
let that = this;
const query = wx.createSelectorQuery()
query.select(".pro-item").boundingClientRect()
//计算每一个item的高度(右侧分类的小标题高度是在css里写死的50px)
query.exec(function(res) {
that.moveHeight(res[0].height, name);
})
},
moveHeight(height, name) {
let list = this.data.serviceTypes;
let top = 50; //右侧每一分类的标题名称的高度为50px,top记录每一个标题到顶部的距离
for (let i = 0; i < list.length; i++) {
for (let j = 0; j < list[i].services.length; j++) {
//如果当前的item是要滚动到顶部的,
if (list[i].services[j].name == name) {
this.setData({
scrollTop: height * j + top
})
break;
}
}
//右侧每划过一个分类,就把此分类的高度和标题的高度累加到top上
top = top + list[i].services.length * height + 50;
}
}
wxss:
.cont-pro {
height: 100%;
display: flex;
background-color: #fff;
}
.pro-left {
width: 160rpx;
flex-basis: 160rpx;
background-color: #f6f6f6;
overflow-y: scroll;
}
.pro-title {
width: 100%;
height: 100rpx;
line-height: 100rpx;
text-align: center;
}
.pro-right {
flex: 1;
background-color: #fff;
overflow-y: scroll;
}
.item-title {
width: 100%;
height: 50px;
line-height: 100rpx;
padding: 0 30rpx;
box-sizing: border-box;
}
.item-name {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
word-break: break-all;
}
.pro-item {
width: 100%;
display: flex;
padding: 30rpx;
box-sizing: border-box;
}
.pro-img {
width: 160rpx;
height: 160rpx;
flex-basis: 160rpx;
flex-shrink: 0;
border-radius: 4rpx;
margin-right: 30rpx;
background-color: #f5f5f5;
}
.pro-text {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.pro-tag {
color: #f08d31;
font-size: 22rpx;
}
.pro-tag text {
padding: 4rpx 10rpx;
background-color: rgba(240, 141, 49, 0.15);
margin-right: 10rpx;
border-radius: 2rpx;
}
.pro-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.count {
width: 170rpx;
flex-basis: 170rpx;
background-color: #f6f6f6;
border-radius: 28rpx;
display: flex;
}
.count text {
flex: 1;
text-align: center;
line-height: 50rpx;
}