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的区别?
-
relative(相对定位):生成相对定位的元素,通过top,bottom,left,right的设置相对于其正常(原先本身)位置进行定位,可通过z-index进行层次分级。
-
absolute(绝对定位):生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。可通过z-index进行层次分级。
-
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>
下一章就是购物车的功能了 💪💪💪