實現(xiàn)效果如下:
效果展示.gif
實現(xiàn)左右聯(lián)動的菜單列表吧黄,主要依靠scroll-view的是三個屬性:
scroll-top:設(shè)置豎向滾動條位置(可視區(qū)域最頂部到scroll-view頂部的距離)氧骤;
scroll-into-view:值應(yīng)為某子元素id(id不能以數(shù)字開頭)陌选。設(shè)置哪個方向可滾動救氯,則在哪個方向滾動到該元素驾诈;
bindscroll:滾動時觸發(fā)凤瘦,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}
結(jié)構(gòu)圖示:
布局.png
wxml:
<!-- tabs -->
<view class='tabs font28 color9'>
<view class='tab-item {{currentTab==0?"tab-active":""}}' bindtap='changeTab' data-pos='0'>選擇項目</view>
<view class='tab-item {{currentTab==1?"tab-active":""}}' bindtap='changeTab' data-pos='1'>選擇技師</view>
<view class='tab-item {{currentTab==2?"tab-active":""}}' bindtap='changeTab' data-pos='2'>優(yōu)惠券</view>
</view>
<!-- 選擇項目 -->
<view wx:if="{{currentTab==0}}" class='cont-pro'>
<!-- 左側(cè)列表 -->
<view class='pro-left font28 color9'>
<view wx:for="{{serviceTypes}}" class='pro-title {{index==currentLeft?"font30 color3 bgWhite":""}}' bindtap='proItemTap' data-pos='{{index}}'>{{item.type}}</view>
</view>
<!-- 右側(cè)列表 -->
<scroll-view class='pro-right' scroll-y scroll-with-animation="true" scroll-into-view="{{selectId}}" bindscroll="scrollEvent" scroll-top="{{scrollTop}}">
<!-- id要用來實現(xiàn)點擊左側(cè)右側(cè)滾動至相應(yīng)位置的效果冤竹;class(pro-box)要用來計算右側(cè)對應(yīng)左側(cè)某一分類的高度 -->
<!-- id: item0, item1, item2... (注意:不能直接使用數(shù)字或漢字做id)-->
<view id='{{"item"+index}}' class='pro-box' wx:for="{{serviceTypes}}" wx:for-index="index" wx:for-item="item">
<!-- 右側(cè)列表里的標(biāo)題拂封,高度為50px -->
<view class="item-title font30">{{item.type}}</view>
<view class='pro-item' wx:for="{{item.services}}" wx:for-index="idx" wx:for-item="itemName">
<image class='pro-img' src='{{itemName.img}}'></image>
<view class='pro-text'>
<view class='item-name color3 font32'>{{itemName.name}}</view>
<view class='pro-tag'>
<text wx:for="{{itemName.label}}" wx:for-item="tag">{{tag}}</text>
</view>
<view class='pro-bottom'>
<text style='color:#C93131;' class='font32'>¥{{itemName.price}}</text>
<view class='count font30 color6'>
<text catchtap='subCount' data-pos='{{idx}}' data-index='{{index}}' data-sid='{{itemName.id}}'>-</text>
<text class='color3'>{{itemName.count?itemName.count:0}}</text>
<text catchtap='addCount' data-pos='{{idx}}' data-index='{{index}}' data-sid='{{itemName.id}}'>+</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 選擇技師 -->
<view wx:if="{{currentTab==1}}" class='staff'>
...
</view>
<!-- 優(yōu)惠券-->
<view wx:if="{{currentTab==2}}" class='coupon'>
...
</view>
js:
var app = getApp();
Page({
//右側(cè)分類的高度累加數(shù)組
//比如:[洗車數(shù)組的高度,洗車+汽車美容的高度鹦蠕,洗車+汽車美容+精品的高度冒签,...]
heightArr: [],
//記錄scroll-view滾動過程中距離頂部的高度
distance: 0,
/**
* 頁面的初始數(shù)據(jù)
*/
data: {
currentTab: 0, //選擇項目、選擇技師钟病、優(yōu)惠券
currentLeft: 0, //左側(cè)選中的下標(biāo)
selectId: "item0", //當(dāng)前顯示的元素id
scrollTop: 0, //到頂部的距離
serviceTypes: [], //項目列表數(shù)據(jù)
staffList: [],
coupons: []
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面加載
*/
onLoad: function(options) {
this.request();
},
//請求列表數(shù)據(jù)
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();
})
},
//選擇項目左側(cè)點擊事件 currentLeft:控制左側(cè)選中樣式 selectId:設(shè)置右側(cè)應(yīng)顯示在頂部的id
proItemTap(e) {
this.setData({
currentLeft: e.currentTarget.dataset.pos,
selectId: "item" + e.currentTarget.dataset.pos
})
},
//計算右側(cè)每一個分類的高度萧恕,在數(shù)據(jù)請求成功后請求即可
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:洗車標(biāo)題高度50px,item的高度110肠阱,洗車只有一個item票唆,所以50+110*1=160px;
// 320: 汽車美容標(biāo)題高度50px,只有一個item屹徘,再加上洗車的高度走趋,所以50+110*1+160=320px;
// ...
})
},
//監(jiān)聽scroll-view的滾動事件
scrollEvent(event) {
if (this.heightArr.length == 0) {
return;
}
let scrollTop = event.detail.scrollTop;
let current = this.data.currentLeft;
if (scrollTop >= this.distance) { //頁面向上滑動
//如果右側(cè)當(dāng)前可視區(qū)域最底部到頂部的距離 超過 當(dāng)前列表選中項距頂部的高度(且沒有下標(biāo)越界),則更新左側(cè)選中項
if (current + 1 < this.heightArr.length && scrollTop >= this.heightArr[current]) {
this.setData({
currentLeft: current + 1
})
}
} else { //頁面向下滑動
//如果右側(cè)當(dāng)前可視區(qū)域最頂部到頂部的距離 小于 當(dāng)前列表選中的項距頂部的高度噪伊,則更新左側(cè)選中項
if (current - 1 >= 0 && scrollTop < this.heightArr[current - 1]) {
this.setData({
currentLeft: current - 1
})
}
}
//更新到頂部的距離
this.distance = scrollTop;
}
})
數(shù)據(jù)結(jié)構(gòu):
數(shù)據(jù)結(jié)構(gòu).png
如果你還想實現(xiàn)從其他頁面簿煌,點擊按鈕跳轉(zhuǎn)到當(dāng)前頁面,并且列表滾動到指定項鉴吹,此項在可視區(qū)域的第一個展示:
//優(yōu)惠券點擊事件
couponTap(e) {
let item = e.currentTarget.dataset.item;
if (item.limitServiceName) {
this.setData({
currentTab: 0
})
this.scrollTo(item.limitServiceName);
}
},
//滾動到指定名稱的某一項(通過列表的商品name來判斷姨伟,也可以用id或者其他的,只要是列表項的唯一標(biāo)志)
scrollTo(name) {
let that = this;
const query = wx.createSelectorQuery()
query.select(".pro-item").boundingClientRect()
//計算每一個item的高度(右側(cè)分類的小標(biāo)題高度是在css里寫死的50px)
query.exec(function(res) {
that.moveHeight(res[0].height, name);
})
},
moveHeight(height, name) {
let list = this.data.serviceTypes;
let top = 50; //右側(cè)每一分類的標(biāo)題名稱的高度為50px豆励,top記錄每一個標(biāo)題到頂部的距離
for (let i = 0; i < list.length; i++) {
for (let j = 0; j < list[i].services.length; j++) {
//如果當(dāng)前的item是要滾動到頂部的夺荒,
if (list[i].services[j].name == name) {
this.setData({
scrollTop: height * j + top
})
break;
}
}
//右側(cè)每劃過一個分類,就把此分類的高度和標(biāo)題的高度累加到top上
top = top + list[i].services.length * height + 50;
}
}
moveTo.gif
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;
}