uniapp购物车-侧边导航栏-导航栏定位效果demo(整理)

效果图:
在这里插入图片描述

<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/enter/btn_xuanzhong.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>


测试demo
```bash
<template>
	<view class="container">
		<status-bar title="热门店铺"></status-bar>
		<!-- 顶部面板 -->
		<view class="top--panel">
			<!-- <view style="background-color: #ffaa00;text-align: center;font-size: 28rpx;padding: 10px 0;color: #fff;">
				<view>这里顶部内容占位区域,不需要则删除</view>
				<view>可添加需放在页面顶部的内容,比如banner图</view>
			</view> -->
			<view class="viewTop padding-bottom20">
				<view class="viewBox margin-left30 margin-right30 row-me row-center posRelative">
					<image src="../static/img/icon_search.png" mode="aspectFit" class="width30 height30 margin-left20">
					</image>
					<input type="text" v-model="form.keyword" placeholder="搜索店铺名称" placeholder-style="color:#999;"
						confirm-type="search" @confirm="doSearch" class="putBox font-size26 color222 margin-left20" />
					<view class="posAbsolute clearBox" v-if="form.keyword.length>0" @click="clickClear">×</view>
				</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' }">
						<view class="item row-me row-center" v-for="(item, index) in leftArray" :key="index"
							:class="{ active: item.id == leftId }" :id="'left-' + index" :data-index="index"
							@tap="leftTap(item)">
							<view class="borderL" v-if="item.id == leftId"></view>
							<view class="flex1">{{ item.name }}</view>
						</view>
					</scroll-view>
				</view>
				<view class="main">
					<scroll-view scroll-y="true" :style="{ height: scrollHeight + 'px' }" :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="goods">
								<image src="../static/img/zhanwietu.png" mode="aspectFill">
								</image>
								<view>
									<view class="font-bold font-size30 color222">{{ index + 1 }}个商品标题</view>
									<view class="font-size24 color666 margin-top10 line1">位置:二楼108号</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: [],
				leftId: '',
				scrollInto: '',
				form: {},
			};
		},
		computed: {

		},
		mounted() {
			/* 等待DOM挂载完成 */
			this.$nextTick(() => {
				/* 在非H5平台,nextTick回调后有概率获取到错误的元素高度,则添加200ms的延迟来减少BUG的产生 */
				setTimeout(() => {
					/* 等待滚动区域初始化完成 */
					this.initScrollView().then(() => {
						/* 获取列表数据,你的代码从此处开始 */
						this.getListData();
					});
				}, 200);
			});
		},
		methods: {
			// 清空搜索
			clickClear() {
				this.form.keyword = '';
			},
			/* 初始化滚动区域 */
			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() {
				this.leftArray = [{
						name: '测试1',
						id: 1,
					},
					{
						name: '测试2',
						id: 2,
					},
					{
						name: '测试3',
						id: 3,
					},
					{
						name: '测试4',
						id: 4,
					},
					{
						name: '测试5',
						id: 5,
					},
					{
						name: '测试1',
						id: 6,
					},
					{
						name: '测试2',
						id: 7,
					},
					{
						name: '测试3',
						id: 8,
					},
					{
						name: '测试4',
						id: 9,
					},
					{
						name: '测试5',
						id: 10,
					},

					{
						name: '测试1',
						id: 11,
					},
					{
						name: '测试2',
						id: 12,
					},
					{
						name: '测试3',
						id: 13,
					},
					{
						name: '测试4',
						id: 14,
					},
					{
						name: '测试5',
						id: 15,
					},

					{
						name: '测试1',
						id: 16,
					},
					{
						name: '测试2',
						id: 17,
					},
					{
						name: '测试3',
						id: 18,
					},
					{
						name: '测试4',
						id: 19,
					},
					{
						name: '测试5',
						id: 20,
					},
				];
				var newObj = {
					name: '全部',
					id: 0,
				}
				this.leftArray.unshift(newObj);

				this.leftId = this.leftArray[0].id;
				this.mainArray = [{
						name: '测试1',
						id: 1,
					},
					{
						name: '测试2',
						id: 2,
					},
					{
						name: '测试3',
						id: 3,
					},
					{
						name: '测试4',
						id: 4,
					},
					{
						name: '测试5',
						id: 5,
					},
					{
						name: '测试1',
						id: 1,
					},
					{
						name: '测试2',
						id: 2,
					},
					{
						name: '测试3',
						id: 3,
					},
					{
						name: '测试4',
						id: 4,
					},
					{
						name: '测试5',
						id: 5,
					},

				];

				uni.hideLoading();
				return false;

				// 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;
					}
				});
			},

			/* 左侧导航点击 */
			leftTap(row) {
				// let index = e.currentTarget.dataset.index;
				// this.scrollInto = `item-${index}`;
				this.leftId = row.id;
			}
		}
	};
</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: 168rpx;
			background: #F6F7FA;
			line-height: 90rpx;
			box-sizing: border-box;
			font-size: 26rpx;
			color: #999999;

			.item {
				text-align: center;
				// 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 {
					font-weight: bold;
					color: #222222;
					background-color: #fff;
				}

				.borderL {
					// position: absolute;
					width: 6rpx;
					height: 35rpx;
					background: #58207C;
					border-radius: 0rpx 3rpx 3rpx 0rpx;
				}
			}

			.fill-last {
				height: 0;
				width: 100%;
				background: none;
			}
		}

		.main {
			background-color: #fff;
			// padding-left: 20rpx;
			width: 0;
			flex-grow: 1;
			box-sizing: border-box;
			margin-right: 20rpx;
			margin-left: 20rpx;

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

			.item {
				padding: 20rpx;
				box-shadow: 0rpx 3rpx 20rpx rgba(34, 34, 34, 0.1);
				border-radius: 8rpx;
				margin-bottom: 20rpx
			}

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

				&>image {
					width: 100rpx;
					height: 100rpx;
					margin-right: 20rpx;
					border-radius: 8rpx;
				}
			}
		}
	}

	// 头部搜索
	.viewTop {
		background-color: #fff;
		padding-top: 10rpx;
		position: -webkit-sticky;
		position: sticky;
		top: 0;

		.viewBox {
			border: 1px solid #F6F6F6;
			height: 60rpx;
			border-radius: 8rpx;
			background: #F6F7FA;

			.putBox {
				flex: 1;
				height: 60rpx;
				margin-right: 70rpx;
			}

			.clearBox {
				background-color: rgba(0, 0, 0, 0.5);
				color: #fff;
				font-size: 22rpx;
				// padding: 4rpx 6rpx;
				width: 30rpx;
				height: 30rpx;
				line-height: 30rpx;
				right: 20rpx;
				border-radius: 20rpx;
				text-align: center;
			}
		}
	}
</style>

在这里插入图片描述

在这里插入图片描述

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用uniapp checkbox-group实现购物车全选的示例代码: 1. 在template中添加checkbox-group和checkbox组件 ``` <template> <view> <checkbox-group bindchange="selectAllChange"> <checkbox value="all" checked="{{allSelected}}">全选</checkbox> </checkbox-group> <view v-for="(item, index) in cartList"> <checkbox value="{{item.id}}" checked="{{item.selected}}" bindchange="selectChange">{{item.name}}</checkbox> </view> </view> </template> ``` 2. 在script中添加data属性和方法 ``` <script> export default { data() { return { cartList: [], // 购物车列表 allSelected: false // 是否全选 } }, onShow() { // 获取购物车列表 this.getCartList() }, methods: { // 获取购物车列表 getCartList() { // 省略获取购物车列表的代码 }, // 单个商品选中状态改变事件 selectChange(event) { const id = event.target.value const selected = event.detail.value.length > 0 // 更新购物车列表中对应商品的选中状态 this.cartList = this.cartList.map(item => { if (item.id === id) { item.selected = selected } return item }) // 判断是否全选 this.checkAllSelected() }, // 全选状态改变事件 selectAllChange(event) { const selected = event.detail.value.length > 0 // 更新购物车列表中所有商品的选中状态 this.cartList = this.cartList.map(item => { item.selected = selected return item }) // 更新全选状态 this.allSelected = selected }, // 检查是否全选 checkAllSelected() { const selectedList = this.cartList.filter(item => item.selected) this.allSelected = selectedList.length === this.cartList.length } } } </script> ``` 3. 在style中添加样式 ``` <style> .uni-checkbox { margin-right: 10px; } </style> ``` 4. 注意事项 - checkbox-group组件中的value值需要设置为all,并且checked值绑定allSelected变量; - checkbox组件中的value值需要设置为对应商品的id,并且checked值绑定商品的selected属性; - selectChange方法中需要更新对应商品的selected属性,并调用checkAllSelected方法检查是否全选; - selectAllChange方法中需要更新所有商品的selected属性,并更新allSelected变量; - checkAllSelected方法中需要筛选出已选中的商品,判断是否全选。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值