一、实现目标
1、点击左侧分类列表,高亮显示当前分类项,右侧scroll-view滑动到分类对应的位置。
2、滑动右侧的scroll-view,左侧的分类列表高亮显示当前商品所属的分类。
二、功能点实现
1、点击左侧分类列表,高亮显示当前分类项。
<!--左侧导航栏-->
<view class="navigation">
<view v-for="(cuisineType,index) in cuisineTypes"
:key="index" :id="'left'+index"
@tap="chooseCuisineType(index)"
class="cuisineTypeStatic"
:class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
{{cuisineType.typeName}}
</view>
</view>
<script>
export default {
data() {
return{
cuisineTypes: [],
currentCuisineTypeIndex: 0,
}
},
methods: {
chooseCuisineType(index){
this.currentCuisineTypeIndex = index
}
}
}
</script>
<style>
.cuisineTypeActive {background: rgba(255, 255, 255, 1)}
</style>
cuisineTypes是一个向后端请求得到的按优先级排序的json数据,此处为
[
{
"id": 3,
"typeName": "招牌菜",
"priorityLevel": 0
},
{
"id": 4,
"typeName": "小吃",
"priorityLevel": 1
},
{
"id": 2,
"typeName": "饮料",
"priorityLevel": 2
},
{
"id": 1,
"typeName": "主食",
"priorityLevel": 3
}
]
currentCuisineTypeIndex记录当前点击的cuisineTypes列表下标。
:class动态class属性,当index === currentCuisineTypeIndex结果为true时,为<view>标签叠加cuisineTypeActive样式。
2、点击左侧分类列表项,右侧scroll-view滑动
<!--左侧导航栏-->
<view class="navigation">
<view v-for="(cuisineType,index) in cuisineTypes"
:key="index" :id="'left'+index"
@tap="chooseCuisineType(index)"
class="cuisineTypeStatic"
:class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
{{cuisineType.typeName}}
</view>
</view>
<!--右侧scroll-view-->
<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId">
<view v-for="(cuisine,index) in cuisines" :key="index">
<view :id="'right'+index">{{cuisine.cuisineType.typeName}}</view>
<view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i">
<image :src="sameTypeCuisine.cuisinePictureUrl"></image>
<view class="cuisine-info">
<view>{{sameTypeCuisine.cuisineName}}</view>
<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
</view>
</view>
</view>
<scroll-view>
<script>
export default {
data() {
return{
cuisineTypes: [],
cuisines: [],
currentCuisineTypeIndex: 0,
rightId: '',
}
},
methods: {
chooseCuisineType(index){
this.currentCuisineTypeIndex = index
this.rightId = 'right' + index
}
}
}
</script>
<style>
.cuisineTypeActive {background: rgba(255, 255, 255, 1)}
</style>
cuisines是一个向后端请求得到按照类型排好的列表,此处为
[
{
"cuisineType": {
"id": 3,
"typeName": "招牌菜",
"priorityLevel": 0
},
"sameTypeCuisines": [
{
"id": 1,
"cuisineName": "冰皮鸡",
"cuisinePictureUrl": "https://localhost:7106/StaticFiles/556733-冰皮鸡.jpg",
"cuisinePrice": 66,
"cuisineDescription": "清远三黄鸡",
"t_CuisineType_Id": 3,
"number": 0,
},
{
"id": 2,
"cuisineName": "北京烤鸭",
"cuisinePictureUrl": "https://localhost:7106/StaticFiles/1621493-北京烤鸭.png",
"cuisinePrice": 88,
"cuisineDescription": "老北京招牌烤鸭",
"t_CuisineType_Id": 3,
"number": 0,
}
]
},
{
"cuisineType": {
"id": 4,
"typeName": "小吃",
"priorityLevel": 1
},
"sameTypeCuisines": [
{
"id": 3,
"cuisineName": "臭豆腐",
"cuisinePictureUrl": "https://localhost:7106/StaticFiles/490542-臭豆腐.jpg",
"cuisinePrice": 8,
"cuisineDescription": "正宗台湾臭豆腐",
"t_CuisineType_Id": 4,
"number": 0,
},
{
"id": 6,
"cuisineName": "冰粉",
"cuisinePictureUrl": "https://localhost:7106/StaticFiles/859892-屏幕截图 2023-03-15 002936.png",
"cuisinePrice": 6,
"cuisineDescription": "",
"t_CuisineType_Id": 4,
"number": 0,
}
]
},
//省略
]
scroll-into-views属性,值应为子元素id(id不能以数字开头)。chooseCuisineType方法执行时改变了rightId的值,使scroll-view滚动到指定id的子元素。
3、滑动右侧的scroll-view,左侧的分类列表高亮显示当前商品所属的分类。
<!--左侧导航栏-->
<view class="navigation">
<view v-for="(cuisineType,index) in cuisineTypes"
:key="index" :id="'left'+index"
@tap="chooseCuisineType(index)"
class="cuisineTypeStatic"
:class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
{{cuisineType.typeName}}
</view>
<view>
<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId" @scroll="scrolling">
<view v-for="(cuisine,index) in cuisines" :key="index">
<view :id="'right'+index" class="typeName">
{{cuisine.cuisineType.typeName}}
</view>
<view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i">
<image :src="sameTypeCuisine.cuisinePictureUrl"></image>
<view class="cuisine-info">
<view>{{sameTypeCuisine.cuisineName}}</view>
<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
</view>
</view>
</view>
<scroll-view>
<script>
export default {
data() {
return{
rightScrollHeight: 0,
typeNamePositions: []
}
},
mounted() {
this.getRightScrollHeight()
this.getTypeNamePositions()
},
methods: {
getRightScrollHeight() {
const query = uni.createSelectorQuery().in(this);
query.select('.right-scroll').boundingClientRect(data => {
this.rightScrollHeight = data.height}).exec();
},
getTypeNamePositions() {
const query = uni.createSelectorQuery().in(this);
query.selectAll('.typeName').boundingClientRect(data => {
data.forEach((item) => {this.typeNamePositions.push(item.top)})
}).exec();
},
scrolling(e) {
//获取当前窗口顶边与scroll-view顶边的距离
const scrollTop = e.target.scrollTop
//右侧scroll-view滑到底时,左侧分类导航高亮显示最后一项
const lastIndex = this.typeNamePositions.length - 1;
if (scrollTop >= this.typeNamePositions[lastIndex]) {
this.currentCuisineTypeIndex = lastIndex;
}
//判断右侧窗口顶边在哪两个分类名称之间,左侧分类导航高亮显示该索引项
else{
for (var i = 0; i < lastIndex; i++) {
if (scrollTop>= this.typeNamePositions[i]&&scrollTop<=this.typeNamePositions[i + 1]) {
this.currentCuisineTypeIndex = i
break;
}
}
}
}
}
}
</script>
mounted钩子函数调用getRightScrollHeight()方法和getTypeNamePositions()方法,提前计算出右侧scroll-view元素的高度,和每一个分类项名称(typeNamePosition)所在的位置并存入数组中。
@scroll事件,当右侧scroll-view滑动时触发scrolling方法,判定当前窗口顶边在哪两个分类名称之间,左侧分类导航高亮显示该索引项
4、调整样式
为右侧scrow-view最后一个子元素(此处为主食数组),添加paddingBottom样式,paddingBottom的数值为倒数第二个元素的高度(即饮料数组的高度)。
mounted钩子函数调用penultSameTypeCuisinesHeight()方法,获取倒数第二个元素的高度。pensult中文释译为倒数第二个。
:style动态样式,三元表达式paddingBottom:index===cuisines.length-1?penultSameTypeCuisinesHeight +'px': ' '
当index == cuisines.length-1为true时,为view标签赋予paddingBottom。
三、完整代码
<!--左侧导航栏-->
<view class="navigation">
<view v-for="(cuisineType,index) in cuisineTypes"
:key="index" :id="'left'+index"
@tap="chooseCuisineType(index)"
class="cuisineTypeStatic"
:class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
{{cuisineType.typeName}}
</view>
</view>
<!--右侧scroll-view-->
<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId" @scroll="scrolling">
<view v-for="(cuisine,index) in cuisines" :key="index">
<view :id="'right'+index" class="typeName">
{{cuisine.cuisineType.typeName}}
</view>
<view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i"
:style="{paddingBottom:index===cuisines.length-1?penultSameTypeCuisinesHeight +'px':''}"
:class="[index===cuisines.length-2?'penultSameTypeCuisines':'']">
<image :src="sameTypeCuisine.cuisinePictureUrl"></image>
<view class="cuisine-info">
<view>{{sameTypeCuisine.cuisineName}}</view>
<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
</view>
</view>
</view>
<scroll-view>
<script>
export default {
data() {
return{
cuisineTypes: [],
cuisines: [],
currentCuisineTypeIndex: 0,
rightId: '',
rightScrollHeight: 0,
typeNamePositions: [],
penultSameTypeCuisinesHeight: 0
}
},
mounted() {
this.getRightScrollHeight()
this.getTypeNamePositions()
this.getPenultSameTypeCuisinesHeight()
},
methods: {
chooseCuisineType(index){
this.currentCuisineTypeIndex = index
this.rightId = 'right' + index
},
getRightScrollHeight() {
const query = uni.createSelectorQuery().in(this);
query.select('.right-scroll').boundingClientRect(data => {
this.rightScrollHeight = data.height}).exec();
},
getTypeNamePositions() {
const query = uni.createSelectorQuery().in(this);
query.selectAll('.typeName').boundingClientRect(data => {
data.forEach((item) => {this.typeNamePositions.push(item.top)})
}).exec();
},
getPenultSameTypeCuisinesHeight() {
const query = uni.createSelectorQuery().in(this);
query.select('.penultSameTypeCuisines').boundingClientRect(data => {
this.penultSameTypeCuisinesHeight = data.height
}).exec();
},
scrolling(e) {
const scrollTop = e.target.scrollTop
const lastIndex = this.typeNamePositions.length - 1;
if (scrollTop >= this.typeNamePositions[lastIndex]) {
this.currentCuisineTypeIndex = lastIndex;
} else {
for (var i = 0; i < lastIndex; i++) {
if (scrollTop >= this.typeNamePositions[i] && scrollTop <= this.typeNamePositions[i + 1]) {
this.currentCuisineTypeIndex = i
break;
}
}
}
}
}
}
</script>
<style>
.cuisineTypeActive {background: rgba(255, 255, 255, 1)}
<style>