课程名称: CRMEB uniapp电商项目二次开发实战
课程章节: 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中获取传递参数