为了账号安全,请及时绑定邮箱和手机立即绑定

【备战春招】第18天 商品详情页规格选择组件开发一

标签:
小程序

课程章节: 6-1–6-3 商品详情页规格选择组件开发一
课程讲师: CRMEB

课程内容:
1、首先创建商品详情页面goods_detail.vue

<template>
	<view class="goods-detail" v-if="goodsDetail.storeInfo.id">
		<view class="goods-detail-container">
			<Banner :list="goodsDetail.storeInfo.slider_image"></Banner>
			<BaseInfo :info="baseInfo"></BaseInfo>
			<SpecsSelect :info="specsInfo" ref="specsSelect"></SpecsSelect>
			<Description :info="description"></Description>
			<GoodsAction
				:info="goodsActionInfoData"
				@collect="onCollection"
				@cancel-collect="onCancelCollection"
				@cart="onGoCart"
				@add-cart="onAddCart">
			</GoodsAction>
		</view>
	</view>
</template>

<script>
	import authorizationMixin from '@/mixins/authorization'
	import {
		productDetail as productDetailApi,
		collectProduct as collectProductApi,
		cancelCollectProduct as cancelCollectProductApi
	} from '@/api/goods'
	import {
		addCart as addCartApi,
		getCartNum as getCartNumApi
	} from '@/api/cart'
	import Banner from './components/Banner'
	import BaseInfo from './components/BaseInfo'
	import SpecsSelect from './components/SpecsSelect'
	import Description from './components/Description'
	import GoodsAction from './components/GoodsAction'

	export default {
		mixins: [authorizationMixin],
		components: {
			Banner,
			BaseInfo,
			SpecsSelect,
			Description,
			GoodsAction
		},
		data() {
			return {
				id: 0,
				goodsDetail: {
					storeInfo: {}
				},
				cartNum: 0
			}
		},
		computed: {
			baseInfo () {
				return {
					price: this.goodsDetail.storeInfo.price,
					vip_price: this.goodsDetail.storeInfo.vip_price,
					store_name: this.goodsDetail.storeInfo.store_name,
					ot_price: this.goodsDetail.storeInfo.ot_price,
					stock: this.goodsDetail.storeInfo.stock,
					fsales: this.goodsDetail.storeInfo.fsales,
					unit_name: this.goodsDetail.storeInfo.unit_name
				}
			},
			specsInfo () {
				return {
					productValue: this.goodsDetail.productValue,
					productAttr: this.goodsDetail.productAttr,
					store_name: this.goodsDetail.storeInfo.store_name
				}
			},
			description () {
				return this.goodsDetail.storeInfo.description ? this.goodsDetail.storeInfo.description.replace(/<img/g, '<img style="width: 100%"') : ''
			},
			goodsActionInfoData () {
				return {
					id: this.id,
					userCollect: this.goodsDetail.storeInfo.userCollect,
					cartNum: this.cartNum
				}
			},
		},
		onLoad (options) {
			this.id = options.gid ? options.gid : 0
			this.getProductDetail()
			if (this.isLogined()) {
				this.getCartNum()
			}
		},
		methods: {
			onCollection () {
				const job = {
					name: '收藏商品',
					// 自定义函数
					funcs: [
					],
					// 页面方法
					methods: [
					],
					// data数据相关字段对应的数据值
					dataParams: {
					}
				}

				if (!this.isLogined()) {
					job.funcs.push({
						body: (pagePath) => {
							uni.navigateTo({
								url: pagePath
							})
						},
						args: [getCurrentPages().pop().$page.fullPath],
						delay: 0
					})
				}
				
				job.methods.push({
					name: this.collectProduct,
					delay: this.isLogined() ? 0 : 1000
				})
				
				this.needLoginCheckClickHandler(job)
			},
			onCancelCollection () {
				this.cancelCollectProduct()
			},
			onGoCart () {
				const job = {
					name: '去购物车列表',
					// 自定义函数
					funcs: [
					],
					// 页面方法
					methods: [
					],
					// data数据相关字段对应的数据值
					dataParams: {
					}
				}

				job.funcs.push({
					body: (pagePath) => {
						uni.switchTab({
							url: pagePath
						})
					},
					args: ['/pages/order_addcart/order_addcart'],
					delay: 0
				})
				
				this.needLoginCheckClickHandler(job)
			},
			onAddCart () {
				const job = {
					name: '加入购物车',
					// 自定义函数
					funcs: [
					],
					// 页面方法
					methods: [
					],
					// data数据相关字段对应的数据值
					dataParams: {
					}
				}
				if (!this.isLogined()) {
					job.funcs.push({
						body: (pagePath) => {
							uni.navigateTo({
								url: pagePath
							})
						},
						args: [getCurrentPages().pop().$page.fullPath],
						delay: 0
					})
				} else {
					job.methods.push({
						name: this.addCart,
						delay: 0
					})
				}
				
				this.needLoginCheckClickHandler(job)
			},
			async collectProduct () {
				const params = {
					id: this.id
				}
				const { status, msg } = await collectProductApi(params)
				if (status === this.API_STATUS_CODE.SUCCESS) {
					this.goodsDetail.storeInfo.userCollect = true
					uni.showToast({
						icon: 'none',
						title: '收藏成功',
						duration: 3000
					})
				} else {
					uni.showToast({
						icon: 'none',
						title: msg,
						duration: 3000
					})
				}
			},
			async cancelCollectProduct () {
				const params = {
					id: this.id
				}
				const { status, msg } = await cancelCollectProductApi(params)
				if (status === this.API_STATUS_CODE.SUCCESS) {
					this.goodsDetail.storeInfo.userCollect = false
				  uni.showToast({
				    icon: 'none',
				    title: '取消收藏成功',
				    duration: 3000
				  })
				} else {
				  uni.showToast({
				    icon: 'none',
				    title: msg,
				    duration: 3000
				  })
				}
			},
			async addCart () {
				const specsSelectRef = this.$refs.specsSelect
				if (false === specsSelectRef.popupIsShow) {
					specsSelectRef.popupIsShow = true
					return false
				}
				const params = {
					cartNum: specsSelectRef.buyNum,
					new: 0,
					productId: this.id,
					uniqueId: specsSelectRef.curSelectedSpecValue.unique
				}
				const { status, msg } = await addCartApi(params)
				if (status === this.API_STATUS_CODE.SUCCESS) {
					this.getCartNum()
					uni.showToast({
						icon: 'none',
						title: '添加购物车成功',
						duration: 3000
					})
					setTimeout(() => {
						  specsSelectRef.popupIsShow = false
					}, 3000)
				} else {
				  uni.showToast({
				    icon: 'none',
				    title: msg,
				    duration: 3000
				  })
				}
			},
			async getCartNum () {
				const { status, data, msg } = await getCartNumApi()
				if (status === this.API_STATUS_CODE.SUCCESS) {
					this.cartNum = data.count
				} else {
				  uni.showToast({
				    icon: 'none',
				    title: msg,
				    duration: 3000
				  })
				}
			},
			async getProductDetail () {
				const { status, data, msg } = await productDetailApi(this.id)
				if (status === this.API_STATUS_CODE.SUCCESS) {
				  this.goodsDetail = data
				} else {
				  uni.showToast({
				    icon: 'none',
				    title: msg,
				    duration: 3000
				  })
				}
			}
		}
	}
</script>

<style lang="scss" scoped>
.goods-detail {
	min-height: 100vh;
	background-color: #f5f5f5;
}
</style>

2、新建轮播图组件Banner.vue

<template>
	<view class="banner">
		<view class="banner-container">
			<swiper
			  class="swiper"
			  :indicator-dots="indicatorDots"
			  :autoplay="autoplay"
			  :interval="interval"
			  :duration="duration"
			  indicator-active-color="#ffffff">
				<swiper-item
					v-for="(src, idx) in list"
					:key="idx">
					<image :class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="src" mode="aspectFit"></image>
				</swiper-item>
			</swiper>
		</view>
	</view>
</template>

<script>
	export default {
		props: {
			list: {
				type: Array,
				default: () => []
			}
		},
		data () {
			return {
				indicatorDots: true,
				autoplay: true,
				interval: 2000,
				duration: 500
			}
		}
	}
</script>

<style lang="scss" scoped>
.swiper {
	height: 750rpx;
	image {
		width: 100%;
		height: 750rpx;
	}
}
</style>

3、新建规格选择组件BaseInfo.vue

<template>
	<view class="base-info" v-if="info.store_name">
		<view class="base-info-container">
			<view class="price">
				<view class="market-price">
					¥
					<text>{{ info.price }}</text>
					起
				</view>
				<view class="vip-price">
					¥{{ info.vip_price }}
					<image :class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="vipIcon"></image>
				</view>
				<view class="share iconfont icon-fenxiang"></view>
			</view>
			<view class="name">{{ info.store_name }}</view>
			<view class="stock">
				<text>原价:¥{{ info.ot_price }}</text>
				<text>库存:{{ info.stock }} {{ info.unit_name }}</text>
				<text>销量:{{ info.fsales }} {{ info.unit_name }}</text>
			</view>
		</view>
	</view>
</template>

<script>
	import vipIcon from '@/static/images/jvip.png'
	export default {
		props: {
			info: {
				type: Object,
				default: () => {}
			}
		},
		data () {
			return {
				vipIcon
			}
		}
	}
</script>

<style lang="scss" scoped>
.base-info {
	background-color: #fff;
	&-container {
		.price {
			position: relative;
			display: flex;
			align-items: flex-end;
			padding-top: 24rpx;
			font-size: 28rpx;
			font-weight: 700;
			margin: 0 30rpx;
			.market-price {
				color: #fc4141!important;
				text {
					font-size: 48rpx;
				}
			}
			.vip-price {
				color: #282828;
				margin-left: 12rpx;
				image {
					width: 46rpx;
					height: 22rpx;
				}
			}
			.share {
				position: absolute;
				right: 30rpx;
				bottom: 10rpx;
				color: #515151;
				font-size: 40rpx;
			}
		}
		.name {
			font-size: 32rpx;
			font-weight: 700;
			margin: 22rpx 30rpx 0 30rpx;
			word-break: break-all;
		}
		.stock {
			display: flex;
			justify-content: space-between;
			font-size: 24rpx;
			color: #82848f;
			padding-bottom: 20rpx;
			margin: 22rpx 30rpx 0;
		}
	}
}
</style>

课程收获:
这个节课主要学习到了image的图片裁剪、缩放的模式mode其中包括的参数有aspectFit( 保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。)scaleToFill(不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素)aspectFill(保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。)widthFix(宽度不变,高度自动变化,保持原图宽高比不变)heightFix(高度不变,宽度自动变化,保持原图宽高比不变 App 和 H5 平台 HBuilderX 2.9.3+ 支持、微信小程序需要基础库 2.10.3)再有学习到了主页跳转到详情页面通过onLoad中获取传递参数

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
2
获赞与收藏
23

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消