<template> <view class="container"> <view class="fication-search"> <input type="text" value="" placeholder="请输入您要搜索的内容"/><image class="search-icon" src="../../static/images/search.png" mode=""></image> </view> <!-- 滚动区域 --> <view class="scroll-panel" id="scroll-panel"> <view class="list-box"> <view class="left"> <scroll-view scroll-y="true" :style="{ height: scrollHeight + 'px' }" :scroll-into-view="leftIntoView"> <view class="item" v-for="(item, index) in leftArray" :key="index" :class="{ active: index == leftIndex }" :id="'left-' + index" :data-index="index" @tap="leftTap" > <view class="activelink"></view> <text class="item-name">{{ item }}</text> </view> </scroll-view> </view> <view class="main"> <scroll-view scroll-y="true" :style="{ height: scrollHeight + 'px' }" @scroll="mainScroll" :scroll-into-view="scrollInto" scroll-with-animation="true"> <view class="item main-item" v-for="(item, index) in mainArray" :key="index" :id="'item-' + index"> <view class="title"> <view>{{ item.title }}</view> </view> <view class="goods" v-for="(item2, index2) in item.list" :key="index2"> <view class="orderlist-list"> <view class="list-left"> <image src="../../static/images/store_bg.png" mode=""></image> </view> <view class="list-right"> <view class="list-name">香辣火锅</view> <view class="list-ping"> <u-rate :count="count" active-color="#FFB800" inactive-color="#E0E0E0" size='32' v-model="value"></u-rate> <text>{{4.8}}分</text> </view> <view class="list-meuns"><text>销量{{99}}+</text><text>配送费¥{{3}}</text><text>距离{{1.2}}km</text></view> </view> </view> </view> </view> <view class="fill-last" :style="{ height: fillHeight + 'px' }"></view> </scroll-view> </view> </view> </view> </view> </template> <script> export default{ data() { return { scrollHeight: 400, scrollTopSize: 0, fillHeight: 0, // 填充高度,用于最后一项低于滚动区域时使用 leftArray: [], mainArray: [], topArr: [], leftIndex: 0, scrollInto: '', count: 5, value: 4 }; }, computed: { /* 计算左侧滚动位置定位 */ leftIntoView() { return `left-${this.leftIndex > 3 ? this.leftIndex - 3 : 0}`; } }, mounted() { /* 等待DOM挂载完成 */ this.$nextTick(() => { /* 在非H5平台,nextTick回调后有概率获取到错误的元素高度,则添加200ms的延迟来减少BUG的产生 */ setTimeout(() => { /* 等待滚动区域初始化完成 */ this.initScrollView().then(() => { /* 获取列表数据,你的代码从此处开始 */ this.getListData(); }); }, 200); }); }, methods: { /* 初始化滚动区域 */ initScrollView() { return new Promise((resolve, reject) => { let view = uni.createSelectorQuery().select('#scroll-panel'); view.boundingClientRect(res => { this.scrollTopSize = res.top; this.scrollHeight = res.height; this.$nextTick(() => { resolve(); }); }).exec(); }); }, /* 获取列表数据 */ getListData() { // Promise 为 ES6 新增的API ,有疑问的请自行学习该方法的使用。 new Promise((resolve, reject) => { /* 因无真实数据,当前方法模拟数据。正式项目中将此处替换为 数据请求即可 */ uni.showLoading(); setTimeout(() => { let [left, main] = [[], []]; for (let i = 0; i < 12; i++) { left.push(`${i + 1}类商品`); let list = []; let r = Math.floor(Math.random() * 10); r = r < 1 ? 3 : r; for (let j = 0; j < r; j++) { list.push(j); } main.push({ title: `第${i + 1}类商品标题`, list }); } // 将请求接口返回的数据传递给 Promise 对象的 then 函数。 resolve({ left, main }); }, 1000); }).then(res => { console.log('-----------请求接口返回数据示例-------------'); console.log(res); uni.hideLoading(); this.leftArray = res.left; this.mainArray = res.main; // DOM 挂载后 再调用 getElementTop 获取高度的方法。 this.$nextTick(() => { this.getElementTop(); }); }); }, /* 获取元素顶部信息 */ getElementTop() { new Promise((resolve, reject) => { let view = uni.createSelectorQuery().selectAll('.main-item'); view.boundingClientRect(data => { resolve(data); }).exec(); }).then(res => { let topArr = res.map(item => { return item.top - this.scrollTopSize; /* 减去滚动容器距离顶部的距离 */ }); this.topArr = topArr; /* 获取最后一项的高度,设置填充高度。判断和填充时做了 +-20 的操作,是为了滚动时更好的定位 */ let last = res[res.length - 1].height; if (last - 20 < this.scrollHeight) { this.fillHeight = this.scrollHeight - last + 20; } }); }, /* 主区域滚动监听 */ mainScroll(e) { let top = e.detail.scrollTop; let index = 0; /* 查找当前滚动距离 */ for (let i = this.topArr.length - 1; i >= 0; i--) { /* 在部分安卓设备上,因手机逻辑分辨率与rpx单位计算不是整数,滚动距离与有误差,增加2px来完善该问题 */ if (top + 2 >= this.topArr[i]) { index = i; break; } } this.leftIndex = index < 0 ? 0 : index; }, /* 左侧导航点击 */ leftTap(e) { let index = e.currentTarget.dataset.index; this.scrollInto = `item-${index}`; } } }; </script> <style lang="scss"> page, .container { width: 100vw; height: 100%; } /* 容器 */ .container { display: flex; flex-direction: column; flex-wrap: nowrap; justify-content: flex-start; align-items: flex-start; align-content: flex-start; // & > view { // width: 100%; // } .scroll-panel { flex-grow: 1; height: 0; overflow: hidden; } } .fication-search{ width: 686rpx; height: 64rpx; margin: 12rpx auto; background: #F5F6F7; border-radius: 32rpx 32rpx 32rpx 32rpx; display: flex; flex-direction: row; align-items: center; justify-content: space-between; } .fication-search input{ padding-left: 24rpx; font-size: 12px; font-family: PingFang SC-Regular, PingFang SC; font-weight: 400; color: #B5B5B5; } .fication-search input::-webkit-input-placeholder{ color:red!important; } .search-icon{ width: 24rpx; height: 24rpx; margin-right: 30rpx; } .list-box { display: flex; flex-direction: row; flex-wrap: nowrap; justify-content: flex-start; align-items: flex-start; align-content: flex-start; font-size: 28rpx; .left { width: 196rpx; background: #F5F6F7; line-height: 112rpx; box-sizing: border-box; font-size: 28rpx; font-family: PingFang SC-Medium, PingFang SC; .item { position: relative; display: flex; &:not(:first-child) { margin-top: 1px; &::after { content: ''; display: block; height: 0; // border-top: #d6d6d6 solid 1px; width: 620upx; position: absolute; top: 0px; right: 0; transform: scaleY(0.5); /* 1px像素 */ } } .item-name{ padding-left: 32rpx; } &.active { color: #F7433D; background-color: #fff; } &.active .activelink{ width: 8rpx; height: 48rpx; margin-top: 32rpx; padding-left: 0; background-color: #F7433D; border-radius: 2px 2px 2px 2px; } } .fill-last { height: 0; width: 100%; background: none; } } .title { line-height: 64rpx; font-size: 16px; font-family: PingFang SC-Bold, PingFang SC; font-weight: bold; color: #000000; padding: 8rpx 0; background-color: #fff; position: sticky; top: 0; z-index: 19; } } .orderlist-list{ width: 526rpx; height: 200rpx; background: #FFFFFF; margin: 0 auto; display: flex; border-bottom: 2rpx solid #F5F6F7; // border-radius: 8px 8px 8px 8px; } .list-left{ margin: 24rpx; width: 152rpx; height: 152rpx; border-radius: 4px 4px 4px 4px; overflow: hidden; } .list-left image{ width: 100%; height: 100%; } .list-right{ flex: 1; display: flex; flex-direction: column; } .list-name{ margin-top: 24rpx; font-size: 16px; font-family: PingFang SC-Medium, PingFang SC; font-weight: 500; color: #000000; line-height: 19px; } .list-ping{ margin: 20rpx 0; display: flex; font-size: 12px; font-family: PingFang SC-Regular, PingFang SC; font-weight: 400; color: #FFB800; } .list-ping text{ margin-left: 6rpx; } .list-meuns{ margin-bottom: 28rpx; font-size: 12px; font-family: PingFang SC-Medium, PingFang SC; font-weight: 500; color: #999999; } </style>