uniapp vue3 实现菜单左右联动分类页面,以及滚动自动定位
·
以下完整代码拷贝直接可以使用, 替换一下 tabr数组里面的图片就可以了

<template>
<view class="">
<view class="u-wrap">
<view class="u-menu-wrap">
<scroll-view
scroll-y
scroll-with-animation
class="u-tab-view menu-scroll-view"
:scroll-top="scrollTop"
>
<view v-for="(item,index) in tabbar" :key="index" class="u-tab-item"
:class="[optIndex == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{item.name}}</text>
</view>
</scroll-view>
<scroll-view
:scroll-top="scrollRightTop"
scroll-y
scroll-with-animation
:scroll-into-view="itemId"
class="right-box"
@scroll="rightScroll">
<view class="page-view">
<view class="class-item" :id="'item' + index" v-for="(item , index) in tabbar" :key="index">
<view class="item-title">
<text>{{item.name}}</text>
</view>
<view class="item-container">
<view
class="thumb-box"
v-for="(item1, index1) in item.foods"
:key="index1">
<image v-if="item1.icon != ''" class="item-menu-image" :src="item1.icon" mode=""></image>
<!-- <view class="item-menu-name">{{item1.name}}</view> -->
<view class="contetn-box">
<view class="item-menu-name">{{item1.name}}</view>
<view class="info">
味道很好吃
</view>
<view class="price-box">
<view class="price">
¥66
</view>
<!-- <order-number-box v-model="item.cat" /> -->
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import { onLoad, onReachBottom, onPullDownRefresh, onShow, onHide, onPageScroll } from '@dcloudio/uni-app';
import { ref } from 'vue';
const optIndex = ref(0); //左边菜单值,同时也为上一个页面传过来的值(传过来精确在第几行并使样式为active)
const itemId = ref(''); // 右边scroll-view用于滚动的id(自动滚动定位左边)
const scrollTop = ref(0); //tab标题的滚动条位置
const oldScrollTop = ref(0);// tab标题的滚动条旧位置
//const current = ref(0) // 预设当前项的值
const menuHeight = ref(0); // 左边菜单的高度
const menuItemHeight = ref(0);// 左边菜单item的高度
const arr = reactive([]);// 储存距离顶部高度的数组
const scrollRightTop = ref(0);// 右边栏目scroll-view的滚动条高度
const timer = ref(null);// 定时器
const dataVal = ref()
//商品分类数据
const tabbar = ref([
{
"name": "白兰地",
"foods": [{
"cat": 383,
"name": "蔬菜",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "蔬菜"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
{
"name": "香槟系列",
"foods": [{
"cat": 388,
"name": "禽类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "禽类"
},
{
"cat": 389,
"name": "畜类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "畜类"
},
{
"cat": 391,
"name": "蛋类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "蛋类"
},
{
"cat": 390,
"name": "水产",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水产"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
{
"name": "东南亚美食",
"foods": [{
"cat": 393,
"name": "米面粮油",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "米面粮油"
},
{
"cat": 394,
"name": "坚果干果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "坚果干果"
},
{
"cat": 395,
"name": "加工食品",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "加工食品"
},
{
"cat": 396,
"name": "茶烟酒",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "茶烟酒"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
{
"name": "少年甜品",
"foods": [{
"cat": 398,
"name": "鲜花盆景",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "鲜花盆景"
},
{
"cat": 399,
"name": "果树苗",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "果树苗"
},
{
"cat": 400,
"name": "蔬瓜种子",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "蔬瓜种子"
},
{
"cat": 401,
"name": "经济作物",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "经济作物"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
{
"name": "暖心服务",
"foods": [{
"cat": 403,
"name": "全草类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "全草类"
},
{
"cat": 405,
"name": "根茎皮叶花",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "根茎皮叶花"
},
{
"cat": 406,
"name": "滋补品类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "滋补品类"
},
{
"cat": 404,
"name": "果实籽仁类",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "果实籽仁类"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
{
"name": "团购套餐",
"foods": [{
"cat": 434,
"name": "包装",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "包装"
},
{
"cat": 435,
"name": "安全溯源",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "安全溯源"
},
{
"cat": 436,
"name": "农用百货",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "农用百货"
},
{
"cat": 437,
"name": "仓储物流",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "仓储物流"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
},
{
"cat": 385,
"name": "水果",
"icon": "https://i-blog.csdnimg.cn/direct/1d476d2952364d98a4aba837d1b0b463.jpeg",
"key": "水果"
}
]
},
])
onLoad( (opt) => {
console.log(opt.index);
// 判断是否有传过来的 index ,有就赋值itemId,并激活optIndex
if (opt.index) {
itemId.value = "item" + opt.index;
optIndex.value = opt.index;
}
// 若获取数据后的情况下定位不起作用,就加定时器
//if(opt.index){
// setTimeout(()=>{
// itemId.value = "item" + opt.index;
// optIndex.value = opt.index;
// },1000)
//}
});
// Handle the ready event
onReady (()=> {
getMenuItemTop()
})
/**
* 获取一个目标元素的高度
* @elClass 元素的类名
* @dataVal 储存高度的对象
*/
const getElRect = (elClass, pdataVal) => {
// console.log(elClass, 'elClass1');
// console.log(pdataVal, 'dataVal2');
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
// console.log(query, 'query66');
query.select('.' + elClass).fields({ size: true }, (res) => {
// console.log(res, 'res.fields');
if (!res) {
setTimeout(() => {
getElRect(elClass, dataVal); // 递归调用
}, 10);
return;
}
dataVal.value = res.height;
resolve();
}).exec();
});
};
// Method to get the menu item top values
const getMenuItemTop = () => {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
if (!rects.length) {
setTimeout(() => {
getMenuItemTop();
}, 10);
return;
}
rects.forEach((rect) => {
// 视情况而定,这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
// this.arr.push(rect.top - rects[0].top);
arr.push(rect.top - rects[0].top);
resolve();
});
}).exec();
});
};
/**
* 观测元素相交状态
* 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
* 如果跟.right-box底部相交,就动态设置左边栏目的活动状态
*/
const observer = async () => {
tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
observer.relativeTo('.right-box', { top: 0 }).observe('#item' + index, res => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
leftMenuStatus(id);
}
});
});
};
/**
* 设置左边菜单的滚动状态
* @index 传入的 ID
*/
const leftMenuStatus = async (index) => {
optIndex.value = index;
// 如果为0,意味着尚未初始化
if (menuHeight.value === 0 || menuItemHeight.value === 0) {
await getElRect('menu-scroll-view', 'menuHeight');
await getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
scrollTop.value = index * menuItemHeight.value + menuItemHeight.value / 2 - menuHeight.value / 2;
};
/**
* 点击左边的栏目切换
* @index 传入的 ID
*/
const swichMenu = async (index) => {
if (arr.length === 0) {
await getMenuItemTop();
}
scrollRightTop.value = oldScrollTop.value;
nextTick(function() {
scrollRightTop.value = arr[index];
optIndex.value = index;
leftMenuStatus(index);
});
};
/**
* 右边菜单滚动
* 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
*/
const rightScroll = async (e) => {
oldScrollTop.value = e.detail.scrollTop;
if (arr.length === 0) {
await getMenuItemTop();
}
if (timer.value) return;
if (!menuHeight.value) {
await getElRect('menu-scroll-view', 'menuHeight');
}
setTimeout(() => {//节流
timer.value = null;
// scrollHeight为右边菜单垂直中点位置
// let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
// scrollHeight为右边菜单头部位置
let scrollHeight = e.detail.scrollTop + 20;
for (let i = 0; i < arr.length; i++) {
let height1 = arr[i];
let height2 = arr[i + 1];
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
leftMenuStatus(i);
return;
}
}
}, 10);
};
</script>
<style scoped lang="scss">
.u-wrap {
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
height: 100vh;
}
.u-search-box {
padding: 18rpx 30rpx;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-tab-view {
width: 200rpx;
height: 100%;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #333;
font-weight: 400;
line-height: 1;
}
.u-tab-item-active {
position: relative;
background: linear-gradient(to right,#d9ffd9,#f5f5f5) !important;
border-left: .3125rem solid #7acc7a;
font-size: 28rpx;
font-weight: bold;
background: #fff;
z-index: 1;
}
.u-tab-view {
height: 100%;
background-color: #F9F9F9;
}
.page-view {
padding: 16rpx;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
padding: 16rpx;
border-radius: 8rpx;
}
.class-item:last-child {
min-height: 100vh;
}
.item-title {
font-size: 28rpx;
color: #333333;
font-weight: bold;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.thumb-box {
width: 100%;
display: flex;
margin-top: 20rpx;
}
.contetn-box {
margin-left: 24rpx;
flex: 1;
.item-menu-name {
margin-top: 8rpx;
font-weight: normal;
font-size: 28rpx;
color: #333333;
}
.info {
margin: 30rpx 0 40rpx 0;
font-size: 24rpx;
color: #666;
}
.price-box {
display: flex;
align-items: center;
justify-content: space-between;
}
}
.item-menu-image {
width: 200rpx;
height: 200rpx;
}
</style>
更多推荐
所有评论(0)