目前市面上很多小程序或者app都出现点餐或者商城类的,这里使用到了uniapp的内置组件scroll-view
注意:
这个组件用的时候要注意需要给滚动区域设置高度,滚动才能生效
需要给二级分类添加id,并且值不能已数字开头,可以加上类似于 :id="'item-' + index"与js中的api对应
实现的效果,如图所示
因为是从完整的代码中取得,若有样式问题请自行搭建补充
搭建html
左侧菜单品类
<!-- 左侧一级分类 -->
<scroll-view
scroll-y="true"
:scroll-with-animation="false"
class="leftColumn"
>
<view>
<view
class="menu-item"
v-for="(good, index) in orderlist"
:key="good.categoryCode"
:class="{ current: index === leftCilck }"
:style="{ color: index === leftCilck ? iconColor : '#000' }"
@click="clickMenuItem(index)"
>
<image
:showLoading="true"
v-if="good.categoryImage"
:src="good.categoryImage"
class="firstLevelImage"
></image>
<span v-if="good.categoryName">{{ good.categoryName }}</span>
</view>
</view>
</scroll-view>
右侧菜单二级分类
<!-- 右侧联动二级分类 -->
<scroll-view
ref="rightList"
scroll-y="true"
:scroll-with-animation="false"
@scroll="mainScroll"
:scroll-into-view="scrollInto"
>
<view class="'foods-wrapper'">
<view
class="food-list-hook"
v-for="(item, index) in orderlist"
:id="'item-' + index"
:key="index"
>
<view class="title">{{ item.categoryName }}</view>
<view class="box" v-for="v in item.goods" :key="v.goodsCode">
<view class="box_left">
<view class="box_left_img">
<image :src="v.imageUrl" mode="aspectFill"></image>
</view>
</view>
<view class="box_right">
<text class="box_right_title">{{ v.goodsName }}</text>
<span class="describe" v-html="v.goodsDesc"></span>
<view class="box_right_price">
<view class="box_right_price_left">
¥{{ v.amount }}
<text class="provinciate_price">¥{{ v.price }}</text>
</view>
<!-- 多规格产品 -->
<view
v-if="v.goodsType == '2' || v.goodsType == '3'"
class="box_right_price_right"
@tap="specifications(v)"
>
<text>选规格</text>
</view>
<!-- 单品 -->
<u-number-box
:asyncChange="true"
v-if="v.goodsType == '1'"
:min="0"
:max="v.maxQuantity"
:disablePlus="shopVal == maxQuantity"
v-model="v.num"
@change="
(value) => {
menuChange(v, value);
}
"
>
<view slot="minus" class="minus">
<u-icon name="minus" size="12"></u-icon>
</view>
<text
slot="input"
style="text-align: center"
class="input"
>{{ v.num }}</text
>
<view slot="plus" class="plus">
<u-icon name="plus" color="#FFFFFF" size="12"></u-icon>
</view>
</u-number-box>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
js部分-让滚动区域能够正常滚动跳转
async onLoad() {
await this.onRightListFetch();
await this.initScrollView();
},
methods:{
clickMenuItem(index) {
this.leftCilck = index;
this.scrollInto = "item-" + index;
},
/* 主区域滚动监听 */
mainScroll(e) {
clearTimeout(this.mainThrottle);
this.mainThrottle = setTimeout(() => {
scrollFn();
}, 10);
let scrollFn = () => {
let top = e.detail.scrollTop;
let index = 0;
/* 查找当前滚动距离 */
this.topArr.forEach((item, id) => {
if (top + 140 >= item) {
index = id;
this.leftCilck = index < 0 ? 0 : index;
}
});
};
},
/* 初始化滚动区域 */
initScrollView() {
return new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().select(".goods");
view
.boundingClientRect((res) => {
this.scrollTopSize = res.top;
this.scrollHeight = res.height;
setTimeout(() => {
resolve();
}, 100);
})
.exec();
});
},
/* 获取元素顶部信息 */
getElementTop() {
new Promise((resolve) => {
let view = uni.createSelectorQuery().selectAll(".food-list-hook");
view
.boundingClientRect((data) => {
resolve(data);
})
.exec();
}).then((res) => {
let topArr = res.map((item) => {
return item.top - this.scrollTopSize; /* 减去滚动容器距离顶部的距离 */
});
this.topArr = topArr;
});
},
clickMenuItem(index) {
this.leftCilck = index;
this.scrollInto = "item-" + index;
},
// 获取右边数据
async onRightListFetch() {
this.$nextTick(() => {
this.initScrollView();
});
},
}
变量部分
data() {
return {
mainThrottle: null,
topArr: [],
scrollInto: "", //左侧定位
scrollTopSize: "",
scrollHeight: "",
leftCilck: 0, //左侧点击菜单
listHeight: [], //右侧每一分类高度集合
scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
tops: [], // 所有右侧分类li的top组成的数组 (列表第一次显示后就不再变化)
cityInfo: null,
menuList: [],
indexList: [],
selection_goods: {}, //选择规格的元素
orderlist: [],
mobilePhoneModel: "",
};
}
css部分
.goods {
display: flex;
width: 100%;
background: #fff;
overflow: hidden;
font-size: 28rpx;
.leftColumn {
width: 160rpx;
background: #f3f5f7;
}
.menu-wrapper {
width: 160rpx;
background: #f3f5f7;
height: 100%;
}
.listMenu-wrapper {
width: 160rpx;
background: #f3f5f7;
padding-bottom: 90rpx;
box-sizing: border-box;
}
.menu-item {
image {
height: 60rpx;
width: 60rpx;
}
display: flex;
min-height: 120rpx;
padding: 20rpx;
box-sizing: border-box;
line-height: 28rpx;
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 24rpx;
text-align: center;
&.current {
display: flex;
align-items: center;
background: #fff;
font-weight: 700;
}
.icon {
display: inline-block;
vertical-align: top;
width: 24rpxx;
height: 24rpx;
margin-right: 4rpx;
background-size: 24rpx 24rpx;
background-repeat: no-repeat;
}
.text {
display: table-cell;
width: 112rpx;
vertical-align: middle;
font-size: 24rpx;
}
}
}
.foods-wrapper {
padding: 20rpx 30rpx;
box-sizing: border-box;
width: calc(100vw - 160rpx);
}
.box {
margin-bottom: 60rpx;
display: flex;
position: relative;
border-radius: 40rpx;
.box_left {
.box_left_img {
width: 180rpx;
height: 180rpx;
position: relative;
image {
border-radius: 11rpx;
}
.box_left_img_text {
width: 63rpx;
height: 36rpx;
background: #05b6f6;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: 400;
color: #fdfeff;
line-height: 36rpx;
text-align: center;
padding: 3rpx 5rpx;
position: absolute;
box-sizing: border-box;
top: -10rpx;
right: -5rpx;
}
}
}
::v-deep .box_right {
margin-left: 20rpx;
display: flex;
justify-content: space-around;
flex-direction: column;
flex: 1;
.box_right_title {
font-size: 30rpx;
font-weight: 700;
color: #353535;
display: inline-block;
margin: 10rpx 0;
width: 100%;
}
.box_right_price {
display: flex;
align-items: center;
justify-content: space-between;
line-height: 1;
width: 100%;
margin-top: 14rpx;
.box_right_price_left {
font-size: 30rpx;
color: #ff2b21;
.provinciate_price {
color: #999;
font-size: 22rpx;
margin-left: 12rpx;
text-decoration: line-through;
line-height: 46rpx;
}
}
.box_right_price_right {
width: 101rpx;
height: 47rpx;
border-radius: 17rpx;
font-size: 24rpx;
font-weight: 400;
color: #fdfeff;
line-height: 47rpx;
text-align: center;
position: relative;
}
}
}
}
.minus {
width: 22px;
height: 22px;
border-width: 1px;
border-color: #e6e6e6;
border-style: solid;
border-top-left-radius: 100px;
border-top-right-radius: 100px;
border-bottom-left-radius: 100px;
border-bottom-right-radius: 100px;
@include flex;
justify-content: center;
align-items: center;
}
.input {
padding: 0 10px;
}
.plus {
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.soldOut {
position: absolute;
width: 100%;
height: 100%;
span {
position: absolute;
top: -20rpx;
left: -20rpx;
color: #fff;
background-color: #d62f35;
padding: 2rpx 6rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
}