UniApp 商品分类左右联动技术文档
一、功能说明
实现 商品分类左右联动 效果:
-
点击左侧分类 → 右侧自动滚动到对应分类内容;
-
滚动右侧内容 → 左侧自动高亮当前分类。
二、核心结构
<scroll-view class="menuLeft" scroll-y><view v-for="(item,index) in menuList" @click="changeCurrent(index)" :class="{currentMenu: currentIndex==index}">{{item.name}}</view>
</scroll-view><scroll-view class="menuRight" scroll-y :scroll-top="scrollTop" @scroll="scrollRight"><view class="menuDetailBox" v-for="(item,index) in menuList" :key="index"><view class="title">{{item.name}}</view><view v-for="menu in item.children" class="menu">{{menu.name}}</view></view>
</scroll-view>
三、主要逻辑
1️⃣ 初始化高度计算(onLoad)
获取右侧每个分类区块高度,并计算累计值:
query.selectAll('.menuDetailBox').boundingClientRect(data => {this.newData = data.map((item, i) => {item.offsetHeight = data.slice(0, i + 1).reduce((sum, cur) => sum + cur.height, 0)return item})
}).exec()
2️⃣ 点击左侧分类
根据预计算的高度滚动右侧内容:
changeCurrent(index) {this.currentIndex = indexthis.scrollTop = this.newData[index - 1]?.offsetHeight || 0
}
3️⃣ 右侧滚动时联动左侧
监听滚动距离,动态更新左侧高亮:
scrollRight(e) {const top = e.detail.scrollTopfor (let i = 0; i < this.newData.length; i++) {if (top < this.newData[i].offsetHeight) {this.currentIndex = ibreak}}
}
四、样式要点
-
左侧固定宽度
200rpx,右侧自适应。 -
当前分类高亮(加粗 / 红条标识)。
-
右侧滚动视图需开启
scroll-y="true"。
.currentMenu { font-weight: 700; }
.line::before { background-color: red; }
五、总结
-
使用
scrollTop+ 高度累计实现左右同步滚动。 -
左右两栏通过
currentIndex实现联动高亮。 -
简单可靠,适用于电商、分类导航场景。
完整代码
<template><view class="menuBox"><view class="menuContent"><scroll-view scroll-y="true" class="menuLeft"><view class="menuType" @click="changeCurrent(index)" v-for="(item,index) in menuList":class="{currentMenu:currentIndex==index}">{{item.name}}</view><view class="line" :style="{top:currentIndex*90 +'rpx'}"></view></scroll-view><scroll-view :scroll-top="scrollTop" @scroll="scrollRight" class="menuRight" scroll-y="true"><div class="menuDetailBox" v-for="(item,index) in menuList" :key="index"><div class="title">{{item.name}}</div><div class="menu" v-for="(menu,index) in item.children" :key="index"><div class="d2">123</div><div class="menuName">{{menu.name}}</div></div></div></scroll-view></view></view>
</template><script>export default {data() {return {newData:[], // 拿到右侧所有的商品列表元素scrollTop: 0,currentIndex: 0,menuList: [{"id": 1,"name": "手机数码","children": [{"id": 101,"name": "手机"},{"id": 105,"name": "数码相机"}]},{"id": 2,"name": "家用电器","children": [{"id": 201,"name": "电视机"},{"id": 204,"name": "冰箱"},{"id": 205,"name": "吸尘器"}]},{"id": 3,"name": "服饰鞋包","children": [{"id": 301,"name": "男装"},{"id": 302,"name": "女装"},{"id": 303,"name": "运动鞋"},{"id": 304,"name": "箱包"},{"id": 305,"name": "配饰"}]},{"id": 4,"name": "美妆个护","children": [{"id": 401,"name": "面部护肤"},{"id": 402,"name": "彩妆"},{"id": 403,"name": "洗发护发"},{"id": 404,"name": "身体护理"},{"id": 405,"name": "香水"}]},{"id": 5,"name": "母婴玩具","children": [{"id": 501,"name": "奶粉"},{"id": 502,"name": "尿不湿"},{"id": 503,"name": "婴儿用品"},{"id": 504,"name": "儿童玩具"},{"id": 505,"name": "孕妇用品"}]},{"id": 6,"name": "食品饮料","children": [{"id": 601,"name": "休闲零食"},{"id": 602,"name": "粮油调味"},{"id": 603,"name": "饮料酒水"},{"id": 604,"name": "冲调速食"},{"id": 605,"name": "生鲜水果"}]},{"id": 7,"name": "家居家装","children": [{"id": 701,"name": "家具"},{"id": 702,"name": "灯具"},{"id": 703,"name": "床上用品"},{"id": 704,"name": "厨房用品"},{"id": 705,"name": "装饰摆件"}]},{"id": 8,"name": "运动户外","children": [{"id": 801,"name": "跑步装备"},{"id": 802,"name": "健身器材"},{"id": 803,"name": "户外鞋服"},{"id": 804,"name": "骑行装备"},{"id": 805,"name": "垂钓用品"}]},{"id": 9,"name": "汽车用品","children": [{"id": 901,"name": "机油"},{"id": 902,"name": "轮胎"},{"id": 903,"name": "车载电器"},{"id": 904,"name": "清洁用品"},{"id": 905,"name": "维修配件"}]},{"id": 10,"name": "图书文娱","children": [{"id": 1001,"name": "文学小说"},{"id": 1002,"name": "少儿读物"},{"id": 1003,"name": "教育考试"},{"id": 1004,"name": "艺术设计"},{"id": 1005,"name": "音像制品"}]}]}},methods: {changeCurrent(index) {this.currentIndex = indexthis.scrollTop = this.newData[index-1]?.offsetHeight || 0},scrollRight(e) {// console.log(e.detail.scrollTop,'scrollRight');const query = uni.createSelectorQuery().in(this);for (let i = 0; i < this.newData.length; i++) {let el = this.newData[i]if (e.detail.scrollTop < el.offsetHeight) {this.currentIndex = ireturn true}}}},onLoad() {uni.setNavigationBarTitle({title: '你好'})const query = uni.createSelectorQuery().in(this);query.selectAll('.menuDetailBox').boundingClientRect(data => {console.log('所有分类项:', data);this.newData = data.map((item, index) => {function fgfg(total) {let sum = 0for (var i = 0; i <= total; i++) {sum += data[i].height}return sum}item.offsetHeight = fgfg(index)return item})}).exec();}}
</script><style lang="scss" scoped>.menuBox {height: calc(100vh);display: flex;flex-direction: column;border: 1px solid #ccc;margin: 0 10rpx;.menuContent {height: calc(100vh);overflow: auto;position: relative;display: flex;position: sticky;top: 0rpx;.menuLeft {float: left;position: relative;width: 200rpx;background-color: #f5f5f5;.menuType {transition: all 0.4s;position: relative;z-index: 999999999;height: 90rpx;display: flex;align-items: center;padding-left: 20rpx;&.currentMenu {font-weight: 700;}}}.menuRight {flex: 1;background-color: #fff;.menuDetailBox {.title {height: 60rpx;display: flex;align-items: center;}.menu {background-color: skyblue;height: 100rpx;display: flex;justify-content: center;&:not(:nth-last-of-type(1)) {margin-bottom: 20rpx;}.d2 {width: 60rpx;height: 60rpx;}.menuName {flex: 1;margin-left: 20rpx;}}}}}}.line {position: absolute;left: 0;top: 0;height: 90rpx;width: 100%;background-color: #fff;z-index: 999;transition: all 0.4s;&::before {content: '';position: absolute;left: 0;top: 0;height: 90rpx;width: 4rpx;background-color: red;z-index: 999;transition: all 0.4s}}
</style>
