购物车-侧边导航栏-导航栏定位

<template>
	<view class="container">
		<!-- 顶部面板 -->
		<view class="top--panel">
			<!-- 顶部面板,可添加所需要放在页面顶部的内容代码。比如banner图 -->
			<view style="background-color: #ffaa00;text-align: center;font-size: 28rpx;padding: 10px 0;color: #fff;">
				<view>这里顶部内容占位区域,不需要则删除</view>
				<view>可添加需放在页面顶部的内容,比如banner图</view>
			</view>
		</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"
						>
							{{ item }}
						</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">
								<image src="/static/logo.png" mode=""></image>
								<view>
									<view>第{{ index2 + 1 }}个商品标题</view>
									<view class="describe">第{{ index2 + 1 }}个商品的描述内容</view>
									<view class="money">第{{ index2 + 1 }}个商品的价格</view>
								</view>
							</view>
						</view>
						<view class="fill-last" :style="{ height: fillHeight + 'px' }"></view>
					</scroll-view>
				</view>
			</view>
		</view>
		<!-- 底部面板 -->
		<view class="bottom-panel">
			<!-- 底部面板,可添加所需要放在页面底部的内容代码。比如购物车栏目 -->
			<view style="background-color: #ffaa00;text-align: center;font-size: 28rpx;padding: 10px 0;color: #fff;">
				<view>这里底部内容占位区域,不需要则删除</view>
				<view>可添加需放在页面底部的内容,比如购物车栏目</view>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			scrollHeight: 400,
			scrollTopSize: 0,
			fillHeight: 0, // 填充高度,用于最后一项低于滚动区域时使用
			leftArray: [],
			mainArray: [],
			topArr: [],
			leftIndex: 0,
			scrollInto: ''
		};
	},
	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 < 25; 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 {
	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;
	}

	.bottom-panel {
		padding-bottom: 0;
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);
	}
}

.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: 200rpx;
		background-color: #f6f6f6;
		line-height: 80rpx;
		box-sizing: border-box;
		font-size: 32rpx;

		.item {
			padding-left: 20rpx;
			position: relative;
			&:not(:first-child) {
				margin-top: 1px;

				&::after {
					content: '';
					display: block;
					height: 0;
					border-top: #d6d6d6 solid 1px;
					width: 620upx;
					position: absolute;
					top: -1px;
					right: 0;
					transform: scaleY(0.5); /* 1px像素 */
				}
			}

			&.active {
				color: #42b983;
				background-color: #fff;
			}
		}

		.fill-last {
			height: 0;
			width: 100%;
			background: none;
		}
	}
	.main {
		background-color: #fff;
		padding-left: 20rpx;
		width: 0;
		flex-grow: 1;
		box-sizing: border-box;

		.title {
			line-height: 64rpx;
			font-size: 24rpx;
			font-weight: bold;
			color: #666;
			background-color: #fff;
			position: sticky;
			top: 0;
			z-index: 19;
		}

		.item {
			padding-bottom: 10rpx;
			border-bottom: #eee solid 1px;
		}

		.goods {
			display: flex;
			flex-direction: row;
			flex-wrap: nowrap;
			justify-content: flex-start;
			align-items: center;
			align-content: center;
			margin-bottom: 10rpx;

			& > image {
				width: 120rpx;
				height: 120rpx;
				margin-right: 16rpx;
				margin-left: 2px;
			}

			.describe {
				font-size: 24rpx;
				color: #999;
			}

			.money {
				font-size: 24rpx;
				color: #efba21;
			}
		}
	}
}
</style>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值