双联滚动在做商城时经常会用到
本例采用左右scrollview组件实现双联滚动
效果图如下
本例右部采用单排显示,感觉效果比双排好,可完整显示图片,以及不会因为标题长短不一出现显示不美观
注意部分组件为自定义组件,您可以删除或更换自己的组件,本例仅为示例代码。
<template>
<view class="bg color-555">
<mo-nav isBarHeight>
<view class="flex flex-row bgColor-whites flex-items-center" @click="$mo.pageto('/pagesA/shop/carlists')">
<view class="headbtn flex flex-row">
<mo-icon name="gouwuche" size="20"></mo-icon>
</view>
<view class="marlr10">购物车</view>
</view>
</mo-nav>
<view class="mo-wrap">
<view class="mo-search-box">
<mo-search v-model="sotext" bgColor="#ffffff" @search="soso" @confirm="soso"></mo-search>
</view>
<view class="mo-menmo-wrap">
<scroll-view scroll-y scroll-with-animation class="mo-tab-view menmo-scroll-view bg" :scroll-top="scrollTop">
<view v-for="(item,index) in categoryBox" :key="index" class="mo-tab-item"
:class="[current==index ? 'mo-tab-item-active' : '']"
@tap.stop="swichMenu(index)">
<text class="flex-wrap any-font-size-14">{
{item.name}}</text>
</view>
<mo-gap :gap="60"></mo-gap>
</scroll-view>
<scroll-view scroll-y scroll-with-animation class="right-box flex-1" :scroll-top="scrollRightTop" @scroll="rightScroll">
<view class="page-view">
<view class="class-item" :id="'item' + index"
v-for="(item , index) in categoryBox" :key="index">
<view class="item-title">
<view v-if="item.thumb" class="width-bb"
@click="item.click>0?$mo.pageto('/pagesA/shop/listso?id='+item.id):''">
<image :src="$comjs.api.uploadsUrl+item.thumb" mode="widthFix" class="width-bb" style="height: auto;"></image>
</view>
<view class="any-font-size-14" @click="item.click>0?$mo.pageto('/pagesA/shop/listso?id='+item.id):''">{
{item.name}}</view>
</view>
<view v-for="(items,indexs) in item.data" :key="indexs">
<view class="martb10 flex flex-row flex-items-center" @click="$mo.pageto('/pagesA/shop/show?id='+items.id+'&catid='+items.catid)">
<view>
<image :src="items.thumb?$comjs.api.uploadsUrl+items.thumb:'../../static/300.png'" mode="aspectFill" class="thumb"></image>
</view>
<view class="flex flex-column flex-1 any-padding-10">
<view class="any-font-size-12 flex-wrap">{
{items.title}}</view>
<view class="flex flex-between martb10">
<view class="color-red">
<text class="any-font-size-14">¥</text>
<text class="any-font-size-16">{
{items.money}}</text>
</view>
<view class="text-right">
<mo-icon name="shaogouwu" size="20"></mo-icon>
</view>
</view>
</view>
</view>
</view>
<view v-if="!item.data.length" class="martb10 text-center color-ccc">- 暂无商品 -</view>
<view v-else-if="item.data.length>=6&&item.click>0" class="martb10 text-center"
@click="$mo.pageto('/pagesA/shop/listso?id='+item.id)">查看更多</view>
<mo-gap :gap="10"></mo-gap>
<mo-divider></mo-divider>
</view>
</view>
<mo-gap :gap="60"></mo-gap>
</scroll-view>
</view>
<!-- tabbar占位高度 -->
</view>
<mo-loading ref="loading"></mo-loading>
</view>
</template>
<script>
var num = 0;
var dsq = null;
export default {
components: {
},
data() {
return {
//systemInfo:base.systemInfo,
isPopup:false,
PrimaryColor:'#fe461d',//主题色
categoryBox:[],//分类商品
current: 0,
scrollTop:0,
specGoodsData:{},//选择的规格数据
// ***滑动联动所需参数***
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动高度
timer: null, // 定时器
flag:false,//禁止current两次赋值
flag2:true,//是否禁止右侧滑动
c_id:'',//跳转的参数--定位分类id位置
sotext:''
}
},
onLoad(e) {
// this.c_id = e.c_id
//this.c_id = 9 //跳转的参数--定位分类id位置
},
onReady() {
this.getMenuItemTop()
},
onShow(){
let that=this;
//加载药品列表
this.getgoodsList()
// #ifndef MP-ALIPAY
num = 0;
this.DSQ()
// #endif
},
methods: {
soso(){
let that=this;
that.$comjs.pageto("/pagesA/shop/listso",{},function(res){
res.eventChannel.emit('sotext', { sotext:that.sotext})
});
},
getgoodsList() {
let that=this;
that.$comjs.http(that,that.$comjs.api.shop,{},false,redata=>{
that.categoryBox=redata.data;
// 如果有传分类c_id参数-滑动到对应的选项
if(that.sotext){
let currItem=that.categoryBox.find(item=>{
return item.data.find(items=>{
return items.name==that.sotext;
})
})
if(currItem){
console.log(currItem,11111)
that.c_id=currItem.id;
that.categoryBox.forEach((item,index)=>{
if(item.id == that.c_id){
let time =500 //实际接口这里数字可以调小点
// 等待页面渲染完成
setTimeout(()=>{
if(index>=2){
that.swichMenu(index-1)
}
},time)
setTimeout(()=>{
that.swichMenu(index)
},time+200)
}
})
}
}
});
},
// 点击左边的栏目切换
async swichMenu(index) {
console.log('swichMenu')
// #ifdef MP-ALIPAY
if(index == this.current) return ;
this.current = index;
// 如果为0,意味着尚未初始化
if(this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menmo-scroll-view', 'menuHeight');
await this.getElRect('mo-tab-item', 'menuItemHeight');
}
// 将菜单菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
// #endif
// #ifndef MP-ALIPAY
if(this.arr.length == 0) {
await this.getMenuItemTop();
}
if (index == this.current) return;
this.scrollRightTop = this.oldScrollTop;
this.$nextTick(function(){
this.scrollRightTop = this.arr[index];
this.current = index;
this.flag = true
this.flag2 = false //禁止右侧滑动
this.leftMenuStatus(index);
num = 0
})
// #endif
},
DSQ(isClear){
// 解决点击分类,左侧多次滚动
dsq && clearInterval(dsq)
dsq = setInterval(()=>{
num+=100
if(num>500){
this.flag2 = true
}
// console.log(num)
},100)
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({size: true},res => {
// 如果节点尚未生成,res值为null,循环调用执行
if(!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return ;
}
this[dataVal] = res.height;
}).exec();
})
},
// *********************************************** 微信小程序 S *****************************************************************
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
console.log('左侧滚动')
// #ifndef MP-ALIPAY
if(this.flag){
this.current = index;
this.flag = false
console.log(index)
}
// 如果为0,意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menmo-scroll-view', 'menuHeight');
await this.getElRect('mo-tab-item', 'menuItemHeight');
}
console.log(this.menuHeight,'this.menuHeight')
console.log(this.menuItemHeight,'this.menuItemHeight')
// 将菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
// #endif
},
// 获取右边菜单每个item到顶部的距离
getMenuItemTop() {
// #ifndef MP-ALIPAY
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
if(!rects.length) {
setTimeout(() => {
this.getMenuItemTop();
}, 10);
return ;
}
rects.forEach((rect) => {
// 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
this.arr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
// #endif
},
// 右边菜单滚动
async rightScroll(e) {
// #ifndef MP-ALIPAY
if(this.flag2 && !this.flag){
console.log('右侧滚动')
this.oldScrollTop = e.detail.scrollTop;
if(this.arr.length == 0) {
await this.getMenuItemTop();
}
if(this.timer) return ;
if(!this.menuHeight) {
await this.getElRect('menmo-scroll-view', 'menuHeight');
}
setTimeout(() => { // 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
for (let i = 0; i < this.arr.length; i++) {
let height1 = this.arr[i];
let height2 = this.arr[i + 1];
// 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
this.flag = true
this.leftMenuStatus(i);
return ;
}
}
}, 10)
}
// #endif
},
// *********************************************** 微信小程序 E *****************************************************************
},
}
</script>
<style lang="scss" scoped>
.headbtn {
border-radius: 20px;
margin-left: 15px;
}
.bg {
background: #edf4f7;
}
.leftque{color:red;display: inline-block;margin-right:2px;border-radius:50%;background-color: #fff;border:1px solid red;width:10px;height:10px;padding:2px;}
.mo-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.mo-search-box {
padding: 0rpx 20rpx 15rpx 20rpx;
margin-top:140rpx;
}
.mo-search-inner {
background-color: #f5f5f5;
border-radius: 100rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 10rpx 16rpx;
}
.mo-search-text {
color: #999;
margin-left: 10rpx;
}
.mo-menmo-wrap {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
overflow: hidden;
}
.mo-tab-view {
width: 160rpx;
height: 100%;
}
.mo-tab-item {
width: 160rpx;
// height: 110rpx;
padding:15px 8px;
box-sizing: border-box;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
font-weight: 400;
line-height: 1;
position: relative;
z-index: 10;
.cartNumber{
position: absolute;
z-index: 10;
border-radius: 34rpx;
top: 8rpx;
right: 8rpx;
// width: 34rpx;
padding: 0 10rpx;
height: 34rpx;
line-height: 34rpx;
background: #508c22;
color: #fff;
}
text{
line-height: 1.2;
}
}
.mo-tab-item-active {
position: relative;
z-index: 10;
color:#333;
background: #fff;
.title{
color: #D54F4F
}
}
.mo-tab-item-active::before {
content: "";
position: absolute;
z-index: 10;
border-left: 4rpx solid #D54F4F;
height: 32rpx;
left: 0;
top: 50%;
transform: translateY(-50%);
}
.mo-tab-view {
height: 100%;
}
.right-box {
/* #ifdef MP-ALIPAY */
/* #endif */
/* #ifndef MP-ALIPAY */
/* #endif */
.thumb{width:68px;height:68px;border-radius:8px;}
}
.page-view {
// padding: 16rpx;
}
.class-item {
// margin-bottom: 30rpx;
background-color: #fff;
padding:10px 5px;
border-radius: 8rpx;
/* #ifndef MP-ALIPAY */
padding-bottom: 20rpx;
/* #endif */
}
.item-title {
color: #333;
text{
color: #999;
}
}
.item-menmo-name {
font-weight: normal;
color: #333;
}
.item-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-wrap: wrap;
}
</style>
基本VUE3,兼容VUE2的UI组件正在开发中,届时免费提供大家使用
以下为正在使用本组件的一只个人项目管理小程序案例