Taro微信小程序实现 美团购物车小红点动画效果

这篇具有很好参考价值的文章主要介绍了Taro微信小程序实现 美团购物车小红点动画效果。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Taro微信小程序实现 美团购物车小红点动画效果文章来源地址https://www.toymoban.com/news/detail-497584.html

1、mall.vue

<template>
	<view class="container">
		<!-- 头部背景 -->
		<view class="fixed-header">
			<NavBar
				titleTxt=""
				showLeft
				bgColor="transparent"
				textColor="#FFFFFF"
				leftArrowColor="#FFFFFF"
			/>
			<image
				:src="(mallInfo.imgs && mallInfo.imgs[0]) ? mallInfo.imgs[0].imgUrl : `${baseImgHost}/default-img1.png`"
				mode="aspectFill"
				@tap="toMallImgs"
			/>
		</view>
		<view class="relative-container">
			<!-- 商家信息 -->
			<view class="mall-info">
				<view
					class="name-phone flex f-ai-c f-jc-sb"
					@tap="makeCall"
				>
					<view class="left flex f-d-c">
						<text class="fz-40">
							{{ mallInfo.merchantName }}
						</text>
						<text class="fz-24">
							{{ mallInfo.phone }}
						</text>
					</view>
					<image
						:src="`${baseImgHost}/phone3-g.png`"
						mode="aspectFill"
					/>
				</view>
				<view
					class="address flex f-d-c"
					@tap="switchLocation"
				>
					<text class="fz-24 p-name">
						{{ mallInfo.address }}
					</text>
					<text class="fz-24 p-dis">
						距您{{ shortenNumber(mallInfo.meter) }}{{ mallInfo.meter > 1000 ? 'km' : 'm' }}
					</text>
					<image
						class="nav-bg"
						:src="`${baseImgHost}/navigation-bg.png`"
						mode="aspectFill"
					/>
					<image
						class="nav icon-box"
						:src="`${baseImgHost}/navigation.png`"
						mode="aspectFill"
					/>
				</view>
			</view>
			<!-- 商品分类列表+商品列表 -->
			<view class="goods-type-g">
				<BgTitle
					title="商品列表"
					:fontSize="30"
					style="margin-left: 20rpx;"
				/>
				<view class="type-goods-container flex">
					<!-- 商品类别滚动区域 -->
					<view class="goods-type">
						<view
							v-for="item in Object.values(goodsTypeList)"
							:key="item.id"
							class="goods-type-item flex f-jc-c f-ai-c"
							:class="{active: item.id === activeTypeId}"
							@tap="goodsTypeClickHandle(item)"
						>
							<view class="fz-24 type-name flex f-jc-c f-ai-c">
								{{ item.typeName }}
								<view
									v-if="item.count"
									class="badge fz-20"
									:class="[item.count > 9 ? 'rectangle' : 'circle']"
								>
									{{ item.count }}
								</view>
							</view>
						</view>
					</view>
					<!-- 商品滚动区域 -->
					<view
						v-if="goodsList.length"
						class="flex flex1 f-d-c"
						style="height: 100%; position: relative;"
					>
						<view
							v-for="item in goodsList"
							:key="item.id"
							class="goods-item flex"
							@tap.stop="switchGoods(item.id)"
						>
							<image
								class="goods-img"
								:src="item.imgs[0]?.imgUrl || `${baseImgHost}/default-img2.png`"
								mode="aspectFill"
							/>
							<view class="flex f-d-c f-jc-sb goods-info">
								<text class="goods-name fz-28">
									{{ item.goodsName }}
								</text>
								<view class="flex f-jc-sb f-ai-fe">
									<view class="flex f-d-c">
										<text class="goods-sell fz-24">
											月销 {{ item.monthSell }} 单
										</text>
										<text class="goods-price fz-24"><text class="fz-34">
												{{ item.specifications[0].price }}
											</text>
										</text>
									</view>
									<!-- 选规格 -->
									<view
										v-if="item.specifications.length > 1"
										class=" count-opt specification fz-26"
									>
										<view
											class="gg"
											@tap.stop="specificationClick(item)"
										>
											选规格
											<text
												v-if="item.count"
												class="spec-item-count fz-20"
												:class="[item.count > 9 ? 'rectangle' : 'circle']"
											>
												{{ item.count }}
											</text>
										</view>
									</view>
									<!-- 增减数量 -->
									<view
										v-else
										class="flex f-ai-c count-opt"
									>
										<view
											v-if="item.count"
											class="opt-b reduce fz-36"
											@tap.stop="goodsCountOpt($event, item)"
										>
											-
										</view>
										<text
											v-if="item.count"
											class="fz-28 choosed"
										>
											{{ item.count }}
										</text>
										<view
											class="opt-b add fz-36"
											@tap.stop="goodsCountOpt($event, item, 1)"
										></view>
									</view>
								</view>
							</view>
						</view>
					</view>
					<NoData
						v-else
						style="margin-top: 20rpx; height: 290rpx;"
					/>
				</view>
				<view class="fixed-bottom" />
			</view>
		</view>
		<!-- 底部下单、购物袋等 -->
		<view class="func-bar flex f-ai-c f-jc-sb">
			<view class="flex f-ai-c">
				<view
					class="shopping-bag"
					@tap.stop="bagClickHandle($event)"
				>
					<image
						id="bagIcon"
						:src="`${baseImgHost}/bag.png`"
						mode="aspectFie"
					/>
					<view
						v-if="total"
						class="badge fz-20"
						:class="[total > 9 ? 'rectangle' : 'circle']"
					>
						{{ total }}
					</view>
					<view
						v-else
						class="badge-hidden"
					/>
				</view>
				<text class="fz-24 total-price"><text class="fz-34">
						{{ totalPrice }}
					</text>
				</text>
				<text class="fz-22 total">
					共 {{ total }} 件
				</text>
			</view>
			<view
				class="place-order fz-30"
				@tap="confirmOrder"
			>
				去下单
			</view>
		</view>
		<!-- 选规格弹框 -->
		<Overlay
			:show="showSpecification"
			@overlayClick="closeSpecDialog"
		/>
		<view
			class="spec"
			:class="showSpecContainer ? 'show' : 'hidden'"
		>
			<view class="spec-container flex f-d-c">
				<view class="goods-name fz-34">
					{{ specGoods?.goodsName }}
				</view>
				<!-- 规格信息 -->
				<view class="flex flex1 f-d-c spec-con">
					<text class="fz-24">
						规格
					</text>
					<view class="specs flex">
						<view
							v-for="(item, index) in specGoods?.specifications"
							:key="item.id"
							class="spec-item fz-24"
							:class="{active: index === activeSpecIndex}"
							@tap.stop="specClick(item, index)"
						>
							{{ item.name }}
						</view>
					</view>
				</view>
				<text class="spec-choosed fz-24">
					已选择规格:{{ specGoods?.specNames?.join(',') }}
				</text>
				<!-- 购买数量 -->
				<view class="flex f-jc-sb f-ai-c spec-count-opt">
					<view class="unit-price flex f-ai-c">
						<text
							class="fz-28"
							style="font-weight: 600;"
						>
							单价
						</text>
						<view style="color: #FF4200;">
							<text class="fz-24"></text>
							<text
								class="fz-38"
								style="font-weight: 600;"
							>
								{{ specGoods?.specifications[activeSpecIndex].price }}
							</text>
						</view>
					</view>
					<view
						v-if="!specGoods?.specifications[activeSpecIndex].count"
						class="add-bag flex f-jc-c f-ai-c"
						@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods, 1)"
					>
						<text class="fz-26">
							+
						</text>
						<text class="fz-26">
							加入购物袋
						</text>
					</view>
					<view
						v-else
						class="flex f-ai-c count-opt"
					>
						<view
							class="opt-b reduce fz-28"
							@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods)"
						>
							-
						</view>
						<text class="fz-28 choosed">
							{{ specGoods?.specifications[activeSpecIndex].count || 0 }}
						</text>
						<view
							class="opt-b add fz-28"
							@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods, 1)"
						></view>
					</view>
				</view>
				<image
					class="close-btn"
					:src="`${baseImgHost}/close-2.png`"
					mode="aspectFill"
					@tap.stop="closeSpecDialog"
				/>
			</view>
		</view>
		<!-- 购物袋 -->
		<PageContainer
			:show="showBag"
			height="70vh"
			:round="true"
			:z-index="9990"
			:showShadow="true"
			@overlayClick="showBag = false;"
		>
			<view class="bag-container">
				<view class="flex f-ai-c f-jc-sb bag-header">
					<view>
						<text class="fz-28">
							购物袋
						</text>
						<text class="fz-24 total">
							(共{{ total }}件商品)
						</text>
					</view>
					<view class="flex f-ai-c">
						<image :src="`${baseImgHost}/del.png`" />
						<text
							class="fz-24 total"
							@tap.stop="clearBagHandle"
						>
							清空购物袋
						</text>
					</view>
				</view>
				<view class="bg-list">
					<view
						v-for="item in Object.values(shoppingBag)"
						:key="item.id"
						class="bg-item flex f-jc-sb"
					>
						<view class="flex">
							<image :src="(item.goodsImgs ?? [])[0]?.imgUrl || `${baseImgHost}/default-img2.png`" />
							<view class="flex f-d-c">
								<text class="fz-28">
									{{ item.goodsName }}
								</text>
								<text
									class="fz-24"
									style="color: #828180;"
								>
									{{ item.specificationName }}
								</text>
								<view
									class="fz-20"
									style="color: #FF4200"
								><text class="fz-28">
										{{ item.price }}
									</text>
								</view>
							</view>
						</view>
						<view
							class="flex f-ai-c count-opt"
						>
							<view
								class="opt-b reduce fz-36"
								@tap.stop="bagCoutOpt(item)"
							>
								-
							</view>
							<text
								class="fz-28 choosed"
							>
								{{ item.count }}
							</text>
							<view
								class="opt-b add fz-36"
								@tap.stop="bagCoutOpt(item, 1)"
							></view>
						</view>
					</view>
				</view>
			</view>
		</PageContainer>
		<view
			class="dot"
			:style="{ opacity: dotOpacity, left: dotLeft, top: dotTop }"
		/>
	</view>
</template>
<script>
import './mall.less';
import { NavBar, BgTitle, Overlay, PageContainer, NoData } from '@/components';
import Taro from '@tarojs/taro';
import { throttle, twoBezier } from '@/utils/util';
import { shortenNumber } from '@/utils/number';
import { getGoodsTypeList, getGoodsList, merchantDetail, bagAdd, bagList, bagDelete, bagClear } from '@/apis/goods';

export default {
	name: 'MallDetail',
	components: { NavBar, BgTitle, Overlay, PageContainer, NoData },
	provide () {
		return {
			activeBackground: 'linear-gradient(90deg, #FFA00C, rgba(255,159,10,0)) !important'
		};
	},
	data () {
		return {
			isInit: true, // 是否是页面数据初始化
			showBag: false,
			merchantId: null, // 商家ID
			keyword: '',
			mallInfo: {}, // 商家信息
			goodsList: [], // 商品列表
			goodsTypeList: {}, // 商品类型列表
			shortenNumber,
			activeTypeId: 0, // 当前选中的商品类型id
			activeType: null, // 当前选中的商品类型
			showSpecification: false, // 是否展示选规格弹框
			showSpecContainer: false, // 是否展示选规格容器
			activeSpecIndex: 0, // 任何一个规格弹框中,选中的规格的索引
			specGoods: null, // 当前查看规格的商品
			/**
			 * 购物袋, key: 某个规格的某个商品的记录id;
			 */
			shoppingBag: {},
			totalPrice: 0, // 总价
			total: 0, // 总数量
			pager: {
				currentPage: 1,
				pageSize: 10,
				pageCount: 0
			},
			specCountOptThrottle: null,
			dotTop: 0,
			dotLeft: 0,
			endLeft: 0,
			endTop: 0,
			dotOpacity: 0
		};
	},
	onLoad (options) {
		this.merchantId = options.id;
	},
	async onShow () {
		this.pager = {
			currentPage: 1,
			pageSize: 10,
			pageCount: 0
		};
		this.goodsList = [];
		this.wxToken = Taro.getStorageSync('token');
		this.initLngLat(this.getMallInfo);
		const storageActiveTypeId = await Taro.getStorageSync('activeGoodsType');
		if (storageActiveTypeId) Taro.removeStorageSync('activeGoodsType');
		await this.getGoodsTypeList(storageActiveTypeId);
		await this.getGoodsList(this.activeTypeId);
		this.specCountOptThrottle = throttle(this.specCountOpt, 500);
		// 初始化购物袋的位置坐标
		const query = Taro.createSelectorQuery();
		query.select('#bagIcon').boundingClientRect((res) => {
			this.endLeft = res.left + 12.5;
			this.endTop = res.top;
		}).exec();
	},
	onHide () {
		this.showBag = false;
	},
	methods: {
		makeCall () {
			if (this.mallInfo.phone) {
				Taro.makePhoneCall({
					phoneNumber: this.mallInfo.phone
				}).catch(() => {});
			}
		},
		switchLocation () {
			const that = this;
			Taro.openLocation({
				latitude: Number(that.mallInfo.lat),
				longitude: Number(that.mallInfo.lng),
				scale: 18,
				name: that.mallInfo.merchantName,
				address: that.mallInfo.address
			});
		},
		clearBagHandle () {
			bagClear({ merchantId: this.merchantId }, this.wxToken, false).then(async res => {
				if (res) {
					Taro.showToast({
						title: '操作成功',
						icon: 'none',
						duration: 1000
					});
					await this.getGoodsTypeList();
					await this.getGoodsList(this.activeTypeId);
				}
			});
		},
		toMallImgs () {
			Taro.navigateTo({ url: `/pages/mallImgs/mallImgs?type=mall&merchantId=${this.merchantId}` });
		},
		confirmOrder () {
			if (this.total) {
				Taro.navigateTo({ url: `/pages/orderConfirm/orderConfirm?merchantId=${this.merchantId}` });
			}
		},
		// 购物袋被点击
		bagClickHandle () {
			if (!this.total) return;
			if (this.showBag) {
				this.showBag = false;
				return;
			}
			this.showBag = true;
			this.getBagList();
		},
		// 获取商家信息
		getMallInfo () {
			merchantDetail(this.merchantId, { lat: this.lat, lng: this.lng }, this.wxToken).then(res => {
				this.mallInfo = res;
			});
		},
		// 商品类型列表
		async getGoodsTypeList (storageActiveTypeId) {
			const res = await getGoodsTypeList({ merchantId: this.merchantId, page: 1, limit: 100 }, this.wxToken);
			if (res && res.list.length) {
				if (storageActiveTypeId) {
					this.activeTypeId = storageActiveTypeId;
					this.activeType = res.list.find(l => l.id === storageActiveTypeId);
				} else {
					this.activeTypeId = res.list[0].id;
					this.activeType = res.list[0];
				}
				res.list.map(l => {
					this.goodsTypeList[l.id] = { ...l, count: 0 };
				});
			}
		},
		// 商品列表
		async getGoodsList (id, showLoading) {
			const res = await getGoodsList({
				merchantId: this.merchantId,
				typeId: id,
				page: this.pager.currentPage,
				limit: this.pager.pageSize
			}, this.wxToken, showLoading);
			if (this.triggered) {
				this.triggered = false;
				Taro.stopPullDownRefresh();
			}
			if (res && res.list) {
				if (this.pager.currentPage === 1) {
					this.goodsList = res.list;
				} else {
					this.goodsList = this.goodsList.concat(res.list);
				}
				this.pager = res.page;
			}
			this.getBagList();
		},
		// 获取购物袋中物品列表
		getBagList () {
			this.goodsList = this.goodsList.map(gl => {
				gl.count = 0;
				gl.specifications = gl.specifications.map(sp => {
					sp.count = 0;
					return sp;
				});
				return gl;
			});
			bagList({ merchantId: this.merchantId }, this.wxToken).then(res => {
				this.shoppingBag = {};
				// 购物袋每条记录中带有该商品的类别id,字段名为: typeId
				if (res && res.length) {
					const _goodsTypeList = {};
					const { count, price } = res.reduce((pre, curr, index, arr) => {
						// 填充购物袋物品
						this.shoppingBag[curr.id] = curr;
						// 各商品类型回显已选中的商品总数
						if (_goodsTypeList[curr.goodsTypeId]) {
							_goodsTypeList[curr.goodsTypeId].count += curr.count;
						} else {
							_goodsTypeList[curr.goodsTypeId] = { count: curr.count };
						}
						const goods = this.goodsList.find(g => g.id === curr.goodsId);
						if (goods) {
							// 各商品回显已选中的商品总数
							goods.count = (goods.count ?? 0) + curr.count;
							// 某商品中的各个规格回显总数
							const spec = goods.specifications.find(s => s.id === curr.specificationId);
							if (spec) {
								spec.count = curr.count;
							}
						}
						return {
							count: pre.count + curr.count,
							price: +(pre.price + (curr.price || 0)).toFixed(2)
						};
					}, { count: 0, price: 0 });
					// 各商品类型回显已选中的商品总数
					for (const gt in _goodsTypeList) {
						this.goodsTypeList[gt].count = _goodsTypeList[gt].count;
					}
					this.total = count;
					this.totalPrice = price;
				} else {
					this.showBag = false; // 如果购物袋中物品数为0时关闭购物袋
					this.total = 0;
					this.totalPrice = 0;
					for (const key in this.goodsTypeList) {
						this.goodsTypeList[key].count = 0;
					}
				}
			});
		},
		tabClickHandle (id) {
			Taro.navigateTo({ url: `/pages/mallDetail/mallDetail?id=${id}` });
		},
		// 某个商品类别被点击
		goodsTypeClickHandle (item) {
			this.activeTypeId = item.id;
			this.activeType = item;
			this.pager.currentPage = 1;
			this.getGoodsList(item.id, true);
		},
		dotAnimate (event) {
			// 设置小红点初始位置
			const { clientX, clientY } = event.changedTouches[0];
			this.dotLeft = clientX + 'px';
			this.dotTop = clientY + 'px';
			const num = 30;
			const track = [];
			for (let i = 0; i < num + 1; i++) {
				const [x, y] = twoBezier(i / num, [clientX, clientY], [clientX - 60, clientY - 120], [this.endLeft, this.endTop]);
				track.push({ opacity: 1, left: `${x}px`, top: `${y}px` });
			}
			track[num].opacity = 0;
			let i = 0;
			const inter = setInterval(() => {
				if (i === num + 1) {
					clearInterval(inter);
					return;
				}
				const { opacity, left, top } = track[i];
				this.dotOpacity = opacity;
				this.dotLeft = left;
				this.dotTop = top;
				i++;
			}, 10);
		},
		// 商品列表中的加减号
		goodsCountOpt (event, item, type) {
			if (!type && !item.count) return;
			(type ? bagAdd : bagDelete)({
				merchantId: this.merchantId, // 商户id
				goodsId: item.id, // 商品id
				specificationId: item.specifications[0].id // 规格id
			}, this.wxToken).then(res => {
				if (res) {
					if (type) {
						this.dotAnimate(event);
						item.count = (item.count ?? 0) + 1;
					} else {
						item.count--;
					}
					this.updateOther(type, item.specifications[0].price);
					this.updateShoppingBagDatas(item.id, item.specifications[0].id, type, res);
				}
			});
		},
		// 更新总数、总价、各商品类型已选购总数
		updateOther (type, price) {
			if (type) {
				this.total++;
				this.goodsTypeList[this.activeTypeId].count = (this.goodsTypeList[this.activeTypeId].count ?? 0) + 1;
				this.totalPrice = +(this.totalPrice + price).toFixed(2);
			} else {
				if (this.total) {
					this.goodsTypeList[this.activeTypeId].count--;
					this.total--;
					this.totalPrice = +(this.totalPrice - price).toFixed(2);
				}
			}
		},
		// 更新购物袋中的物品
		updateShoppingBagDatas (goodsId, specificationId, type, res) {
			for (const i in this.shoppingBag) {
				if (this.shoppingBag[i].goodsId === goodsId && this.shoppingBag[i].specificationId === specificationId) {
					// 如果是添加物品
					if (type) {
						this.shoppingBag[i].count++;
					} else {
						this.shoppingBag[i].count--;
						if (!this.shoppingBag[i].count) {
							delete this.shoppingBag[i];
						}
					}
				}
			}
			// 将购物袋中没有的物品同步进去
			if (type && !this.shoppingBag[res.id]) {
				this.shoppingBag[res.id] = res;
			}
		},
		// 点击某个货物
		switchGoods (id) {
			Taro.setStorageSync('activeGoodsType', this.activeTypeId);
			Taro.navigateTo({
				url: `/pages/mallGoodsDetail/mallGoodsDetail?id=${id}&merchantId=${this.merchantId}`
			});
		},
		// 商品列表中的规格按钮点击
		specificationClick (item) {
			item.specNames = [];
			this.showSpecification = true;
			this.showSpecContainer = true;
			item.count = 0;
			Object.values(this.shoppingBag).forEach(gr => {
				const spec = item.specifications.find(s => {
					if (item.id === gr.goodsId && s.id === gr.specificationId) return s;
				});
				if (spec) {
					spec.count = gr.count; // 将购物袋中该商品的某个规格的购买数量赋值给该商品的该规格
					item.count = (item.count ?? 0) + gr.count;
					item.specNames.push(spec.name);
				}
			});
			this.specGoods = item;
		},
		// 关闭规格弹框
		closeSpecDialog () {
			this.activeSpecIndex = 0;
			this.showSpecContainer = false;
			this.showSpecification = false;
		},
		// 规格弹框中的某个规格点击
		specClick (item, index) {
			this.activeSpecIndex = index;
		},
		// 商品规格弹框中的加减号
		specCountOpt (event, item, specGoods, type) {
			(type ? bagAdd : bagDelete)({
				merchantId: this.merchantId, // 商户id
				goodsId: specGoods.id, // 商品id
				specificationId: item.id // 规格id
			}, this.wxToken).then(res => {
				if (res) {
					if (type) {
						this.dotAnimate(event);
						if (!item.count) {
							specGoods.specNames = Array.from(new Set([...specGoods.specNames, item.name]));
						}
						item.count = (item.count ?? 0) + 1; // 该商品选中的某规格的总数
						specGoods.count = (specGoods.count ?? 0) + 1; // 该商品选中的各规格的总数
					} else {
						item.count--;
						if (!item.count) {
							specGoods.specNames.splice(specGoods.specNames.indexOf(item.name), 1);
						}
						specGoods.count--;
					}
					this.updateOther(type, item.price);
					this.updateShoppingBagDatas(specGoods.id, item.id, type, res);
				}
			});
		},
		// 购物袋弹框中的加减号
		bagCoutOpt (item, type) {
			(type ? bagAdd : bagDelete)({
				merchantId: this.merchantId, // 商户id
				goodsId: item.goodsId, // 商品id
				specificationId: item.specificationId // 规格id
			}, this.wxToken).then(res => {
				if (res) {
					// 如果是减号,且该商品选购数量==1,则将其从购物袋中清除
					if (!type && item.count === 1) {
						delete this.shoppingBag[item.id];
					}
					this.getBagList();
				}
			});
		}
	},
	async onPullDownRefresh () {
		if (this.triggered) return;
		this.triggered = true;
		await this.getGoodsTypeList();
		this.pager = {
			currentPage: 1,
			pageSize: 10,
			pageCount: 0
		};
		await this.getGoodsList(this.activeTypeId);
	},
	async onReachBottom () {
		this.onTolowerMixin(() => this.getGoodsList(this.activeTypeId));
	}
};
</script>


2、mall.less

page {
	height: 100vh;
	overflow-y: scroll;
	.container {
		padding-top: 400rpx;
		.fixed-header {
			position: fixed;
			z-index: 998;
			top: 0;
			width: 100vw;
			height: 434rpx;
			image {
				width: 100vw;
			}
		}
		.relative-container {
			width: 100vw;
			z-index: 998;
    		background-color: #FFFFFF;
			border-radius: 35rpx 35rpx 0 0;
			.mall-info {
				margin-top: 38rpx;
				padding: 0 20rpx;
				box-sizing: border-box;
				.name-phone {
					.left {
						text:nth-child(1) {
							font-family: PingFang-SC-Bold;
							font-weight: bold;
							color: #333333;
						}
						text:nth-child(2n) {
							display: inline-block;
							margin-top: 25rpx;
							font-family: PingFangSC-Light;
							font-weight: 300;
							color: #666666;
						}
					}
					image {
						width: 50rpx;
						height: 50rpx;
					}
				}
				.address {
					position: relative;
					margin-top: 30rpx;
					height: 130rpx;
					padding-top: 14rpx;
					box-sizing: border-box;
					.p-name {
						width: 500rpx;
						color: #333333;
						font-weight: bold;
						font-family: PingFang-SC-Bold;
					}
					.p-dis {
						margin-top: 21rpx;
						color: #666666;
						font-family: PingFangSC-Regular;
						font-weight: 400;
					}
					image {
						position: absolute;
					}
					.nav-bg {
						width: 290rpx;
						height: 129rpx;
						right: 0;
						top: 0;
						z-index: -1;
					}
					.nav {
						right: 0;
						top: 50%;
						transform: translateY(-50%);
					}
				}
			}
			.goods-type-g {
				margin-top: 45rpx;
				.type-goods-container {
					padding-bottom: 200rpx;
					box-sizing: border-box;
					.goods-type {
						width: 152rpx;
						background-color: #F8F8F8;
						&-item {
							.type-name {
								position: relative;
								width: 152rpx;
								height: 61rpx;
								color: #9B9B9B;
								background-color: #F8F8F8;
								text-align: center;
								margin: 18rpx 0;
								.badge {
									position: absolute;
									top: 0;
									right: 0rpx;
									min-width: 30rpx;
									height: 30rpx;
									line-height: 30rpx;
									text-align: center;
									color: #FFFFFF;
									background: #FE3A46;
									&.rectangle {
										padding: 0 8rpx;
										border-radius: 16rpx;
									}
									&.circle {
										border-radius: 50%;
									}
								}
								.badge-hidden {
									opacity: 0;
									position: absolute;
									top: 0;
									right: 0rpx;
									width: 30rpx;
									height: 30rpx;
								}
							}
							&.active {
								.type-name {
									background-color: #FFFFFF !important;
									color: #010101 !important;
									&::after {
										content: '';
										position: absolute;
										width: 6rpx;
										height: 45rpx;
										left: 0;
										top: 50%;
										transform: translateY(-50%);
										background: #FFA00C;
										border-radius: 0 7rpx 7rpx 0;
									}
								}
							}
						}
					}
					.goods-item {
						position: relative;
						height: 232rpx;
						padding: 25rpx 21rpx;
						box-sizing: border-box;
						background-color: #FFFFFF;
						.goods-img {
							width: 183rpx;
							height: 183rpx;
							border-radius: 30rpx;
							margin-right: 21rpx;
						}
						.goods-info {
							// width: 353rpx;
							.goods-name {
								width: 335rpx;
								display: -webkit-box;
								overflow: hidden;
								text-overflow: ellipsis;
								-webkit-line-clamp: 2;
								-webkit-box-orient: vertical;
								font-weight: 800;
								color: #3E3A39;
								font-family: PingFang-SC-Heavy;
							}
							.goods-sell {
								display: inline-block;
								margin: 10rpx 0 22rpx 0;
								font-family: PingFang-SC-Medium;
								font-weight: 500;
								color: #828180;
							}
							.goods-price {
								width: 115rpx;
								color: #FF4200;
								font-family: PingFang-SC-Bold;
								font-weight: bold;
							}
						}
						.specification {
							.gg {
								position: relative;
								width: 93rpx;
								height: 41rpx;
								line-height: 41rpx;
								font-family: PingFang-SC-Medium;
								text-align: center;
								background: #FFA00C;
								border-radius: 10rpx;
								color: #FFFFFF;
								.spec-item-count {
									display: inline-block;
									position: absolute;
									top: -16rpx;
									right: -13rpx;
									min-width: 30rpx;
									height: 30rpx;
									line-height: 30rpx;
									text-align: center;
									color: #FFFFFF;
									background: #FE3A46;
									&.rectangle {
										padding: 0 8rpx;
										border-radius: 16rpx;
									}
									&.circle {
										border-radius: 50%;
									}
								}
							}
						}
						.count-opt {
							.opt-b {
								width: 43rpx;
								height: 43rpx;
								border-radius: 50%;
								line-height: 43rpx;
								text-align: center;
							}
							.reduce {
								border: 1rpx solid #FFA00C;
							}
							.choosed {
								display: inline-block;
								margin: 0 16rpx;
							}
							.add {
								border: 1rpx solid #FFEBCB;
								background: #FFEBCB;
								color: #FFA00C;
							}
						}
					}
				}
				.fixed-bottom {
					position: fixed;
					width: 100vw;
					height: 74rpx;
					bottom: 0;
					background-color: #FFFFFF;
				}
			}
		}
		.func-bar {
			position: fixed;
			z-index: 9999;
			width: 708rpx;
			height: 98rpx;
			left: 50%;
			transform: translateX(-50%);
			top: 90vh;
			// bottom: 50rpx;
			overflow: hidden;
			background: #FFFFFF;
			box-shadow: 0 2rpx 10rpx 0 rgba(255, 160, 12, 0.2);
			border-radius: 49rpx;
			.shopping-bag {
				position: relative;
				width: 52rpx;
				height: 60rpx;
				margin-left: 42rpx;
				margin-right: 38rpx;
				image {
					width: 52rpx;
					height: 60rpx;
				}
				.badge {
					position: absolute;
					top: 0;
					right: -13rpx;
					min-width: 30rpx;
					height: 30rpx;
					line-height: 30rpx;
					text-align: center;
					color: #FFFFFF;
					background: #FE3A46;
					&.rectangle {
						padding: 0 8rpx;
						border-radius: 16rpx;
					}
					&.circle {
						border-radius: 50%;
					}
				}
			}
			.total-price {
				display: inline-block;
				margin-right: 15rpx;
				color: #FF4200;
				font-weight: bold;
			}
			.total {
				font-weight: 400;
				color: #828180;
			}
			.place-order {
				width: 207rpx;
				height: 98rpx;
				line-height: 98rpx;
				text-align: center;
				background-color: #FE3A46;
				color: #FFFFFF;
			}
		}
		.spec {
			position: fixed;
			z-index: 9999;
			top: 16vh;
			opacity: 0;
			width: 710rpx;
			height: 58vh;
			left: 50%;
			transition: all 0.2s ease-in;
			transform: translateX(-50%) scale(0);
			&.show {
				opacity: 1;
				transform: translateX(-50%) scale(1) !important;
			}
			&.hidden {
				opacity: 0;
				transform: translateX(-50%) scale(0) !important;
			}
			.spec-container {
				position: relative;
				width: 100%;
				// height: 644rpx;
				height: 50vh;
				border-radius: 10rpx;
				padding-top: 30rpx;
				box-sizing: border-box;
				background-color: #FFFFFF;
				.goods-name {
					padding: 0 23rpx;
					font-family: PingFangSC-Semibold;
					font-weight: 600;
					color: #000000;
				}
				.spec-con {
					margin-top: 30rpx;
					text {
						padding: 0 23rpx;
						font-family: PingFangSC-Regular;
						font-weight: 400;
						color: #8A8A8A;
					}
					.specs {
						margin-top: 20rpx;
						padding: 0 23rpx;
						height: 100%;
						flex-wrap: wrap;
						overflow-y: scroll;
						.spec-item {
							height: 58rpx;
							padding: 0 25rpx;
							margin-right: 30rpx;
							margin-bottom: 20rpx;
							line-height: 58rpx;
							text-align: center;
							background: #F2F2F4;
							border: 1rpx solid #F2F2F4;
							border-radius: 10rpx;
							&.active {
								border-color: #FFA00C;
								background-color: #FFF6E9;
								color: #FFA00C;
							}
						}
					}
				}
				.spec-choosed {
					display: inline-block;
					width: 100%;
					padding: 22rpx 30rpx;
					box-sizing: border-box;
					background: #FAFAFA;
					color: #656565;
				}
				.spec-count-opt {
					// position: relative;
					height: 110rpx;
					margin-top: 20rpx;
					padding: 5rpx 23rpx;
					.add-bag {
						position: relative;
						width: 185rpx;
						height: 61rpx;
						line-height: 61rpx;
						text-align: center;
						background: #FFA00C;
						border-radius: 10rpx;
						font-family: PingFang SC;
						font-weight: 400;
						color: #FFFFFF;
						text:nth-child(1) {
							display: inline-block;
							margin-right: 10rpx;
							transform: scale(1.5);
						}
					}
					.count-opt {
						// position: absolute;
						// top: 50%;
						// transform: translateY(-50%);
						right: 22rpx;
						.opt-b {
							width: 43rpx;
							height: 43rpx;
							border-radius: 50%;
							line-height: 43rpx;
							text-align: center;
						}
						.reduce {
							border: 1rpx solid #FFA00C;
						}
						.choosed {
							display: inline-block;
							margin: 0 16rpx;
						}
						.add {
							border: 1rpx solid #FFEBCB;
							background: #FFEBCB;
							color: #FFA00C;
						}
					}
				}
				.close-btn {
					position: absolute;
					width: 77rpx;
					height: 77rpx;
					bottom: -130rpx;
					left: 50%;
					border-radius: 50%;
					transform: translateX(-50%);
				}
			}
		}
		.bag-container {
			position: relative;
			height: 100%;
			padding: 90rpx 20rpx 170rpx 29rpx;
			box-sizing: border-box;
			background-color: #FFFFFF;
			.bag-header {
				position: absolute;
				padding: 0 20rpx 0 29rpx;
				top: 43rpx;
				left: 0;
				right: 0;
				.total {
					color: #A09F9E;
				}
				image {
					width: 24rpx;
					height: 27rpx;
					margin-right: 13rpx;
				}
			}
			.bg-list {
				height: 100%;
				overflow-y: scroll;
				.bg-item {
					position: relative;
					padding: 20rpx 0;
					image {
						width: 113rpx;
						height: 113rpx;
						border-radius: 20rpx;
						margin-right: 20rpx;
					}
					.count-opt {
						position: absolute;
						bottom: 28rpx;
						right: 22rpx;
						.opt-b {
							width: 43rpx;
							height: 43rpx;
							border-radius: 50%;
							line-height: 43rpx;
							text-align: center;
						}
						.reduce {
							border: 1rpx solid #FFA00C;
						}
						.choosed {
							display: inline-block;
							margin: 0 16rpx;
						}
						.add {
							border: 1rpx solid #FFEBCB;
							background: #FFEBCB;
							color: #FFA00C;
						}
					}
				}
			}
		}
		.dot {
			position: fixed;
			opacity: 0;
			width: 20rpx;
			height: 20rpx;
			z-index: 9999;
			border-radius: 50%;
			background-color: #FF4200;
		}
	}
}

3、mall.config.js

export default definePageConfig({
	'navigationStyle': 'custom',
	'navigationBarTextStyle': 'white',
	'enablePullDownRefresh': true, // 当前页
	'backgroundTextStyle': 'dark', // 顶部显示颜色为深色的三个点
	onReachBottomDistance: 10
});

4、 twoBezier

/**
     * @desc 二阶贝塞尔
     * @param {number} t 当前百分比
     * @param {Array} p1 起点坐标
     * @param {Array} cp 控制点
     * @param {Array} p2 终点坐标
     */
const twoBezier = (t, p1, cp, p2) => {
	const [x1, y1] = p1;
	const [cx, cy] = cp;
	const [x2, y2] = p2;
	const x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;
	const y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;
	return [x, y];
};

到了这里,关于Taro微信小程序实现 美团购物车小红点动画效果的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序购物车页面实现

    目录 32.商品加入购物车逻辑实现(前端) 33.购物车页面收货地址实现 34.购物车商品列表显示实现 37.购物车商品复选框选中业务处理 38.购物车全选复选框选中业务处理 39.购物车商品数量编辑实现 40.购物车商品数量为0判定是否删除 42.商品详情立即购买逻辑实现 1.先在produc

    2024年02月11日
    浏览(42)
  • 微信小程序实现购物商城(附源码)

    2018年本人做了一个淘宝购物返利的微信公众号,截至目前已运营了近5年的时间,也陆续积累了不少粉丝。近日,有部分用户反馈是否可以在公众号上展示促销商品列表,而且要具备搜索功能。为感谢粉丝朋友们的长期支持,笔者耗时一周,利用茶余饭后时间,开发了一个微

    2024年02月11日
    浏览(51)
  • 微信小程序实现商品加入购物车案例

    思考: 购物车中的数据保存在哪里?用哪种数据结构进行保存? 小程序中可能有多个页面需要对购物车中的数据进行操作,因此我们想到把数据存到全局中。可以使用 wx.setStorageSync() 储存,用 wx.getStorageSync() 进行获取,以数组格式方便对数据进行操作。 一、商品加入购物车

    2024年02月10日
    浏览(52)
  • 微信小程序在线商城购物系统设计与实现

     博主介绍 :黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、

    2024年02月04日
    浏览(54)
  • 微信小程序毕业设计作品成品(39)微信小程序在线购物商城系统设计与实现

    博主介绍: 《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月08日
    浏览(50)
  • 基于微信小程序的家具购物小程序的设计与实现

    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了基于微信小程序的家具购物小程序的设计与实现的开发全过程。通过分析基于微信小程序的家具购物小程序的设计与实现管理的不足,创建了一个计算机管理基于微信小程

    2024年03月16日
    浏览(52)
  • 微信小程序毕业设计作品成品(60)微信小程序网上在线购物商城系统设计与实现

    博主介绍: 《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月08日
    浏览(66)
  • 微信小程序毕业设计作品成品(77)微信小程序网络书城图书购物商城系统设计与实现

    博主介绍: 《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月08日
    浏览(49)
  • 微信小程序毕业设计作品成品(55)微信小程序网上书城图书购物商城系统设计与实现

    博主介绍: 《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月07日
    浏览(47)
  • 基于微信小程序的商城购物系统的设计与实现(文档+源码)

    目 录 第一章 绪论 1.1开发背景 1.2所选题目意义与目的 1.3研究现状 1.4本文研究内容 第二章 关键技术介绍 2.1 NODE.JS 2.2 MYSQL 2.3 VUE 2.4 HTML 2.5 JS 2.6 CSS 2.7 小程序开发者工具 第三章 系统分析 3.1 微商城小程序设计思路 3.2 数据表 第四章 系统实现 4.1 购物流程 4.2开店流程 4.3 商城首

    2024年01月18日
    浏览(69)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包