慕尚花坊 --- 分类
0. 配置
1. 数据请求 API
1. 定义接口 API 函数
miniprogram/api/cate.js
import http from '@/utils/http'
/**
* @description 获取商品分类的数据
* @returns Promise
*/
export const reqCategoryData = () => {
return http.get('/index/findCategoryTree')
}
pages/cate/cate.js
import {
reqCategoryData
} from '@/api/category'
Page({
data: {
categoryList: []
},
// 获取商品分类数据
async getCategoryData() {
const res = await reqCategoryData()
console.log(res);
this.setData({
categoryList: res.data
})
},
onLoad(options) {
// 获取商品分类数据
this.getCategoryData()
},
})
2. 全局样式
1. 一级分类
**需求: **
- 当进入分类页面的时候, 第一个一级分类默认是高亮选中的状态
- 当点击任意的一级分类以后, 对应的一级分类需要高亮选中, 其余的一级分类取消高亮选中
0. 效果图

1. 页面创建
pages/cate/cate.wxml
<view>
<view class="category-container">
<!-- 左侧的滚动视图区域 -->
<scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
<view class="left-view-item active"> 鲜花玫瑰 </view>
<view class="left-view-item active"> 鲜花玫瑰 </view>
<view class="left-view-item active"> 鲜花玫瑰 </view>
<view class="left-view-item active"> 鲜花玫瑰 </view>
<view class="left-view-item active"> 鲜花玫瑰 </view>
</scroll-view>
</view>
</view>
pages/cate/cate.scss
.category-container {
display: flex;
.category-left-view {
width: 340rpx;
padding: 10rpx 5rpx;
.left-view-item {
height: 65rpx;
padding: 16rpx 20rpx;
line-height: 50rpx;
border-right: 1rpx solid #efefef;
}
.active {
&::before {
content: '|';
background-color: #f3514f; // 使 | 加粗
margin-right: 20rpx;
}
color: #f3514f;
}
}
}
2. 数据交互
1. 根据数据渲染页面
<view>
<view class="category-container">
<!-- 左侧的滚动视图区域 -->
<scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
<block wx:for="{{ categoryData }}" wx:key="id">
<view class="left-view-item">{{item.name}} </view>
</block>
</scroll-view>
</view>
</view>
2. 选中效果
1. 先初始化数据 activeIndex, 代表被激活的一级分类, 默认值为0
pages/cate/cate.js
import {
reqCategoryData
} from '@/api/cate'
Page({
data: {
activeIndex: 0, // 用户点击的一级菜单索引
categoryData: []
},
async getCategoryData() {
const res = await reqCategoryData()
this.setData({
categoryData: res.data
})
},
clickOneCategory(event){
this.setData({
activeIndex: event.target.dataset.index
})
},
onLoad(options) {
this.getCategoryData()
},
})
2. 使用事件冒泡给左侧菜单栏绑定点击事件, 当点击某个一级分类时, 将对应的索引赋值给 activeIndex
pages/cate/cate.wxml
<view>
<view class="category-container">
<!-- 绑定点击事件 -->
<scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
<block wx:for="{{ categoryData }}" wx:key="id">
<!-- 传递当前标签的 index,并对比用户点击的索引是否与当前标签索引一致,如果一致就添加 active 高亮效果 -->
<view data-index="{{ index }}" class="left-view-item {{ activeIndex === index ? 'active' : '' }}">{{item.name}} </view>
</block>
</scroll-view>
</view>
</view>
pages/cate/cate.js
import {
reqCategoryData
} from '@/api/cate'
Page({
data: {
activeIndex: 0,
categoryData: []
},
async getCategoryData() {
const res = await reqCategoryData()
this.setData({
categoryData: res.data
})
},
// 获取用户点击的索引, 并赋值给 activeIndex
clickOneCategory(event){
this.setData({
activeIndex: event.target.dataset.index
})
},
onLoad(options) {
this.getCategoryData()
},
})
2. 二级分类
0. 效果图

1. 页面创建
pages/cate/cate.wxml
<view>
<view class="category-container">
<!-- 左侧的滚动视图区域 -->
<scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
</scroll-view>
<!-- 右侧的滚动视图区域 -->
<scroll-view class="category-right-view" scroll-y>
<!-- 二级分类 -->
<view class="right--view-container">
<view class="right-view-item">
<navigator class="navigator-nav" url="">
<image class="img" src="../../assets/categorys/cate-1.png" mode="" />
<text class="text">真情告白</text>
</navigator>
</view>
<view class="right-view-item">
<navigator class="navigator-nav" url="">
<image class="img" src="../../assets/categorys/cate-1.png" mode="" />
<text class="text">真情告白</text>
</navigator>
</view>
<view class="right-view-item">
<navigator class="navigator-nav" url="">
<image class="img" src="../../assets/categorys/cate-1.png" mode="" />
<text class="text">真情告白</text>
</navigator>
</view>
<view class="right-view-item">
<navigator class="navigator-nav" url="">
<image class="img" src="../../assets/categorys/cate-1.png" mode="" />
<text class="text">真情告白</text>
</navigator>
</view>
<view class="right-view-item">
<navigator class="navigator-nav" url="">
<image class="img" src="../../assets/categorys/cate-1.png" mode="" />
<text class="text">真情告白</text>
</navigator>
</view>
</view>
</scroll-view>
</view>
</view>
pages/cate/cate.scss
.category-container {
display: flex;
// 一级菜单栏
.category-left-view {
}
// 二级菜单栏
.category-right-view {
.right--view-container {
display: flex;
flex-wrap: wrap;
.right-view-item {
width: 110rpx;
margin: 16rpx 35rpx;
.navigator-nav {
display: flex;
flex-direction: column;
.img {
width: 110rpx;
height: 110rpx;
}
.text {
margin-top: 10rpx;
font-size: 27rpx;
}
}
}
}
}
}
2. 数据交互
pages/cate/cate.wxml
<view>
<view class="category-container">
<!-- 左侧的滚动视图区域 -->
<scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
</scroll-view>
<!-- 右侧的滚动视图区域 -->
<scroll-view class="category-right-view" scroll-y>
<view class="right--view-container">
<!-- 循环对应索引的 children 对象数组 -->
<view class="right-view-item" wx:for="{{ categoryData[activeIndex].children }}" wx:key="id">
<!-- 跳转二级分类的商品列表 -->
<navigator class="navigator-nav" url="/pages/goods/list/list?category2Id={{ item.id }}">
<image class="img" src="{{ item.imageUrl }}" mode="" />
<text class="text">{{item.name}}</text>
</navigator>
</view>
</view>
</scroll-view>
</view>
</view>
3. 分包和预下载
app.json
{
"subPackages": [
{
"root": "modules/settingModule",
"name": "settingModule",
"pages": [
"pages/setup/personal-data/personal-data",
"pages/setup/receive-address/add/add",
"pages/setup/receive-address/list/list"
]
},
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": [
"pages/goods/list/list",
"pages/goods/detail/detail"
]
}
],
"preloadRule": {
"pages/setup/setup": {
"network": "all",
"packages": [
"settingModule"
]
},
"pages/cate/cate": {
"network": "all",
"packages": [
"goodModule"
]
}
}
}
全局搜索 pages/goods/list/list 和 pages/goods/detail/detail 将其替换为 /modules/goodModule/pages/goods/list/list 和 /modules/goodModule/pages/goods/detail/detail
4. 商品列表
1. 列表展示
api/goods.js
import http from '@/utils/http'
/**
* @description 分页获取商品列表
* @param {Object} param { page, limit, category1Id, category2Id}
* @returns Promise
*/
export const reqGoodList = ({page, limit, ...data}) => {
return http.get(`/goods/list/${page}/${limit}`, data)
}
miniprogram/modules/goodModule/pages/goods/list/list.json
{
"usingComponents": {
"goods-card": "../../../../../components/goods-card/goods-card",
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index"
},
"navigationBarTitleText": "商品列表"
}
miniprogram/modules/goodModule/pages/goods/list/list.wxml
<!-- 商品列表 -->
<view class="goods_container" wx:if="{{ goodsList.length }}">
<!-- 标题 -->
<view class="goods_title">{{ title }}</view>
<!-- 列表区域 -->
<view class="goods_card_list">
<block wx:for="{{ goodsList }}" wx:key="id">
<goods-card goodItem="{{ item }}"></goods-card>
</block>
</view>
<!-- 查看更多 -->
<view class="goods_more">查看更多</view>
</view>
<van-empty wx:else description="该分类下暂无商品, 去看看其他商品吧~">
<van-button round type="danger" class="bottom-button" bind:tap="gotoBack">查看其他商品</van-button>
</van-empty>
miniprogram/modules/goodModule/pages/goods/list/list.js
import {
reqGoodList
} from "@/api/goods"
Page({
/**
* 页面的初始数据
*/
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
total: 0, // 数据总条数
requestData: { // 商品列表请求数据
page: 1, // 页码
limit: 10, // 每页请求的条数
category1Id: '', // 一级分类id
category2Id: '' // 二级分类id
}
},
// 获取商品列表
async getGoodsList() {
const {
data
} = await reqGoodList(this.data.requestData)
this.setData({
goodsList: data.records,
total: data.total
})
},
// 返回上一级页面
gotoBack(){
wx.navigateBack()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 获取传递过来的参数, 合并对象, 后面对象的属性会覆盖前面对象的相同属性
Object.assign(this.data.requestData, options)
// 调用获取商品列表的方法
this.getGoodsList()
}
})
2. 上拉加载更多
miniprogram/modules/goodModule/pages/goods/list/list.json
{
"onReachBottomDistance": 400
}
miniprogram/modules/goodModule/pages/goods/list/list.js
import {
reqGoodList
} from "@/api/goods"
Page({
/**
* 页面的初始数据
*/
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
total: 0, // 数据总条数
requestData: { // 商品列表请求数据
page: 1, // 页码
limit: 10, // 每页请求的条数
category1Id: '', // 一级分类id
category2Id: '' // 二级分类id
}
},
// 获取商品列表
async getGoodsList() {
const {
data
} = await reqGoodList(this.data.requestData)
this.setData({
// 将最新请求到的数据和之前的数据合并
goodsList: [...this.data.goodsList,...data.records],
total: data.total
})
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
console.log("上拉了")
const { page } = this.data.requestData
// 页码加 1
this.setData({
requestData: {...this.data.requestData,page: page + 1}
})
// 重新获取商品列表
this.getGoodsList()
}
})
3. 判断数据是否加载完毕
使用 goodsList 数组的长度 和数据总条数进行对比
import {
reqGoodList
} from "@/api/goods"
Page({
/**
* 页面的初始数据
*/
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
total: 0, // 数据总条数
requestData: { // 商品列表请求数据
page: 1, // 页码
limit: 10, // 每页请求的条数
category1Id: '', // 一级分类id
category2Id: '' // 二级分类id
}
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
const { goodsList, total,requestData} = this.data
const { page } = requestData
// 对比 goodsList 和 total 对比
if (goodsList.length === total){
this.setData({
isFinish: true
})
return
}
this.setData({
requestData: {
...this.data.requestData,
page: page + 1
}
})
this.getGoodsList()
},
})
4. 节流
在用户网速很慢的情况下, 如果用户在距离底部来回的进行多次滑动, 可能会发送一些无意义的请求,造成请求浪费的情况, 因此需要给上拉加载添加节流功能
- 在
data中定义节流阀状态isLoading, 默认值为false - 在请求发送之前, 将
isLoading设置为true, 表示请求正在发送 - 在请求结束之后, 将
isLoading设置为false, 表示请求已经完成 - 在
onReachBottom事件监听函数中, 对isLoading进行判断, 如果数据正在请求中, 不请求下一次页的数据
miniprogram/modules/goodModule/pages/goods/list/list.js
import {
reqGoodList
} from "@/api/goods"
Page({
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
isLoading: false, // 判断本次请求是否正在发送
total: 0, // 数据总条数
requestData: { // 商品列表请求数据
page: 1, // 页码
limit: 10, // 每页请求的条数
category1Id: '', // 一级分类id
category2Id: '' // 二级分类id
}
},
async getGoodsList() {
// 在请求发送之前,将 isLoading 设置为 true, 表示请求正在发送中...
this.data.isLoading = true
// 发送请求
const {
data
} = await reqGoodList(this.data.requestData)
this.setData({
goodsList: [...this.data.goodsList, ...data.records],
total: data.total,
})
// 在请求发送之后,将 isLoading 设置为 false, 表示请求发送完毕
this.data.isLoading = false
},
gotoBack() {
wx.navigateBack()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
Object.assign(this.data.requestData, options)
this.getGoodsList()
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
const {
goodsList,
total,
requestData,
isLoading
} = this.data
const {
page
} = requestData
// 判断请求是否正在发送, 如果正在发送中,则不执行以下请求逻辑,请求下一页数据
if (isLoading) return
if (goodsList.length === total) {
this.setData({
isFinish: true
})
return
}
this.setData({
requestData: {
...this.data.requestData,
page: page + 1
}
})
this.getGoodsList()
}
})
5. 下拉刷新
下拉刷新时小程序常见的一种刷新方式, 当用户下拉页面时, 页面也会自动刷新,以便用户获取最新的内容
- 在
app.json (全局配置)或页面.json (当前页面配置)中配置允许下拉, 同时可以配置 窗口、loading样式等 - 在
页面.js中定义onPullDownRefresh事件监听用户下拉刷新
miniprogram/modules/goodModule/pages/goods/list/list.json
{
"enablePullDownRefresh": true,
"backgroundColor": "#efefef", // 背景色
"backgroundTextStyle": "dark", // loading 样式
}
miniprogram/modules/goodModule/pages/goods/list/list.js
import {
reqGoodList
} from "@/api/goods"
Page({
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
isLoading: false, // 判断当前请求是否发送中...
total: 0, // 数据总条数
requestData: { // 商品列表请求数据
page: 1, // 页码
limit: 10, // 每页请求的条数
category1Id: '', // 一级分类id
category2Id: '' // 二级分类id
}
}
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
// 将所有数据进行重置
this.setData({
goodsList: [],
total: 0,
isFinish: false,
requestData: {
...this.data.requestData,
page: 1
}
})
// 使用最新的参数发送请求
this.getGoodsList()
// 手动关闭下拉刷新的窗口
wx.stopPullDownRefresh()
}
})
5. 商品详情
1. 页面构建
miniprogram/modules/goodModule/pages/goods/detail/detail.json
{
"usingComponents": {
"van-icon": "@vant/weapp/icon/index",
"van-button": "@vant/weapp/button/index",
"van-goods-action": "@vant/weapp/goods-action/index",
"van-goods-action-icon": "@vant/weapp/goods-action-icon/index",
"van-goods-action-button": "@vant/weapp/goods-action-button/index",
"van-action-sheet": "@vant/weapp/action-sheet/index",
"van-stepper": "@vant/weapp/stepper/index",
"van-field": "@vant/weapp/field/index"
}
}
miniprogram/modules/goodModule/pages/goods/detail/detail.wxml
<view class="container goods-detail">
<!-- 商品大图 -->
<view class="banner-img">
<image src="{{ goodsInfo.imageUrl }}" mode="" class="img" bind:tap="previewImage" />
</view>
<!-- 商品基本信息 -->
<view class="content">
<view class="price">
<view class="price-num">¥{{ goodsInfo.price }}</view>
<view class="price-origin-num">¥{{ goodsInfo.marketPrice }}</view>
</view>
<view class="title">{{ goodsInfo.name }}</view>
<view class="desc">{{ goodsInfo.floralLanguage }}</view>
</view>
<!-- 商品的详细信息 -->
<view class="detail">
<image wx:for="{{ goodsInfo.detailList }}" wx:key="index" src="{{ item }}" mode="" mode="widthFix" class="img" />
</view>
<!-- 底部导航栏 -->
<view class="bottom">
<view class="bottom-items">
<van-goods-action>
<navigator class="home" url="/pages/index/index" open-type="switchTab">
<van-goods-action-icon icon="chat-o" text="首页" />
</navigator>
<navigator class="to-cart" url="/pages/cart/cart" open-type="switchTab">
<van-goods-action-icon icon="cart-o" text="购物车" open-type="switchTab" />
</navigator>
<van-goods-action-icon icon="chat-o" text="客服" open-type="contact" />
<van-goods-action-button text="加入购物车" type="warning" bind:tap="onClickCartButton" />
<van-goods-action-button text="立即购买" bind:tap="onClickPatButton" />
</van-goods-action>
</view>
<!-- 弹出层 -->
<van-action-sheet show="{{ show }}" round safe-area-inset-bottom overlay close-on-click-overlay bind:close="onClose">
<view class="goods-item">
<!-- 商品图片 -->
<view class="mid">
<image class="img" src="{{ goodsInfo.imageUrl }}" mode="" />
</view>
<!-- 商品基本信息 -->
<view class="right">
<view class="title">{{ goodsInfo.name }}</view>
<view class="right-bottom">
<view class="price">¥{{ goodsInfo.price }}</view>
<van-stepper class="stepper" button-size="22px" min="1" integer value="{{ count }}" bind:change="onChange" />
</view>
</view>
</view>
<view class="popup-bottom">
<view class="title"><text>祝福语</text></view>
<textarea value="" required placeholder="必填, 写上您的祝福语, 给心爱的他 (她) 送上你的祝福 (请勿填写特殊符号或表情符号)" />
</view>
<van-button size="large" round type="primary">确定</van-button>
</van-action-sheet>
</view>
</view>
miniprogram/modules/goodModule/pages/goods/detail/detail.scss
.container {
position: relative;
width: 750rpx;
.banner-img {
.img {
width: 100%;
}
}
.content {
position: absolute;
top: 400rpx;
width: 650rpx;
padding: 40rpx 30rpx;
height: 120rpx;
margin-left: 20rpx;
background-color: #efefef;
.price {
display: flex;
height: 40rpx;
.price-num {
color: orange;
}
.price-origin-num {
font-size: 22rpx;
line-height: 40rpx;
margin-left: 12rpx;
color: #adaaaa;
font-weight: 300;
text-decoration: line-through;
}
}
.title {
margin-top: 12rpx;
font-weight: 1000;
overflow: hidden; // 超出的文字以省略号代替
text-overflow: ellipsis;
white-space: nowrap;
}
.desc {
font-size: 22rpx;
color: #adaaaa;
margin-top: 12rpx;
}
}
.detail {
width: 710rpx;
padding: 20rpx;
margin-top: 120rpx;
.img {
width: 100%;
}
}
.bottom {
van-action-sheet {
height: 650rpx;
.goods-item {
padding: 25rpx;
display: flex;
.mid {
.img {
width: 240rpx;
height: 280rpx;
}
}
.right {
.title {
margin-left: 12rpx;
font-weight: 1000;
overflow: hidden; // 超出的文字以省略号代替
text-overflow: ellipsis;
white-space: nowrap;
}
.right-bottom {
display: flex;
.price {
margin-top: 180rpx;
margin-left: 12rpx;
color: orange;
font-size: 40rpx;
}
.stepper {
margin-top: 180rpx;
margin-left: 130rpx;
}
}
}
}
.popup-bottom {
padding: 20rpx;
.title{
font-weight: 600;
}
textarea{
margin-top: 60rpx;
font-size: 28rpx;
font-weight: 300;
}
}
}
}
}
2. 数据交互
api/goods.js
/**
* @description 获取商品详情
* @param {Number} param 商品的id
* @returns Promise
*/
export const reqGoodsInfo = (goodId) => {
return http.get(`/goods/${goodId}`)
}
miniprogram/modules/goodModule/pages/goods/detail/detail.js
import {
reqGoodsInfo
} from '@/api/goods'
Page({
/**
* 页面的初始数据
*/
data: {
goodsInfo: {}, // 商品详情
show: false, // 加入购物车和立即购买时显示的弹框
count: 1, // 商品购买数量, 默认为1
blessing: '' // 祝福语
},
// 获取商品详情的数据
async getGoodsInfo() {
const {
data: goodsInfo
} = await reqGoodsInfo(this.goodsId)
this.setData({
goodsInfo
})
},
// 打开弹出框
openPopup(){
this.setData({
show: true
})
},
// 关闭弹出框
closePopup(){
this.setData({
show: false
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 接收传递过来的商品 id, 并挂载到this上, 方便多个方法调用
this.goodsId = options.goodsId
// 调用获取商品详情数据的方法
this.getGoodsInfo()
},
})
3. 详情图片预览
miniprogram/modules/goodModule/pages/goods/detail/detail.wxml
<view class="container goods-detail">
<!-- 商品大图 -->
<view class="banner-img">
<!-- 绑定点击事件 -->
<image src="{{ goodsInfo.imageUrl }}" mode="" class="img" bind:tap="previewImage" />
</view>
</view>
miniprogram/modules/goodModule/pages/goods/detail/detail.js
import {
reqGoodsInfo
} from '@/api/goods'
Page({
/**
* 页面的初始数据
*/
data: {
goodsInfo: {}, // 商品详情
show: false, // 加入购物车和立即购买时显示的弹框
count: 1, // 商品购买数量, 默认为1
blessing: '' // 祝福语
},
// 详情图片预览
previewImage(){
wx.previewImage({
urls: this.data.goodsInfo.detailList, // urls 必须是数组类型数据
})
}
})
4. 判断用户是否登录
使用 Token 来判断用户是否登录, Token 是存储在 Store 中的
/behaviors/userBehavior.js
import { BehaviorWithStore } from 'mobx-miniprogram-bindings'
import { userStore } from '@/stores/userStore'
export const userBehavior = BehaviorWithStore({
storeBindings:{
store: userStore,
fields: ['token']
}
})
miniprogram/modules/goodModule/pages/goods/detail/detail.js
import {
userBehavior
} from '@/behaviors/userBehavior'
Page({
behaviors: [userBehavior],
})
5. 加入购物车 && 立即购买
- 定义
payNow作为用户点击的是加入购物车还是立即购买, 点击加入购物车, payNow 设置为0, 点击立即购买则设置为 1 - 点击
加入购物车, 将当前商品加入到购物车内 - 点击
立即购买, 跳转到结算支付页面, 立即购买该商品 - 如果是
立即购买, 不支持购买多个商品
miniprogram/modules/goodModule/pages/goods/detail/detail.wxml
<view class="container goods-detail">
<!-- 商品大图 -->
<view class="banner-img">
<image src="{{ goodsInfo.imageUrl }}" mode="" class="img" bind:tap="previewImage" />
</view>
<!-- 商品基本信息 -->
<view class="content">
<view class="price">
<view class="price-num">¥{{ goodsInfo.price }}</view>
<view class="price-origin-num">¥{{ goodsInfo.marketPrice }}</view>
</view>
<view class="title">{{ goodsInfo.name }}</view>
<view class="desc">{{ goodsInfo.floralLanguage }}</view>
</view>
<view class="detail">
<image wx:for="{{ goodsInfo.detailList }}" wx:key="index" src="{{ item }}" mode="" mode="widthFix" class="img" />
</view>
<!-- 底部导航栏 -->
<view class="bottom">
<view class="bottom-items">
<van-goods-action>
<navigator class="home" url="/pages/index/index" open-type="switchTab">
<van-goods-action-icon icon="chat-o" text="首页" />
</navigator>
<navigator class="to-cart" url="/pages/cart/cart" open-type="switchTab">
<van-goods-action-icon icon="cart-o" text="购物车" open-type="switchTab" />
</navigator>
<van-goods-action-icon icon="chat-o" text="客服" open-type="contact" />
<van-goods-action-button text="加入购物车" type="warning" bind:tap="onClickCartButton" />
<van-goods-action-button text="立即购买" bind:tap="onClickPayButton" />
</van-goods-action>
</view>
<!-- 弹出层 -->
<van-action-sheet show="{{ show }}" round safe-area-inset-bottom overlay close-on-click-overlay bind:close="onClose">
<view class="goods-item">
<view class="mid">
<image class="img" src="{{ goodsInfo.imageUrl }}" mode="" />
</view>
<view class="right">
<view class="title">{{ goodsInfo.name }}</view>
<view class="right-bottom">
<view class="price">¥{{ goodsInfo.price }}</view>
<!-- 点击的是加入购物车才显示步进器组件来修改商品数量 -->
<van-stepper wx:if="{{ payNow === 0 }}" class="stepper" button-size="22px" min="1" max="200" integer value="{{ count }}" bind:change="onChangeGoodsCount" />
</view>
</view>
</view>
<view class="popup-bottom">
<view class="title"><text>祝福语</text></view>
<!-- 双向数据绑定祝福语 -->
<textarea value="" required model:value="{{ blessing }}" placeholder="必填, 写上您的祝福语, 给心爱的他 (她) 送上你的祝福 (请勿填写特殊符号或表情符号)" bindinput="onTextAreaChange" />
</view>
<van-button size="large" round type="primary" bind:tap="onSubmit">确定</van-button>
</van-action-sheet>
</view>
</view>
miniprogram/modules/goodModule/pages/goods/detail/detail.js
import {
reqGoodsInfo
} from '@/api/goods'
Page({
/**
* 页面的初始数据
*/
data: {
goodsInfo: {}, // 商品详情
show: false, // 加入购物车和立即购买时显示的弹框
count: 1, // 商品购买数量, 默认为1
blessing: '', // 祝福语
payNow: 0 // 用于区分加入购物车还是立即购买 0 加入购物车 1 立即购买
},
// 获取商品详情的数据
async getGoodsInfo() {
const {
data: goodsInfo
} = await reqGoodsInfo(this.goodsId)
this.setData({
goodsInfo
})
},
// 图片预览
previewImage() {
wx.previewImage({
urls: this.data.goodsInfo.detailList,
})
},
// 点击加入购物车, 打开弹出框, 修改 payNow 为0
onClickCartButton() {
this.setData({
show: true,
payNow: 0
})
},
// 点击立即购买, 打开弹出框, 修改 payNow 为1
onClickPayButton(){
this.setData({
show: true,
payNow: 1
})
},
// 点击遮罩层, 关闭弹出框
onClose() {
this.setData({
show: false,
payNow: 0
})
},
// 修改商品数量
onChangeGoodsCount(event) {
this.setData({
count: Number(event.detail)
})
},
// 用户点击确定按钮
async onSubmit() {
const {
token,
count,
blessing,
payNow
} = this.data
const goodsId = this.goodsId
// 判断用户是否登录, 没有登录就跳转到登录页面, 并终止以下代码执行
if (!token) {
wx.navigateTo({
url: '/pages/login/login',
})
return
}
if (payNow === 0) { // 用户点击的是加入购物车
const res = await reqAddToCart(goodsId, count, blessing)
if (res.code === 200){
// 提示用户加入购物车成功
toast({
title: '加入购物车成功'
})
// 隐藏弹出框
this.setData({
show: false
})
}
} else { // 用户点击的是立即购买
// 跳转到结算页面
wx.navigateTo({
url: `/pages/order/detail/detail?goodsId=${goodsId}&blessing=${blessing}`,
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 接收传递过来的商品 id, 并挂载到this上, 方便多个方法调用
this.goodsId = options.goodsId
// 调用获取商品详情数据的方法
this.getGoodsInfo()
}
})
6. 购物车图标右上角显示数量
miniprogram/modules/goodModule/pages/goods/detail/detail.wxml
<!-- 使用 info 来展示右上角数量 -->
<van-goods-action-icon icon="cart-o" text="购物车" open-type="switchTab" info="{{ allCount}}" />
miniprogram/modules/goodModule/pages/goods/detail/detail.js
import {
reqGoodsInfo
} from '@/api/goods'
import {
userBehavior
} from '@/behaviors/userBehavior'
import {
toast
} from '@/utils/extendAPI'
import {
reqAddToCart,
reqCartList
} from '@/api/cart'
Page({
behaviors: [userBehavior],
/**
* 页面的初始数据
*/
data: {
allCount: 0 // 用户购物车内商品的数量
},
// 计算用户购物车商品数量, 并显示在底部的购物车右上角
async getCartGoodsCount() {
// 判断用户是否登录
if (!this.data.token) return
const res = await reqCartList()
if (res.code === 200 && res.data.length !== 0) {
let allCount = 0
// 累加购物车的每种商品个数
res.data.forEach((item) => {
allCount += item.count
})
this.setData({
// info 的属性值要求必须是字符串类型, 而且如果总数量大于99, 显示99+
allCount: allCount > 99 ? '99+' : String(allCount)
})
}
},
// 用户点击确定按钮
async onSubmit() {
const {
token,
count,
blessing,
payNow
} = this.data
const goodsId = this.goodsId
if (!token) {
wx.navigateTo({
url: '/pages/login/login',
})
return
}
if (payNow === 0) { // 用户点击的是加入购物车
const res = await reqAddToCart(goodsId, count, blessing)
if (res.code === 200) {
toast({
title: '加入购物车成功'
})
// 加入购物车成功后, 需要重新计算购物车内商品的购买数量, 来显示购物车右上角的数量
this.getCartGoodsCount()
this.setData({
show: false
})
}
} else {
wx.navigateTo({
url: `/pages/order/detail/detail?goodsId=${goodsId}&blessing=${blessing}`,
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.goodsId = options.goodsId
this.getGoodsInfo()
// 获取用户的购物车商品数量, 并显示在底部的购物车右上角
this.getCartGoodsCount()
}
})

浙公网安备 33010602011771号