7 | 小程序实战之商品详情页面(轮播图&价格&名称&图文详情动态渲染 + 实现轮播图可放大预览图片 + 底部工具栏如购物车 + 收藏按钮状态)

1. 效果

在这里插入图片描述

2. 获取商品详情数据接口

2.1 数据接口地址
https://api-hmugo-web.itheima.net/api/public/v1/goods/detail

如果发现为null,别慌,记得传个goods_id参数

在这里插入图片描述

2.2 调用请求

先添加一个商品详情编译模式
在这里插入图片描述

3. 分析接口数据

在这里插入图片描述

4. 轮播图&价格&名称&图文详情动态渲染

index.js

import {request} from "../../request/index.js"
import regeneratorRuntime from "../../lib/runtime/runtime.js";
Page({
  /**
   * 页面的初始数据
   */
  data: {
    goodsObj:{}
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    const {goods_id} = options
    this.getGoodsDetail(goods_id)
  },
  // 获取商品详情数据
  async getGoodsDetail(goods_id){
    const goodsObj = await request({url:"/goods/detail",data:{goods_id}});
    this.setData({
      goodsObj
    })
  }
})

index.wxml

<view class="detail_swiper">
  <!-- 轮播图开始 -->
  <swiper
   autoplay
   circular
   indicator-dots
  >
    <swiper-item
     wx:for="{{goodsObj.pics}}"
     wx:key="pics_id"
    >
      <image mode="widthFix" src="{{item.pics_mid}}" />
    </swiper-item>
  </swiper>
  <!-- 轮播图结束 -->
</view>
<!-- 价钱 -->
<view class="goods_price">¥{{goodsObj.goods_price}}</view>
<!-- 商品名称行 -->
<view class="goods_name_row">
  <!-- 商品名称 -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- 收藏按钮 -->
  <view class="goods_collect">
    <view class="iconfont icon-shoucang1"></view>
    <text class="collect_text">收藏</text>
  </view>
</view>
<!-- 商品详请 -->
<view class="goods_Info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本 -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

index.less

.detail_swiper{
  swiper{
    // swiper默认 100% * 150px
    // 原图为 240 * 240
    height: 65vw;
    // 居中
    text-align: center;
    image{
      width: 70%;
    }
  }
  .goods_price{
    color: var(--themeColor);
    padding: 15rpx;
    font-size: 32rpx;
    font-weight: 600;
  }
  .goods_name_row{
    border-top: 5rpx solid #dedede;
    border-bottom: 5rpx solid #dedede;
    padding: 10rpx 0;
    display: flex;
    .goods_name{
      flex: 5;
      color: #000;
      font-size: 30rpx;
      padding: 0 10rpx;
      display: -webkit-box;
      overflow: hidden;
      -webkit-box-orient: vertical;
      -webkit-line-clamp:2;
    }
    .goods_collect{
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      border-left: 1rpx solid #000;
      .iconfont{}
      .icon-shoucang1{
        color: orangered;
      }
      .collect_text{}
    }
  }
  .goods_Info{
    .goods_info_title{
      font-size: 32rpx;
    color: var(--themeColor);
    font-weight: 600;
    padding: 20rpx;
    }
    .goods_info_content{}
  }
}

商品列表的index.wxml

<block wx:if="{{tabs[0].isActive}}">
    <view class="first_tab">
        <navigator class="goods_item"
        wx:for="{{goodsList}}"
        wx:key="goods_id"
        url="/pages/goods_detail/index?goods_id={{item.goods_id}}"
        >

在这里插入图片描述

5. 优化动态渲染

5.1 问题

你以为上面已经完美了吗,其实并不,当你在小程序开发工具中打开AppData,你会发现goodsList有22条数据项,然而我们用的就那么几个,造成小程序性能降低,而且,部分苹果手机不识别 webp图片格式 ,所以我们得给 goodsList传入需要的配置项把图片格式转换成jpg格式

在这里插入图片描述

5.2 优化代码

index.js

   // 获取商品详情数据
  async getGoodsDetail(goods_id){
    const goodsObj = await request({url:"/goods/detail",data:{goods_id}});
    this.setData({
      goodsObj:{
        goods_name: goodsObj.goods_name,
        goods_price: goodsObj.goods_price,
        // iphone部分手机 不识别 webp图片格式 
        // 最好找到后台 让他进行修改 
        // 临时自己改 确保后台存在 1.webp => 1.jpg 
        // /\.webp/g 的 g 代表全部替换
        goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.pics}
    })
  }

在这里插入图片描述

6. 实现轮播图可放大预览图片

6.1 思路

给轮播图绑定点击事件,调用小程序的previewImage

6.2 代码

index.wxml

<swiper-item
     wx:for="{{goodsObj.pics}}"
     wx:key="pics_id"
     bindtap="handlePreviewImage"
     data-url="{{item.pics_mid}}"
    >
      <image mode="widthFix" src="{{item.pics_mid}}" />
</swiper-item>

index.js

   // 全局商品信息
   GoodsInfo:{},

   // 获取商品详情数据
  async getGoodsDetail(goods_id){
    const goodsObj = await request({url:"/goods/detail",data:{goods_id}});
    this.GoodsInfo = goodsObj;
    ...
    }

   // 轮播图放大预览
  handlePreviewImage(e){
    // 1 先构造要预览的图片数组 
    const urls = this.GoodsInfo.pics.map(v=>v.pics_mid)
    // 2 接收传递过来的图片url
    const current = e.currentTarget.dataset.url;
    wx.previewImage({
      current,
      urls
    })
  }

7. 底部工具栏

index.wxml

<!-- 底部工具栏 -->
<view class="btm_tool">
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>客服</view>
    <!-- 这里之所以不把view标签改为button标签是因为担心按钮有很多样式需要改, 没那么好改样式 -->
    <!-- 使用障眼法 把透明度变为0 宽高变成父元素的宽高 -->
    <button open-type="contact"></button>
  </view>
  <view class="tool_item">
    <view class="iconfont icon-yixianshi-"></view>
    <view>分享</view>
    <button open-type="share"></button>
  </view>
  <!-- open-type="switchTab" 使得能跳转到tabbar页面 -->
  <navigator
   open-type="switchTab"
   url="/pages/cart/index"
   class="tool_item"
  >
    <view class="iconfont icon-gouwuche"></view>
    <view>购物车</view>
  </navigator>
  <view class="tool_item btn_cart " bindtap="handleCartAdd">
    加入购物车
  </view>
  <view class="tool_item btn_buy">
    立即购买
  </view>
</view>

index.less

// 为了最下面的数据不被底部工具栏遮盖
page{
  padding-bottom: 90rpx;
}
.btm_tool{
  border-top: 1rpx solid #ccc;
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  display: flex;
  .tool_item{
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-size: 24rpx;
    position: relative;
    button{
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      opacity: 0;
    }
  }
  .btn_cart{
    flex: 2;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #ffa500;
    color: #fff;
    font-size: 30rpx;
    font-weight: 600;
  }
  .btn_buy{
    flex: 2;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #eb4450;
    color: #fff;
    font-size: 30rpx;
    font-weight: 600;
  }
}


疑惑:
position: absolute、position: fixed 和 position: relative的区别?

  1. relative(相对定位):生成相对定位的元素,通过top,bottom,left,right的设置相对于其正常(原先本身)位置进行定位,可通过z-index进行层次分级。
    在这里插入图片描述

  2. absolute(绝对定位):生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。可通过z-index进行层次分级。
    在这里插入图片描述

  3. fixed(固定定位):生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。可通过z-index进行层次分级。
    在这里插入图片描述
    参考1 参考2

8. 加入购物车

8.1 思路

1 先绑定点击事件
2 获取缓存中的购物车数据 数组格式
3 先判断 当前的商品是否已经存在于 购物车
4 已经存在 修改商品数据 执行购物车数量++ 重新把购物车数组 填充回缓存中
5 不存在于购物车的数组中 直接给购物车数组添加一个新元素 新元素 带上 购买数量属性 num 重新把购物车数组 填充回缓存中
6 弹出提示

8.2 代码

index.wxml

<view class="tool_item btn_cart " bindtap="handleCartAdd">
    加入购物车
</view>

index.js

//点击 加入购物车
  handleCartAdd(e){
    // 1 获取缓存中的购物车数据 数组格式 如果没有赋予空数组
    let cart = wx.getStorageSync('cart') || [];
    console.log(cart);
    // 2 判断 商品对象是否存在于购物车中
    // findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
    let index = cart.findIndex(v=>v.goods_id === this.GoodsInfo.goods_id);
    if(index === -1){
      // 3 不存在 第一次添加
      this.GoodsInfo.num = 1;
      // 4 填充回缓存中
      cart.push(this.GoodsInfo);
    }else{
      // 4 已经存在购物车数据 执行 num++
      cart[index].num++; 
    }
    // 5 把购物车重新添加回缓存中
    wx.setStorageSync("cart", cart);
    // 6 弹窗提示
    wx.showToast({
      title: '加入成功',
      icon: 'success',
      // true 防止用户 手抖 疯狂点击按钮 
      mask: true
    });
  }

在这里插入图片描述

9. 收藏按钮的状态切换

9.1 思路

1 页面onShow的时候 加载缓存中的商品收藏的数据
2 判断当前商品是不是被收藏
1 是 改变页面的图标
2 不是 什么都不做
3 点击商品收藏按钮
1 判断该商品是否存在于缓存数组中
2 已经存在 把该商品删除
3 没有存在 把商品添加到收藏数组中 存入到缓存中即可

9.2 代码
onShow: function () {
	// 获取当前页面栈。数组中第一个元素为首页,最后一个元素为当前页面
    let pages = getCurrentPages();
    let currentPage = pages[pages.length - 1];
    let options = currentPage.options;
    const { goods_id } = options; 
    this.getGoodsDetail(goods_id);
  },

// 获取商品详情数据
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "/goods/detail",
      data: { goods_id },
    });
    this.GoodsInfo = goodsObj;
    // 1 获取缓存中的商品收藏的数组
    let collect = wx.getStorageSync("collect") || [];
    // 2 判断当前商品是否被收藏
    let isCollect = collect.some((v) => v.goods_id === this.GoodsInfo.goods_id);
    this.setData({
      goodsObj: {
        goods_name: goodsObj.goods_name,
        goods_price: goodsObj.goods_price,
        // iphone部分手机 不识别 webp图片格式
        // 最好找到后台 让他进行修改
        // 临时自己改 确保后台存在 1.webp => 1.jpg
        goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, ".jpg"),
        pics: goodsObj.pics,
      },
      isCollect,
    });
  },


// 点击 商品收藏图标
  handleCollect() {
    let isCollect = false;
    // 1 获取缓存中的商品收藏数组
    let collect = wx.getStorageSync("collect") || [];
    // 2 判断该商品是否被收藏过
    let index = collect.findIndex(
      (v) => v.goods_id === this.GoodsInfo.goods_id
    );
    // 3 当index!=-1表示 已经收藏过
    if (index !== -1) {
      // 能找到 已经收藏过了  在数组中删除该商品
      collect.splice(index, 1);
      isCollect = false;
      wx.showToast({
        title: "取消成功",
        icon: "success",
        mask: true,
      });
    } else {
      // 没有收藏过
      collect.push(this.GoodsInfo);
      isCollect = true;
      wx.showToast({
        title: "收藏成功",
        icon: "success",
        mask: true,
      });
    }
    // 4 把数组存入到缓存中
    wx.setStorageSync("collect", collect);
    // 5 修改data中的属性  isCollect
    this.setData({
      isCollect,
    });
  },
<view class="goods_collect" bindtap="handleCollect" >
    <text class="iconfont   {{isCollect?'icon-shoucang1':'icon-shoucang'}} "></text>
    <view class="collect_text">收藏</view>
  </view>

在这里插入图片描述

下一章就是购物车的功能了 💪💪💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值