Skip to content

花坊项目购物车个人页面

个人中心页面

隐藏导航栏

pages/my/my.json

ts
{
    "usingComponents": {
        "van-button": "@vant/weapp/button/index"
    },
    "navigationStyle": "custom"
}

路由跳转

思路

shell
静态骨架
引入全局样式图标
隐藏导航栏显示 [通过微信文档-页面配置-navigationStyle]
在登陆按钮设置路由跳转 跳转到登陆页面

用户信息更新成功,回到tabbar个人中心页面
在用户编辑页面 用户点击保存后 页面跳转到个人中心页面
更新用户信息后回到个人中心页面,(onShow生命周期执行)发送请求获取用户信息,展示数据
	获取用户信息,判断用户信息中存在token,再发送请求获取用户信息
	设置响应式数据,存储用户头像和昵称
	渲染数据
		{{headerImage ? headerImage:''}}
		wx:if={{nikename}} 控制显示与隐藏用户昵称
    登陆成功后 切换 编辑个人中心 登陆 按钮显示
    	wx:if={{nikename}} 控制编辑个人中心的显示
    点击 编辑个人中心 按钮,绑定单击事件
        跳转到编辑页面

/pages/my/my.wxml

ts
<!--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

css
/* 背景颜色 */
.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

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

json
{
    "usingComponents": {
        "van-button": "@vant/weapp/button/index"
    },
    "navigationStyle": "custom"
}

登陆页面

设置页面标题

ts
{
    "usingComponents": {
        "van-empty": "@vant/weapp/empty/index",
        "van-button": "@vant/weapp/button/index"
    },
    "navigationBarTitleText": "登录"
}

使用vantUI组件库

json
{
    "usingComponents": {
        "van-empty": "@vant/weapp/empty/index",
        "van-button": "@vant/weapp/button/index"
    },
    "navigationBarTitleText": "登录"
}

login方法

ts
// 登陆按钮回调钩子
    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

shell
设置登陆页面的标题:"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

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

ts
<!--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

css

/pages/login/login.js

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

json
{
    "usingComponents": {
        "van-empty": "@vant/weapp/empty/index",
        "van-button": "@vant/weapp/button/index"
    },
    "navigationBarTitleText": "登录"
}

/utils/request.js

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双向绑定

xml
<!-- 展示微信用户昵称 -->
    <view class="nickname">
        <text class="text">昵称:</text>
        <input type="nickname" placeholder="请你输入昵称" model:value="{{nickname}}"/>
    </view>

获取用户头像

xml
<!-- 展示用户头像 -->
    <button class="avatar" open-type="chooseAvatar" bindchooseavatar="chooseAvatar">
        <text class="text">头像:</text>
        <image src="{{headimgurl}}" class="img" />
    </button>
ts
//获取用户头像的回调
    chooseAvatar(event) { // event对象指向当前
        this.setData({
            headimgurl: event.detail.avatarUrl
        })
    },

获取用户手机号

ts
// 使用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" 属性获取手机号

返回上次一路由

js
获取用户信息 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

ts
<!--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

css
/* 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

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

json
{
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  }
}

获取用户信息(老版本)

ts
API-开放接口-用户信息-wx.getUserInfo() 和 wx.getUserProfile 类似
参数是一个对象
wx.getUserInfo(),直接获得微信用户的头像和昵称

静态页面样式

/api/index.js

js

/pages/edit/edit.wxml

xml

/pages/edit/edit.wxss

css

/pages/edit/edit.js

js

/pages/edit/edit.json

json
{
    "usingComponents": {},
    "navigationBarTitleText": "编辑个人中心"
}

商品列表页面

ts
首页 查看更多 按钮绑定事件,findGoods,跳转到商品列表页面
分类 品类中	绑定事件,findGoods,跳转到商品列表页面

更改商品列表标题
使用自定义card组件
封装获取商品列表接口,参数:页数,每页记录数,二级分类id
没有更多分割线 vant组件
没有数据空组件 vant组件

当从首页页面跳转到商品列表页面
	定义数据状态
	定义获取数据方法
	onLoad发送请求获取数据
	引入card组件,传值
	加载更多商品效果
	使用微信api监听滚动到底 Page()函数中的方法 onReachBottom()
	设置一个 数据状态 标记,判断是否还有数据
	触底时 判断标记是否满足,满足则 page+1 ,重新发送请求,获取数据,并且把新获取的数据拼接到旧数据
	没有更多样式vant样式:使用wx:if控制显示与隐藏

查看更多绑定事件

ts
首页 查看更多 按钮绑定事件,findGoods,跳转到商品列表页面
分类 品类中	绑定事件,findGoods,跳转到商品列表页面

pages/home/home.js

js
//查看更多按钮进入商品列表的页面
findGoods(){
    wx.navigateTo({
        url: '/pages/goods/goods',
    })
},

pages/home/home.wxml

xml
<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

ts
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

xml
<!--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

css

pages/goods/goods.json

json
{
    "usingComponents": {
        "card": "/components/card/card",
        "van-divider": "@vant/weapp/divider/index",
        "van-empty": "@vant/weapp/empty/index"
    },
    "navigationBarTitleText": "商品列表"
}

监听滚动到底事件

onReachBottom()

ts
/**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom() {
        //判断:触底的时候状态more
        if(this.data.flag === 'more'){
            //参数:当前页码+1
            this.setData({
                page:this.data.page + 1
            });
            //再次发请求,获取下一页
            this.getGoodsList();
        }
    },

分类页面跳转到商品

shell
当从分类页面跳转到商品列表页面
	定义数据状态
	定义获取数据方法 
	使用自定义属性data-c2id={{}},把二级分类的id传给event对象使用
	在onload(option) 函数的option参数中获取2级分类id

/pages/goods/goods.wxml

xml
<!--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

css

/pages/goods/goods.js

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

json
{
    "usingComponents": {},
    "navigationBarTitleText": "编辑个人中心"
}

/api/index.js

js
// 获取商品列表
export const reqGoodsList = (page, limit, category2Id) =>{
    return request({
        url: `/mall-api/goods/list/${page}/${limit}?category2Id=${category2Id}`
    })
}

/pages/category/category.wxml

xml
<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

js
// 点击右侧分类进入对应商品列表
findGoods(event){
    //获取点击的二级分类的商品的id
    // console.log(event); // event对象 是 当前点击对象
    // 路由跳转到商品列表
    wx.navigateTo({
        url: `/pages/goods/goods?category2Id=${event.currentTarget.dataset.category2id}`,
    })
},

购物车页面

购物车页面数据渲染

ts
搭建购物车静态页面
封装 获取购物车列表数据 接口

定义获取购物车数据函数
//获取用户购物车的数据
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时,才返回真
ts
{
    name:'小米14',
    price: 3999,
    goodsId:1,
    count:1,
    ischecked:1,
    imageUrl:'https://2216847528.oss-cn-beijing.aliyuncs.com/asset/20241118155049.png'
}

api/index.js

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

xml
<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

ts
// 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

css
.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

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

ts
//计算商品总价
    computeTotalPrice() {
        const totalPrice = this.data.cartList.reduce((prev, next) => {
            //商品勾选情况
            if (next.isChecked) {
                prev += next.price * next.count;
            }
            return prev;
        }, 0);
        //更新总价
        this.setData({
            total: totalPrice
        })
    },

判断购物车全选状态

ts
//计算全选状态
    computedAllSelect() {
        //数组的length务必大于零个
        const checked = this.data.cartList.every(good => good.isChecked == 1) && this.data.cartList.length > 0;
        //更新响应式数据
        this.setData({
            selectAll: checked
        })
    },
ts
    // 全选勾选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)
        }
    },

修改购物车勾选状态

ts
修改购物车商品勾选状态
修改购物车商品勾选状态时,需要发送请求更改数据状态
封装 修改商品勾选状态 接口api 参数[token,商品id,]
vant组件绑定勾选框单击事件 bind:change="updateChecked",同时绑定自定义属性data-goodsId
定义updateChecked函数,
    获取勾选框的id,获取勾选框的勾选状态updateChecked(event){event.detail} // event只想当前行对象
	发送 修改商品勾选状态 请求
    再次获取购物车数据
ts
//商品的勾选状态的更新回调
    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组件库勾选框绑定事件

ts
<view class="check">
    <van-checkbox 
        data-goodsId="{{item.goodsId}}" 
        checked-color="red" 
        value="{{item.isChecked}}" 
        bind:change="updateChecked">
    </van-checkbox>
</view>

获取勾选框的状态

ts
    //商品的勾选状态的更新回调
    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)
        }
    },

删除购物车商品

ts
删除购物车商品
在删除商品按钮 绑定 删除商品 单击事件
在js中使用vant中Dialog组件,引入dialog对象,弹窗,确定,发送删除商品请求,重新获取数据,取消,则关闭弹窗
封装 删除商品 接口api 参数[goodsId,token]
在删除商品按钮 设置 自定义属性,用于获取 goodsId
获取goodsId,发送删除商品请求,再次获取购物车数据
ts
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);
    });
},
xml
<!-- 提示框组件 -->
<van-dialog id="van-dialog" />

修改购物车商品数量

ts
修改购物车数量
使用vant组件库stepper组件
封装 添加和修改购物车数量 接口api 参数[商品id,数量变化值,祝福语,token]
在stepper组件绑定单击事件 bind:change="updateCount",设置 自定义属性,用于获取 goodsId,使用bind:change事件的回调 event.detail 获取输入的新值newValue,设置 自定义属性,用于获取输入的旧值oldValue,使用新值-旧值得到差值(数量变化值)
发送请求,成功后,再次获取购物车的数据
ts
//更新数量的钩子
    //点击+-文本框只要文本发生变化就会触发
    // 使用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

ts
引入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(){
    
})
ts
优化:防抖和节流
防抖:回城(使用防抖效果),最后一次不操作了,等一段时间,才能触发
节流:技能cd,触发了一次后,需要等一段事件后才能,才能再次触发


input修改购物车数量时,使用lodash,防抖
	updateCount:debounce(function(){
        
    })

点击按钮增加和减少购物车数量时,使用lodash,节流
ts
//更新数量的钩子
    //点击+-文本框只要文本发生变化就会触发
    // 使用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),

购物车授权

ts
未登录用户,跳转到登陆用户,登录后返回到购物车页面
购物车页面首次加载时,获取token,存在token,获取数据,没有token,跳转到登陆页面,
    登陆成功后,进入编辑页,编辑页确定后,back到购物车页面
	购物车页面的onshow钩子
ts
//页面首次加载完成:获取一次购物车数据
    onShow() {
        //添加访问购物车登录判断--token
        const token = wx.getStorageSync('TOKEN');
        if(token){
            this.getUserCart();
        }else{
            //未登录用户去登录页
            wx.navigateTo({
              url: '/pages/login/login',
            })
        }
    },

购物车结算

ts
购物车页面结算按钮 绑定单击事件 bind:tap="goOrder"
goOrder(){
    // 路由跳转到订单详情页面
}
ts
goOrder(){
    wx.navigateTo({
        url: '/pages/order/order',
    })
}