花坊项目购物车个人页面
个人中心页面
隐藏导航栏
pages/my/my.json
{
"usingComponents": {
"van-button": "@vant/weapp/button/index"
},
"navigationStyle": "custom"
}
路由跳转
思路
静态骨架
引入全局样式图标
隐藏导航栏显示 [通过微信文档-页面配置-navigationStyle]
在登陆按钮设置路由跳转 跳转到登陆页面
用户信息更新成功,回到tabbar个人中心页面
在用户编辑页面 用户点击保存后 页面跳转到个人中心页面
更新用户信息后回到个人中心页面,(onShow生命周期执行)发送请求获取用户信息,展示数据
获取用户信息,判断用户信息中存在token,再发送请求获取用户信息
设置响应式数据,存储用户头像和昵称
渲染数据
{{headerImage ? headerImage:''}}
wx:if={{nikename}} 控制显示与隐藏用户昵称
登陆成功后 切换 编辑个人中心 和 登陆 按钮显示
wx:if={{nikename}} 控制编辑个人中心的显示
点击 编辑个人中心 按钮,绑定单击事件
跳转到编辑页面
/pages/my/my.wxml
<!--pages/my/my.wxml-->
<view class="person_container container bg">
<view class="top-show">
<image mode="aspectFit" class="top-show-img" src="/static/image/banner1.jpg"></image>
</view>
<view class="wrap">
<!-- 用户信息 -->
<view class="user-container section">
<image class="avator"
src="{{headimgurl ? headimgurl:'https://2216847528.oss-cn-beijing.aliyuncs.com/asset/20241118155049.png'}}"
mode="aspectFill"
/>
<text class="ellipsis">{{nickname}}</text>
<!-- 登录成功以后编辑个人中心 -->
<text class="ellipsis" wx:if="{{nickname}}" bind:tap="goEdit">编辑个人中心</text>
<text class="ellipsis" wx:else bind:tap="goLogin">请登陆</text>
</view>
<!-- 订单卡片 -->
<view class="order section">
<!-- 标题 -->
<view class="order-title-wrap">
<text class="title">我的订单</text>
<text class="more">查看更多></text>
</view>
<!-- 内容 -->
<view class="order-content-wrap">
<view class="order-content-item">
<view class="iconfont icon-dingdan"></view>
<text>商品订单</text>
</view>
<view class="order-content-item">
<view class="iconfont icon-lipinka"></view>
<text>礼品卡订单</text>
</view>
<view class="order-content-item">
<view class="iconfont icon-tuikuan"></view>
<text>退款/售后</text>
</view>
</view>
</view>
<!-- 功能卡片 -->
<view class="order section">
<view class="order-title-wrap">
<text class="title">我的功能</text>
</view>
<view class="order-content-wrap function">
<view class="order-content-item">
<view class="iconfont icon-daohangdizhi"></view>
<text>地址管理</text>
</view>
<view class="order-content-item">
<view class="iconfont icon-kefu_o"></view>
<text>我的客服</text>
</view>
</view>
</view>
<!-- 售后卡片 -->
<view class="order section">
<view class="order-title-wrap">
<text class="title">关于售前售后服务</text>
</view>
<view class="after-scale-wrap">
<view class="after-scale-item">
<view class="iconfont icon-kefu_o"></view>
<text>可与小程序客服实时聊天或电话咨询</text>
</view>
<view class="after-scale-item">
<view class="iconfont icon-shijian"></view>
<text>小程序客服工作时间为: 8:30 ~ 20:30</text>
</view>
<view class="after-scale-item">
<view class="iconfont icon-dizhiguanli"></view>
<text>鲜花制作完毕情况下暂不支持退款</text>
</view>
<view class="after-scale-item">
<view class="iconfont icon-zhangben"></view>
<text>鲜花可以提前7-15天预订重大节假日不支持定时配送</text>
</view>
</view>
</view>
<!-- 页脚 -->
<view class="info-footer">雨落辰潇技术服务支持</view>
</view>
</view>
/pages/my/my.wxss
/* 背景颜色 */
.bg {
background-color: #f7f4f8;
}
/* 顶部部分 */
.top-show {
width: 100%;
height: 360rpx;
}
.top-show-img {
width: 100%;
height: 100%;
}
/* 卡片 */
.section {
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
}
/* 用户部分 */
.user-container {
display: flex;
align-items: center;
margin-top: -40rpx;
color: #999;
}
/* 字体超出隐藏 */
.ellipsis {
width: 300rpx;
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.wrap .user-container .avator {
width: 100rpx;
height: 100rpx;
border-radius: 50rpx;
margin-right: 20rpx;
}
.wrap .user-container .btn{
width: 200rpx;
border-radius: 50rpx;
}
/* 订单卡片 */
.order {
background-color: #fff;
margin-top: 20rpx;
}
/* 内容包裹样式 */
.order-title-wrap,
.order-content-wrap {
padding: 20rpx;
display: flex;
justify-content: space-between;
}
/* 设置字体颜色 */
.order-title-wrap .title {
color: #444;
font-weight: 700;
}
.order-title-wrap .more {
color: #ccc;
font-size: 30rpx;
}
/* 设置内容的布局 */
.order-content-item {
display: flex;
flex-direction: column;
align-items: center;
}
.order-content-item .iconfont {
font-size: 60rpx;
}
.order-content-item text {
font-size: 25rpx;
margin-top: 20rpx;
}
/* 功能样式 */
.function {
display: flex;
justify-content: left;
}
.function .order-content-item {
margin-right: 30rpx;
}
/* 设置图标样式 */
.icon-daohangdizhi {
color: rgb(117, 207, 125);
}
.icon-kefu_o {
color: rgb(230, 154, 12);
}
/* *********售后********** */
.after-scale-wrap .after-scale-item {
display: flex;
margin: 25rpx 15rpx;
color: #999;
}
.after-scale-item text {
font-size: 25rpx;
margin-left: 20rpx;
}
.after-scale-item .iconfont {
color: #0ae9b1;
}
.info-footer{
height: 100rpx;
line-height: 100rpx;
text-align: center;
color: #aaa;
font-size: 25rpx;
}
/pages/my/my.js
// pages/my/my.js
import {reqUserInfo} from '../../api/index'
Page({
/**
* 页面的初始数据
*/
data: {
nickname: "",
//头像
headimgurl:''
},
//请登录文字点击事件回调
goLogin() {
//navigateTo|redirectTo都不能往tabbar页面跳转
wx.navigateTo({
url: '/pages/login/login',
})
},
//编辑个人中心按钮回调
goEdit(){
wx.navigateTo({
url: '/pages/edit/edit',
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
//获取用户信息
// 发请求前,先判断token是否存在
const token = wx.getStorageSync('TOKEN');
//用户授权过登录成功,在获取用户信息
if (token) {
//获取用户信息
this.getUserInfo();
}
},
// 获取用户信息
async getUserInfo() {
const result = await reqUserInfo();
if(result.code==200){
this.setData({
nickname:result.data.nickname,
headimgurl:result.data.headimgurl
})
}
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
/pages/my/my.json
{
"usingComponents": {
"van-button": "@vant/weapp/button/index"
},
"navigationStyle": "custom"
}
登陆页面
设置页面标题
{
"usingComponents": {
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index"
},
"navigationBarTitleText": "登录"
}
使用vantUI组件库
{
"usingComponents": {
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index"
},
"navigationBarTitleText": "登录"
}
login方法
// 登陆按钮回调钩子
goLogin(){
// 调用 wx.login 方法获取 code
// 点击授权登录按钮:需要通过wx.login获取用户登录临时凭证code
wx.login({
success: (res) => {
// 调用登陆借口
this.getToken(res.code)
},
})
},
// 获取token方法
async getToken(code){
const result = await reqToken(code);
if(result.code==200){
//微信小程序本地持久化存储token 上限10M
wx.setStorageSync('TOKEN', result.data.token);
//跳转到编辑页
wx.redirectTo({
url: '/pages/edit/edit',
})
}
},
存储token
设置登陆页面的标题:"navigationBarTitleText": "登录"
使用vant UI组件库,搭建登陆页面的静态骨架
使用wx.login 方法获取code
封装登陆接口
发送请求获取 token
把token存在本地存储中(上限10M,PC上限5M) wx.setStorageSync("Token",value)
登陆成功后使用 redirectTo(不保留当前页面痕迹) 跳转页面进入编辑页面(方便回退到tabbar页面)
登陆成功后在request.js中获取本地token, wx.getStorageSync('TOKEN');
在请求头中添加token
封装获取用户信息的接口(携带参数 header:{token})
/api/index.js
// 登陆接口 获取用户授权登陆token
export const reqToken = (code) => {
return request({
url: `/mall-api/weixin/wxLogin/${code}`
})
}
// 获取用户信息
export const reqUserInfo = () => request({
url: `/mall-api/weixin/getuserInfo`
});
/pages/login/login.wxml
<!--pages/login/login.wxml-->
<van-empty description="点击下方按钮,授权登录您的账户"
image="network"
>
<van-button round type="primary" bind:click="goLogin">轻触登陆</van-button>
</van-empty>
/pages/login/login.wxss
/pages/login/login.js
// pages/login/login.js
import {reqToken} from '../../api/index'
Page({
/**
* 页面的初始数据
*/
data: {
},
// 登陆按钮回调钩子
goLogin(){
// 调用 wx.login 方法获取 code
// 点击授权登录按钮:需要通过wx.login获取用户登录临时凭证code
wx.login({
success: (res) => {
// 调用登陆借口
this.getToken(res.code)
},
})
},
// 获取token方法
async getToken(code){
const result = await reqToken(code);
if(result.code==200){
//微信小程序本地持久化存储token 上限10M
wx.setStorageSync('TOKEN', result.data.token);
//跳转到编辑页
wx.redirectTo({
url: '/pages/edit/edit',
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
})
/pages/login/login.json
{
"usingComponents": {
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index"
},
"navigationBarTitleText": "登录"
}
/utils/request.js
// 网络请求全部的页面使用--功能封装函数(复用)
// params:调用函数传递参数 {url:'xxxx'.method:'post',data:{a:1}}
export default (params) => {
return new Promise((resolve,reject)=>{
//请求基础路径
const baseURL = 'https://gmall-prod.atguigu.cn'
//发请求之前加载效果出来
wx.showLoading({
title: '加载....',
});
// 获取token信息
const token = wx.getStorageSync('TOKEN');
// 定义请求头
let header = {};
// 如果token存在,添加请求头
if (token) {
header.token = token;
}
// 发请求
wx.request({
//请求URL----绝对路径
url: baseURL + params.url,
//请求方式
method: params.method || "GET",
//携带请求体
data: params.data || {},
// 携带请求头
header,
//成功回调
//res即为服务器响应数据
success(res) {
//返回一个成功的Promise对象
//简化数据
resolve(res.data);
},
fail(error) {
reject(error);
},
//成功与失败钩子
complete() {
//加载效果取消
wx.hideLoading()
}
})
})
}
用户编辑页面
input双向绑定
<!-- 展示微信用户昵称 -->
<view class="nickname">
<text class="text">昵称:</text>
<input type="nickname" placeholder="请你输入昵称" model:value="{{nickname}}"/>
</view>
获取用户头像
<!-- 展示用户头像 -->
<button class="avatar" open-type="chooseAvatar" bindchooseavatar="chooseAvatar">
<text class="text">头像:</text>
<image src="{{headimgurl}}" class="img" />
</button>
//获取用户头像的回调
chooseAvatar(event) { // event对象指向当前
this.setData({
headimgurl: event.detail.avatarUrl
})
},
获取用户手机号
// 使用input组件的 model:value={{}} 双向绑定,设置用户昵称数据
// 使用input组件的 type="nickname" 设置input输入框的类型 昵称
// 使用input组件的 type="idcard" 设置input输入框的类型 身份证
// 使用button组件的 open-type="chooseAvatar" 属性获取用户头像
// 使用button组件的 bindchooseavatar 属性绑定获取用户头像的钩子函数,设置用户头像数据状态
// 使用button组件的 open-type="contact" 属性获取客服
// 使用button组件的 open-type="getPhoneNumber" 属性获取手机号
返回上次一路由
获取用户信息 onLoad() 函数中获取用户信息
渲染当前用户的数据,头像和昵称
在api/index.js中 定义更新用户信息接口()
// 使用input组件的 model:value={{}} 双向绑定,设置用户昵称数据
// 使用input组件的 type="nickname" 设置input输入框的类型 昵称
// 使用input组件的 type="idcard" 设置input输入框的类型 身份证
// 使用button组件的 open-type="chooseAvatar" 属性获取用户头像
// 使用button组件的 bindchooseavatar 属性绑定获取用户头像的钩子函数,设置用户头像数据状态
// 使用button组件的 open-type="contact" 属性获取客服
// 使用button组件的 open-type="getPhoneNumber" 属性获取手机号
// 点击更新信息按钮,绑定单击事件,收集请求体数据,发送请求,回到tabbar个人中心页面(不能够使用navigateTo,redirectTo)
// 使用wx.navigateBack(),返回上一次路由页面(结合redirectTo使用)
// 使用wx.switchTab()或wx.reLaunch可以实现同样效果
收集更新后的数据,发送请求更新用户信息
用户信息更新成功,回到tabbar个人中心页面
/pages/edit/edit.wxml
<!--pages/edit/edit.wxml-->
<view class="edit">
<!-- 展示用户头像 -->
<button class="avatar" open-type="chooseAvatar" bindchooseavatar="chooseAvatar">
<text class="text">头像:</text>
<image src="{{headimgurl}}" class="img" />
</button>
<!-- 展示微信用户昵称 -->
<view class="nickname">
<text class="text">昵称:</text>
<input type="nickname" placeholder="请你输入昵称" model:value="{{nickname}}"/>
</view>
<!-- 底部按钮 -->
<view class="footer">
<van-button icon="revoke" round size="normal" type="info">取消</van-button>
<van-button icon="certificate" round size="normal" type="primary" bind:tap="updateUser">确定</van-button>
</view>
</view>
/pages/edit/edit.wxss
/* pages/edit/edit.wxss */
.edit {
width: 100%;
height: 100%;
background: #eee;
}
.edit .avatar {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 140rpx;
padding: 0rpx;
}
.edit .avatar .img {
width: 100rpx;
height: 100rpx;
margin-right: 30rpx;
border-radius: 50rpx;
}
.edit .avatar .text{
font-size: 30rpx;
margin-left: 30rpx;
}
.edit .nickname{
display: flex;
height: 140rpx;
align-items: center;
background-color: #f8f8f8;
border-radius: 10rpx;
}
.edit .nickname .text{
font-size: 30rpx;
margin-left: 30rpx;
}
.edit .nickname input{
font-size: 30rpx;
margin-left: 30rpx;
}
.edit .footer{
margin-top: 80rpx;
display: flex;
justify-content: space-around;
}
/pages/edit/edit.js
// pages/edit/edit.js
import { reqUserInfo,reqUpdateInfo } from '../../api/index'
Page({
/**
* 页面的初始数据
*/
data: {
//用户昵称
nickname: '',
// 头像
headimgurl: '',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//获取用户信息
this.getUserInfo();
},
//获取用户信息(请求头携带token)
async getUserInfo() {
const result = await reqUserInfo();
if (result.code == 200) {
this.setData({
nickname: result.data.nickname,
headimgurl: result.data.headimgurl
})
}
},
//获取用户头像的回调
chooseAvatar(event) {
this.setData({
headimgurl: event.detail.avatarUrl
})
},
//更新信息按钮的回调
async updateUser() {
//用户信息更新成功,回到tabbar个人中心页面
//navigateTo|redirectTo
// 发送请求
const result = await reqUpdateInfo(this.data);
if (result.code == 200) {
//返回个人中心[login登录页销毁了!!!]
wx.navigateBack();
}
},
})
/pages/edit/edit.json
{
"usingComponents": {
"van-button": "@vant/weapp/button/index"
}
}
获取用户信息(老版本)
API-开放接口-用户信息-wx.getUserInfo() 和 wx.getUserProfile 类似
参数是一个对象
wx.getUserInfo(),直接获得微信用户的头像和昵称
静态页面样式
/api/index.js
/pages/edit/edit.wxml
/pages/edit/edit.wxss
/pages/edit/edit.js
/pages/edit/edit.json
{
"usingComponents": {},
"navigationBarTitleText": "编辑个人中心"
}
商品列表页面
首页 查看更多 按钮绑定事件,findGoods,跳转到商品列表页面
分类 品类中 绑定事件,findGoods,跳转到商品列表页面
更改商品列表标题
使用自定义card组件
封装获取商品列表接口,参数:页数,每页记录数,二级分类id
没有更多分割线 vant组件
没有数据空组件 vant组件
当从首页页面跳转到商品列表页面
定义数据状态
定义获取数据方法
onLoad发送请求获取数据
引入card组件,传值
加载更多商品效果
使用微信api监听滚动到底 Page()函数中的方法 onReachBottom()
设置一个 数据状态 标记,判断是否还有数据
触底时 判断标记是否满足,满足则 page+1 ,重新发送请求,获取数据,并且把新获取的数据拼接到旧数据
没有更多样式vant样式:使用wx:if控制显示与隐藏
查看更多绑定事件
首页 查看更多 按钮绑定事件,findGoods,跳转到商品列表页面
分类 品类中 绑定事件,findGoods,跳转到商品列表页面
pages/home/home.js
//查看更多按钮进入商品列表的页面
findGoods(){
wx.navigateTo({
url: '/pages/goods/goods',
})
},
pages/home/home.wxml
<button class="more" bind:tap="findGoods">查看更多</button>
<view class="like">热门推荐</view>
<card list="{{hotArr}}"></card>
<button class="more" bind:tap="findGoods">查看更多</button>
商品列表页面
商品触底动态加载
pages/goods/goods.js
import { reqGoodsList } from "../../api/index";
// pages/goods/goods.js
Page({
/**
* 页面的初始数据
*/
data: {
page: 1,
limit: 10,
category2Id: '', // c2id 分类id
//存储对应商品的数据
goodList: [],
flag: "", //控制到底要不要再发请求 more nomore
},
// 获取商品列表
async getGoodsList(){
const {page,limit,category2Id} = this.data;
const result = await reqGoodsList(page, limit, category2Id);
//修改status的状态数值:需要判断有没有下一次请求
if (result.data.records.length < 10) {
//修改状态:第一次返回四个,没有更多
this.setData({
flag: 'nomore'
})
}else {
this.setData({
flag: 'more'
})
}
// 存储新的商品之前:需要与老的商品的数组合并
const goodList = this.data.goodList;
// 把服务器返回的商品列表,添加到老商品列表
goodList.push(...result.data.records)
// 动态更新商品列表
this.setData({
goodList
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//获取商品列表
this.getGoodsList();
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
//判断:触底的时候状态more
if(this.data.flag === 'more'){
//参数:当前页码+1
this.setData({
page:this.data.page + 1
});
//再次发请求,获取下一页
this.getGoodsList();
}
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
pages/goods/goods.wxml
<!--pages/goods/goods.wxml-->
<view class="goods">
<card list="{{goodList}}" wx:if="{{goodList.length>0}}" />
<van-empty wx:if="{{goodArr.length==0}}" ></van-empty>
<!-- 分割线 -->
<van-divider
contentPosition="center"
fontSize="16"
borderColor="#3cc"
wx:if="{{flag==='nomore'}}"
textColor="#3cc">没有更多了
</van-divider>
</view>
pages/goods/goods.css
pages/goods/goods.json
{
"usingComponents": {
"card": "/components/card/card",
"van-divider": "@vant/weapp/divider/index",
"van-empty": "@vant/weapp/empty/index"
},
"navigationBarTitleText": "商品列表"
}
监听滚动到底事件
onReachBottom()
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
//判断:触底的时候状态more
if(this.data.flag === 'more'){
//参数:当前页码+1
this.setData({
page:this.data.page + 1
});
//再次发请求,获取下一页
this.getGoodsList();
}
},
分类页面跳转到商品
当从分类页面跳转到商品列表页面
定义数据状态
定义获取数据方法
使用自定义属性data-c2id={{}},把二级分类的id传给event对象使用
在onload(option) 函数的option参数中获取2级分类id
/pages/goods/goods.wxml
<!--pages/goods/goods.wxml-->
<view class="goods">
<card list="{{goodList}}" wx:if="{{goodList.length>0}}" />
<van-empty wx:if="{{goodArr.length==0}}" ></van-empty>
<!-- 分割线 -->
<van-divider
contentPosition="center"
fontSize="16"
borderColor="#3cc"
wx:if="{{flag==='nomore'}}"
textColor="#3cc">没有更多了
</van-divider>
</view>
/pages/goods/goods.wxss
/pages/goods/goods.js
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 获取 url 中的param
// console.log('option is ',options.category2Id)
this.setData({
category2Id:options.category2Id || ""
})
//获取商品列表
this.getGoodsList();
},
// 获取商品列表
async getGoodsList(){
const {page,limit,category2Id} = this.data;
const result = await reqGoodsList(page, limit, category2Id);
//修改status的状态数值:需要判断有没有下一次请求
if (result.data.records.length < 10) {
//修改状态:第一次返回四个,没有更多
this.setData({
flag: 'nomore'
})
}else {
this.setData({
flag: 'more'
})
}
// 存储新的商品之前:需要与老的商品的数组合并
const goodList = this.data.goodList;
// 把服务器返回的商品列表,添加到老商品列表
goodList.push(...result.data.records)
// 动态更新商品列表
this.setData({
goodList
})
},
/pages/goods/goods.json
{
"usingComponents": {},
"navigationBarTitleText": "编辑个人中心"
}
/api/index.js
// 获取商品列表
export const reqGoodsList = (page, limit, category2Id) =>{
return request({
url: `/mall-api/goods/list/${page}/${limit}?category2Id=${category2Id}`
})
}
/pages/category/category.wxml
<view class="right">
<view class="title">{{sortArr[active].name}}</view>
<view class="box">
<view class="item" wx:for="{{sortArr[active].children}}"
wx:key="id"
bind:tap="findGoods"
data-category2id="{{item.id}}" >
<image class="img"
src="{{item.imageUrl}}"/>
<text class="title">{{item.name}}</text>
</view>
</view>
</view>
/pages/category/category.js
// 点击右侧分类进入对应商品列表
findGoods(event){
//获取点击的二级分类的商品的id
// console.log(event); // event对象 是 当前点击对象
// 路由跳转到商品列表
wx.navigateTo({
url: `/pages/goods/goods?category2Id=${event.currentTarget.dataset.category2id}`,
})
},
购物车页面
购物车页面数据渲染
搭建购物车静态页面
封装 获取购物车列表数据 接口
定义获取购物车数据函数
//获取用户购物车的数据
async getUserCart() {
// 发送获取购物车请求
const result = await reqCart();
if (result.code == 200) {
this.setData({
cartList: result.data
});
// 调用计算购物车商品的总价
this.computeTotalPrice();
// 调用计算商品的总个数
this.computedTotalCount();
// 调用计算底部的全选的状态
this.computedAllSelect();
}
},
页面首次加载完成,获取购物车数据,存储购物车数据
渲染购物车页面数据,同时使用wx:if控制空组件的显示与隐藏;使用数组reduce方法计算总价
计算商品总价:在购物车数据cartArr[]存在时,触发自定义 computeTotalPrice() 方法
cartArr.reduct()
计算商品总个数:在购物车数据cartArr[]存在时,触发自定义 computeTotalNum() 方法
cartArr.reduct()
计算商品全选状态:在购物车数据cartArr[]存在时,触发自定义 computeAllChecked() 方法
cartArr.every()&&数组中的长度并且大于0时,才返回真
{
name:'小米14',
price: 3999,
goodsId:1,
count:1,
ischecked:1,
imageUrl:'https://2216847528.oss-cn-beijing.aliyuncs.com/asset/20241118155049.png'
}
api/index.js
// 封装获取购物车列表的接口
// 获取商品列表
export const reqGoodsList = (page, limit, category2Id) =>{
return request({
url: `/mall-api/goods/list/${page}/${limit}?category2Id=${category2Id}`
})
}
//获取用户购物车的数据
export const reqCart = () => request({
url: `/mall-api/cart/getCartList`
})
//更新用户的商品的勾选的状态
export const reqUpdateChecked = (goodsId, isChecked) => request({
url: `/mall-api/cart/checkCart/${goodsId}/${isChecked}`
})
//删除商品
export const reqDeleteGood = (goodsId) => request({
url: `/mall-api/cart/delete/${goodsId}`
})
//此接口可以加入购物车|修改购物车某一个商品的数量
export const reqAddOrUpdateCart = (goodsId, count, blessing) => request({
url: `/mall-api/cart/addToCart/${goodsId}/${count}?blessing=${blessing}`
})
pages/shopcart/shopcart.wxml
<view class="container cart_list_container">
<!-- 购物车列表 -->
<view class="cart_list" wx:if="{{cartList.length>0}}">
<view class="cart_item flex" wx:for="{{cartList}}" wx:key="goodsId">
<!-- 左侧选项卡 -->
<view class="check">
<van-checkbox
data-goodsId="{{item.goodsId}}"
checked-color="red"
value="{{item.isChecked}}"
bind:change="updateChecked">
</van-checkbox>
</view>
<!-- 中间商品图 -->
<view class="shop_img">
<image class="img" src="{{item.imageUrl}}"></image>
</view>
<!-- 右侧商品信息 -->
<view class="shop_info flex">
<view class="title">{{item.name}}</view>
<view class="buy flex">
<view class="price">¥{{item.price}}</view>
<view class="buy_btn flex">
<!-- 删除按钮 -->
<van-icon
bind:tap="deleteGood"
size="18px"
color="#71797f"
name="delete"
class="del"
data-goodsid="{{item.goodsId}}"
/>
<!-- 显示商品数量的组件:计数器 -->
<van-stepper
data-goodsid="{{item.goodsId}}"
data-oldvalue="{{item.count}}"
value="{{item.count}}"
bind:change="updateCount"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 空的购物车 -->
<van-empty description="还没有添加商品,快去添加吧~" wx:else />
<!-- 底部结算 -->
<view class="submit_footer flex">
<view class="check">
<van-checkbox checked-color="{{selectAll?'red':'#ccc'}}" value="{{selectAll}}">全选</van-checkbox>
</view>
<view class="right flex">
<view class="total-wrapper">
<text>合计:</text>
<text class="total-price">¥ {{total}}</text>
</view>
<view class="total-count">
<van-button
bind:tap="goOrder"
size="small"
color="linear-gradient(to right, rgb(255, 96, 52), rgb(238, 10, 36))"
round>去结算({{totalCount}})
</van-button>
</view>
</view>
</view>
</view>
pages/shopcart/shopcart.js
// pages/shopcart/shopcart.js
import {reqCart} from '../../api/index'
import Dialog from '@vant/weapp/dialog/dialog';
import debounce from 'lodash.debounce'
Page({
/**
* 页面的初始数据
*/
data: {
// 购物车商品列表
cartList:[], //存储购物车的数据
selectAll:false,//全选的状态
total:123, //计算商品的总价
totalCount:4 //计算结算商品的总个数
},
//页面首次加载完成:获取一次购物车数据
onShow() {
//添加访问购物车登录判断--token
const token = wx.getStorageSync('TOKEN');
if(token){
this.getUserCart();
}else{
//未登录用户去登录页
wx.navigateTo({
url: '/pages/login/login',
})
}
},
//获取用户购物车的数据
async getUserCart() {
const result = await reqCart();
if (result.code == 200) {
this.setData({
cartList: result.data
});
//计算购物车商品的总价
this.computeTotalPrice();
//计算商品的总个数
this.computedTotalCount();
//计算底部的全选的状态
this.computedAllSelect();
}
},
//计算商品总价
computeTotalPrice() {
const totalPrice = this.data.cartList.reduce((prev, next) => {
//商品勾选情况
if (next.isChecked) {
prev += next.price * next.count;
}
return prev;
}, 0);
//更新总价
this.setData({
total: totalPrice
})
},
//计算总个数
computedTotalCount() {
const totalCount = this.data.cartList.reduce((prev, next) => {
//商品勾选情况
if (next.isChecked) {
prev += next.count;
}
return prev;
}, 0);
//更新总价
this.setData({
totalCount
})
},
//计算全选状态
computedAllSelect() {
//数组的length务必大于零个
const checked = this.data.cartList.every(good => good.isChecked == 1) && this.data.cartArr.length > 0;
//更新响应式数据
this.setData({
selectAll: checked
})
},
//商品的勾选状态的更新回调
async updateChecked(event) { // event 指向当前 行对象
//获取当前商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
//勾选的状态参数 携带1|0
const isChecked = event.detail ? 1 : 0;
//更新商品的勾选的状态
try {
//更新商品的状态成功
await reqUpdateChecked(goodsId, isChecked);
//再次获取购物车的数据
this.getUserCart();
} catch (error) {
console.log(error)
}
},
//删除商品的回调
deleteGood(event) {
//删除商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
Dialog.confirm({
title: '删除商品'
}).then(async () => {
//删除商品成功以后
await reqDeleteGood(goodsId);
//再次获取购物车的数据
this.getUserCart();
})
.catch((err) => {
console.log(err);
});
},
//更新数量的钩子
//点击+-文本框只要文本发生变化就会触发
// 使用lodash的 debounce 方法 实现防抖
updateCount: debounce(async function (event) { // event代表当前行对象
//修改商品的id
const goodsId = event.currentTarget.dataset.goodsid;
// 计算差值
const oldValue = event.currentTarget.dataset.oldvalue;
//event.detail输入的新值 - 旧值
const count = event.detail - oldValue;
//如果差值为零没有变化不需要更新
if (count != 0) {
//更新数量
await reqAddOrUpdateCart(goodsId, count);
//再次获取购物车的数据
this.getUserCart();
}
},300),
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
pages/shopcart/shopcart.wxss
.cart_list_container {
height: 100%;
}
.cart_list {
padding-bottom: 150rpx;
}
.cart_item {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
background: #fff;
align-items: center;
}
.cart_item .shop_img {
width: 228rpx;
height: 250rpx;
}
.cart_item .shop_img .img {
height: 100%;
width: 100%;
}
.shop_info {
height: 250rpx;
flex-direction: column;
margin-left: 20rpx;
justify-content: space-between;
flex: 1;
}
.buy {
align-items: center;
justify-content: space-between;
}
.buy .price {
color: #fa4126;
}
/* 底部结算 */
.submit_footer {
position: fixed;
bottom: 0;
left: 0;
background: #fff;
height: 100rpx;
align-items: center;
width: 100%;
justify-content: space-between;
padding: 0 20rpx;
}
.submit_footer .right {
margin: 0 20rpx;
}
.total-price {
color: #ee0a24;
font-weight: 500;
font-size: 20px;
margin: 0 4px;
}
.total-count {
margin: 0 10rpx;
}
pages/shopcart/shopcart.json
{
"usingComponents": {
"van-checkbox": "@vant/weapp/checkbox/index",
"van-icon": "@vant/weapp/icon/index",
"van-stepper": "@vant/weapp/stepper/index",
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index",
"van-dialog": "@vant/weapp/dialog/index"
}
}
计算购物车总价reduce
//计算商品总价
computeTotalPrice() {
const totalPrice = this.data.cartList.reduce((prev, next) => {
//商品勾选情况
if (next.isChecked) {
prev += next.price * next.count;
}
return prev;
}, 0);
//更新总价
this.setData({
total: totalPrice
})
},
判断购物车全选状态
//计算全选状态
computedAllSelect() {
//数组的length务必大于零个
const checked = this.data.cartList.every(good => good.isChecked == 1) && this.data.cartList.length > 0;
//更新响应式数据
this.setData({
selectAll: checked
})
},
// 全选勾选handle函数
selectAllHandle(event){
this.setData({
selectAll:!event.target.dataset.selectstatus
})
if(this.data.selectAll){
const newArr = this.data.cartList.map(good =>{
good.isChecked = 1
return good
})
this.setData({
cartList:newArr
})
}else{
const newArr1 = this.data.cartList.map(good =>{
good.isChecked = 0
return good
})
this.setData({
cartList:newArr1
})
}
},
//商品的勾选状态的更新回调
async updateChecked(event) { // event 指向当前 行对象
//获取当前商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
//勾选的状态参数 携带1|0
const isChecked = event.detail ? 1 : 0;
//更新商品的勾选的状态
try {
//更新商品的状态成功
await reqUpdateChecked(goodsId, isChecked);
//再次获取购物车的数据
this.getUserCart();
} catch (error) {
console.log(error)
}
},
修改购物车勾选状态
修改购物车商品勾选状态
修改购物车商品勾选状态时,需要发送请求更改数据状态
封装 修改商品勾选状态 接口api 参数[token,商品id,]
vant组件绑定勾选框单击事件 bind:change="updateChecked",同时绑定自定义属性data-goodsId
定义updateChecked函数,
获取勾选框的id,获取勾选框的勾选状态updateChecked(event){event.detail} // event只想当前行对象
发送 修改商品勾选状态 请求
再次获取购物车数据
//商品的勾选状态的更新回调
async updateChecked(event) { // event 指向当前 行对象
//获取当前商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
//勾选的状态参数 携带1|0
const isChecked = event.detail ? 1 : 0;
//更新商品的勾选的状态
try {
//更新商品的状态成功
await reqUpdateChecked(goodsId, isChecked);
//再次获取购物车的数据
this.getUserCart();
} catch (error) {
console.log(error)
}
},
vant组件库勾选框绑定事件
<view class="check">
<van-checkbox
data-goodsId="{{item.goodsId}}"
checked-color="red"
value="{{item.isChecked}}"
bind:change="updateChecked">
</van-checkbox>
</view>
获取勾选框的状态
//商品的勾选状态的更新回调
async updateChecked(event) { // event 指向当前 行对象
//获取当前商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
//勾选的状态参数 携带1|0
const isChecked = event.detail ? 1 : 0;
//更新商品的勾选的状态
try {
//更新商品的状态成功
await reqUpdateChecked(goodsId, isChecked);
//再次获取购物车的数据
this.getUserCart();
} catch (error) {
console.log(error)
}
},
删除购物车商品
删除购物车商品
在删除商品按钮 绑定 删除商品 单击事件
在js中使用vant中Dialog组件,引入dialog对象,弹窗,确定,发送删除商品请求,重新获取数据,取消,则关闭弹窗
封装 删除商品 接口api 参数[goodsId,token]
在删除商品按钮 设置 自定义属性,用于获取 goodsId
获取goodsId,发送删除商品请求,再次获取购物车数据
import {reqCart,reqUpdateChecked,reqAddOrUpdateCart,reqDeleteGood} from '../../api/index'
//删除商品的回调
deleteGood(event) {
//删除商品的ID
const goodsId = event.currentTarget.dataset.goodsid;
Dialog.confirm({
title: '删除商品'
}).then(async () => {
//删除商品成功以后
await reqDeleteGood(goodsId);
//再次获取购物车的数据
this.getUserCart();
})
.catch((err) => {
console.log(err);
});
},
<!-- 提示框组件 -->
<van-dialog id="van-dialog" />
修改购物车商品数量
修改购物车数量
使用vant组件库stepper组件
封装 添加和修改购物车数量 接口api 参数[商品id,数量变化值,祝福语,token]
在stepper组件绑定单击事件 bind:change="updateCount",设置 自定义属性,用于获取 goodsId,使用bind:change事件的回调 event.detail 获取输入的新值newValue,设置 自定义属性,用于获取输入的旧值oldValue,使用新值-旧值得到差值(数量变化值)
发送请求,成功后,再次获取购物车的数据
//更新数量的钩子
//点击+-文本框只要文本发生变化就会触发
// 使用lodash的 debounce 方法 实现防抖
updateCount: debounce(async function (event) { // event代表当前行对象
//修改商品的id
const goodsId = event.currentTarget.dataset.goodsid;
// 计算差值
const oldValue = event.currentTarget.dataset.oldvalue;
//event.detail输入的新值 - 旧值
const count = event.detail - oldValue;
//如果差值为零没有变化不需要更新
if (count != 0) {
//更新数量
await reqAddOrUpdateCart(goodsId, count);
//再次获取购物车的数据
this.getUserCart();
}
},300),
优化 防抖和节流 lodash.debounce
引入lodash防抖函数 import debounce from 'lodash'
微信小程序使用lodash的方法,小程序中没有window和dom,不能一下全部引入进来,否则会报错
Uncaught TypeError: Cannot read property 'prototype' of undefined
https://www.02405.com/archives/7466
安装独立的 lodash method package,如 lodash.get
npm install lodash.get lodash.debounce
import get from 'lodash.get'
修改lodash源码
找到 var root = freeGlobal || freeSelf || Function('return this')();
替换为
var root = {
Array: Array,
Date: Date,
Error: Error,
Function: Function,
Math: Math,
Object: Object,
RegExp: RegExp,
String: String,
TypeError: TypeError,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
};
在工具-构建npm-重新构建npm包
导入debounce对象
import debounce from 'lodash.debounce'
// 使用debounce()
updateCount:debounce(function(){
})
优化:防抖和节流
防抖:回城(使用防抖效果),最后一次不操作了,等一段时间,才能触发
节流:技能cd,触发了一次后,需要等一段事件后才能,才能再次触发
input修改购物车数量时,使用lodash,防抖
updateCount:debounce(function(){
})
点击按钮增加和减少购物车数量时,使用lodash,节流
//更新数量的钩子
//点击+-文本框只要文本发生变化就会触发
// 使用lodash的 debounce 方法 实现防抖
updateCount: debounce(async function (event) { // event代表当前行对象
//修改商品的id
const goodsId = event.currentTarget.dataset.goodsid;
// 计算差值
const oldValue = event.currentTarget.dataset.oldvalue;
//event.detail输入的新值 - 旧值
const count = event.detail - oldValue;
//如果差值为零没有变化不需要更新
if (count != 0) {
//更新数量
await reqAddOrUpdateCart(goodsId, count);
//再次获取购物车的数据
this.getUserCart();
}
},300),
购物车授权
未登录用户,跳转到登陆用户,登录后返回到购物车页面
购物车页面首次加载时,获取token,存在token,获取数据,没有token,跳转到登陆页面,
登陆成功后,进入编辑页,编辑页确定后,back到购物车页面
购物车页面的onshow钩子
//页面首次加载完成:获取一次购物车数据
onShow() {
//添加访问购物车登录判断--token
const token = wx.getStorageSync('TOKEN');
if(token){
this.getUserCart();
}else{
//未登录用户去登录页
wx.navigateTo({
url: '/pages/login/login',
})
}
},
购物车结算
购物车页面结算按钮 绑定单击事件 bind:tap="goOrder"
goOrder(){
// 路由跳转到订单详情页面
}
goOrder(){
wx.navigateTo({
url: '/pages/order/order',
})
}