Skip to content

商品详情页面

总体思路

ts
搭建商品详情静态页面 
修改商品详情的标题 json文件
商品card组件传递 商品id
商品详情页面获取 商品id 发送请求获取数据

获取商品id

商品card组件页面:传递商品id

ts
card组件中 item中绑定单击事件 设置自定义属性获取商品id 设置回调函数

card组件中设置方法 
methods:{
    // 点击获取商品的详情页面 
    getDetail(event){ 
        // 获取商品id
        event....id
        // 路由跳转
        微信小程序中的路由没有 params ,只有query
        query: /pages/goodsDetails/goodsDetails?goodsId=${goodsId}
        params: /pages/goodsDetails/goodsDetails/1/2 (微信小程序没有这种写法)
    }
}
xml
<view class="item" wx:for="{{list}}" wx:key="id" bind:tap="goDetetail"  data-goodsid="{{item.id}}" >
ts
 
methods: {
    //点击商品进入商品详情页
    goDetetail(event){
        //商品的ID
        const goodsId = event.currentTarget.dataset.goodsid;
        //点击商品进入详情
        wx.navigateTo({
            url: `/pages/goodsDetail/goodsDetail?goodsId=${goodsId}`,
        })
    }
}

获取商品详情数据

ts
封装获取商品详情的接口api 参数[goodsId]

定义获取商品详情的函数,函数中发送请求

商品详情onLoad时获取数据
onLoad(option){
    option 获取商品的goodsId
    存储 商品goodsID 
    调用 获取商品详情 函数
    存储 商品详情 数据
}

渲染商品详情页面数据
js
//获取商品的详情的数据
async getGoodsInfo() {
    const result = await reqGoodsDetail(this.data.goodsId);
    if (result.code == 200) {
        this.setData({
            goods: result.data
        })
    }
},
onLoad(options){
    //存储商品的ID
    this.setData({
        goodsId: options.goodsId
    });
    //获取商品的详情
    this.getGoodsInfo();
},

动作面板模块

点击立即购买或加入购物车,弹出卡片填写祝福语和数量

ts
动作面板的显示与隐藏

van-action-sheet组件 show属性 show="{{showSheet}}" 控制显示与隐藏
van-action-sheet组件 bind:click-overlay="{{overplay}}"事件 点击其他区域隐藏动作面板

加入购物车和立即购买 分别控制显示和隐藏购买数量区域
	定义一个数据状态type:"",用于区分 加入购物车 和 立即购买
    点击加入购物车时,修改type数据状态
	点击立即购买时,修改type数据状态
	使用wx:if 控制计数条的显示与隐藏

定义 overplay 函数
	点击遮罩层后侧,关闭弹窗显示
	修改showSheet数据状态,关闭动作面板,清空type数据状态
xml
<!-- show:控制动作面板显示与隐藏布尔值 -->
    <van-action-sheet show="{{showSheet}}" bind:click-overlay="overPlay">
        <!-- 动作面板内部展示商品内容 -->
        <view class="sheet_wrapper">
            <!-- 商品信息 -->
            <view class="goods_item flex">
                <image class="img mid" src="{{goods.imageUrl}}"></image>
                <view class="info flex">
                <view class="title">{{goods.name}}</view>
                <view class="buy">
                    <view class="price">
                        <view class="symbol">¥</view>
                        <view class="num">{{goods.price}}</view>
                    </view>
                    <!-- 立即购买的时候不显示数量添加 -->
                    <view wx:if="{{type==='cart'}}" class="buy-btn" style="minWidth:400rpx;">
                        <van-stepper value="{{skuNum}}" bind:change="skuNumChange"/>
                    </view>
                </view>  
            </view>
        </view>
            <!-- 祝福语 -->
            <view class="time-wraper">
                <view class="title">祝福语</view>
                <textarea  model:value="{{remarks}}" class="form-textarea" placeholder="必填,写上您的祝福语,给心爱的他(她)送上你的祝福(请勿填写特殊符号或表情符号)" name="textarea" />
            </view>
            <!-- 确定按钮 -->
            <view class="sheet-footer-btn">
                <van-button block type="primary" round bind:tap="confirm">确定</van-button>
            </view>
        </view>
    </van-action-sheet>
js
//点击动作面板后面遮罩层
overPlay(){
    //控制面板隐藏
    this.setData({
        showSheet: false,
        type: ''
    })
},

动作面板数据收集

ts
定义数据状态 skuNum:1 收集计数器商品数量(默认是1)
在 计数器 组件上 通过 bind:change = "skuNumChange" 事件的回调函数中的参数[event] event.detail 获取当前输入的值

定义 计数器的回调函数 获取当前计数器的值
skuNumChange(event){
    event.detail() // 获取当前计数器最新的值
    存储商品的数量
}

定义数据状态 祝福语 remark:''
在textarea组件中设置 model:value={{}} 设置双向绑定获取祝福语
xml
<!-- 立即购买的时候不显示数量添加 -->
<view wx:if="{{type==='cart'}}" class="buy-btn" style="minWidth:400rpx;">
    <van-stepper value="{{skuNum}}" bind:change="skuNumChange"/>
</view>

<!-- 祝福语 -->
<view class="time-wraper">
    <view class="title">祝福语</view>
    <textarea  model:value="{{remarks}}" class="form-textarea" placeholder="必填,写上您的祝福语," name="textarea" />
</view>
js
// 计步器函数
    skuNumChange(event){
        //存储商品的数量
        this.setData({
            skuNum: event.detail
        })
    },

立即购买和加入购物车

ts
立即购买按钮 绑定单击事件 bind:tap="buynowHandle" 
	修改showSheet数据状态 

加入购物车和立即购买 分别控制显示和隐藏购买数量区域
	定义一个数据状态type:"",用于区分 加入购物车 和 立即购买
    点击加入购物车时,修改type数据状态
	点击立即购买时,修改type数据状态
	使用wx:if 控制计数条的显示与隐藏
    
定义 overplay 函数
	点击遮罩层后侧,关闭弹窗显示
	修改showSheet数据状态,关闭动作面板,清空type数据状态

封装 加入购物车 接口api 参数[商品id,商品数量,祝福语]

在确定按钮 绑定单击事件 bind:tap="confim" 
confim(){
    区分是加入购物车还是立即购买,通过type区分
    如果是加入购物车,调用 加入购物车函数 addGoodsToCart()
    如果是立即购买,调用 立即购买函数 buyImmediate
}

定义 加入购物车 函数 addGoodsToCart(){}
addGoodsToCart(){
    未登录时(通过是否存在token),跳转到登陆页面,提示信息 跳转到编辑页面,
    编辑页面点击确定后,登陆成功
    登录后,返回到商品页面
    登录后,执行下边逻辑
    判断是否 填写了 祝福语
        有,放行
        没有,使用vant中的toast组件 做信息提示
        toast组件是 js 代码,同时也需要在 wxml页面引入 van-toast组件标签
        (van-toast组件标签放在任何位置都可以,一般放在最后)
    整理参数
    发加入购物车请求
    关闭动作面板,清空 计数器数量 和 祝福语
    加入购物车成功 提示信息(选)
}

定义 立即购买 函数 goOrderByImme
goOrderByImme(){
    解构 商品id 和 祝福语
    路由跳转到订单页面order,同时传递 商品id 和 祝福语,通过query的方式传递参数
    清空 商品id 和 祝福语 数据
}
xml
<view class="bottom_right flex">
    <view class="add_shop_cart" bind:tap="addCartHandle">加入购物车</view>
    <view class="buy_now"  bind:tap="buynowHandle">立即购买</view>
</view>

<!-- 确定按钮 -->
<view class="sheet-footer-btn">
    <van-button block type="primary" round bind:tap="confirm">确定</van-button>
</view>
ts
import {reqGoodsDetail ,reqAddOrUpdateCart} from '../../api/index'
import Toast from '@vant/weapp/toast/toast';
//立即购买按钮回调
buynowHandle() {
    //显示控制面板展示商品的详情
    this.setData({
        showSheet: true,
        type: "buy"
    })
},
//加入购物车按钮的回调
addCartHandle() {
    //显示动作面包
    this.setData({
        showSheet: true,
        type: 'cart'
    })
},
//点击动作面板后面遮罩层
overPlay(){
    //控制面板隐藏
    this.setData({
        showSheet: false,
        type: ''
    })
},
//对应按钮的确定回调
confirm() {
    // 判断有没有祝福语
    if (this.data.remarks.trim() == '') {
        Toast.fail('请填写祝福语');
        return;
    }
    //判断用户是否登录
    const token = wx.getStorageSync('TOKEN');
    //登录
    if (token) {
        // 加入购物车
        if (this.data.type == 'cart') {
            // 调用加入购物车函数
            this.addGoodsToCart();
        } else {
            // 调用立即购买的函数
            this.goOrderByImme();
        }
    } else {
        //未登录去登录(不能加入购物车,不能支付)
        wx.navigateTo({
            url: '/pages/login/login',
        })
    }
},
// 加入购物车方法
async addGoodsToCart() {
    //整理参数
    const {
        goodsId,
        skuNum,
        remarks
    } = this.data;
    //发请求
    //加入购物车成功
    await reqAddOrUpdateCart(goodsId, skuNum, remarks);
    Toast.success('加入购物车成功');
    //关闭动作面板
    this.setData({
        showSheet: false,
        skuNum: 1,
        remarks: ''
    })
},
// 跳转订单页面函数
    goOrderByImme(){
        //立即购买的商品的ID与祝福语
        const {goodsId,remarks} = this.data;
        wx.navigateTo({
            url: `/pages/order/order?goodsId=${goodsId}&remarks=${remarks}`,
        })
        //清除祝福语
        this.setData({
            remarks:''
        })
    }

商品详情代码

/pages/goodsDetail.js

js
import {reqGoodsDetail ,reqAddOrUpdateCart} from '../../api/index'
import Toast from '@vant/weapp/toast/toast';
Page({
    data:{
        goodsId: '', //商品的详情ID
        goods: {}, //存储商品的详情的数据
        showSheet: false, //控制动作面包显示与隐藏
        type: '', //区分购物车立即购买
        skuNum: 1, //收集计数器商品数量
        remarks: '', //祝福语
    },
    onLoad(options){
        //存储商品的ID
        this.setData({
            goodsId: options.goodsId
        });
        //获取商品的详情
        this.getGoodsInfo();
    },
    //获取商品的详情的数据
    async getGoodsInfo() {
        const result = await reqGoodsDetail(this.data.goodsId);
        if (result.code == 200) {
            this.setData({
                goods: result.data
            })
        }
    },
    //加入购物车按钮的回调
    addCartHandle() {
        //显示动作面包
        this.setData({
            showSheet: true,
            type: 'cart'
        })
    },
    //立即购买按钮回调
    buynowHandle() {
        //显示控制面板展示商品的详情
        this.setData({
            showSheet: true,
            type: "buy"
        })
    },
    // 计步器函数
    skuNumChange(event){
        //存储商品的数量
        this.setData({
            skuNum: event.detail
        })
    },
    // 隐藏遮罩层
    //点击动作面板后面遮罩层
    overPlay(){
        //控制面板隐藏
        this.setData({
            showSheet: false,
            type: ''
        })
    },
    // 点击确定按钮
    //对应按钮的确定回调
    confirm() {
        // 判断有没有祝福语
        if (this.data.remarks.trim() == '') {
            Toast.fail('请填写祝福语');
            return;
        }
        //判断用户是否登录
        const token = wx.getStorageSync('TOKEN');
        //登录
        if (token) {
            // 加入购物车
            if (this.data.type == 'cart') {
                // 调用加入购物车函数
                this.addGoodsToCart();
            } else {
                // 调用立即购买的函数
                this.goOrderByImme();
            }
        } else {
            //未登录去登录(不能加入购物车,不能支付)
            wx.navigateTo({
                url: '/pages/login/login',
            })
        }
    },
    // 加入购物车方法
    async addGoodsToCart() {
        //整理参数
        const {
            goodsId,
            skuNum,
            remarks
        } = this.data;
        //发请求
        //加入购物车成功
        await reqAddOrUpdateCart(goodsId, skuNum, remarks);
        Toast.success('加入购物车成功');
        //关闭动作面板
        this.setData({
            showSheet: false,
            skuNum: 1,
            remarks: ''
        })
    },
    // 跳转订单页面函数
    goOrderByImme(){
        //立即购买的商品的ID与祝福语
        const {goodsId,remarks} = this.data;
        wx.navigateTo({
            url: `/pages/order/order?goodsId=${goodsId}&remarks=${remarks}`,
        })
        //清除祝福语
        this.setData({
            remarks:''
        })
    }
})

/pages/goodsDetail.wxml

xml
<!--pages/goodsDetail/goodsDetail.wxml-->
<view class="good_detail_container">
    <!-- 商品大图 -->
    <view class="banner_img">
        <image class="img" src="{{goods.imageUrl}}" mode="aspectFit"></image>
    </view>
    <!-- 商品info -->
    <view class="good_info">
        <view class="price">
            <view class="price_num">¥ {{goods.price}}</view>
            <view class="price_origin_num">¥ {{goods.marketPrice}}</view>
        </view>
        <view class="title">{{goods.name}}</view>
        <view class="desc">{{goods.packing}}</view>
    </view>
    <!-- 商品详情 -->
    <view class="good_detail">
        <image class="img"  wx:for="{{goods.detailList}}" wx:key="index" src="{{item}}" mode="aspectFit" ></image>
    </view>
    <!-- 底部选项区域 -->
    <view class="good_footer flex">
        <view class="bottom_left flex">
            <!-- 声明式导航:点击图标跳转首页  open-type跳转方式:默认navigate-->
            <navigator url="/pages/home/home" open-type="reLaunch">
                <view class="icon_wrap flex">
                    <van-icon name="wap-home" color="#666" size="22px"></van-icon>
                    <text>首页</text>
                </view>
            </navigator>
            <!-- 声明式导航:点击图标跳转购物车 -->
            <navigator url="/pages/shopcart/shopcart" open-type="reLaunch">
                <view class="icon_wrap flex">
                    <van-icon name="shopping-cart" color="#666" size="22px"></van-icon>
                    <text>购物车</text>
                </view>
            </navigator>
        </view>
        <view class="bottom_right flex">
            <view class="add_shop_cart" bind:tap="addCartHandle">加入购物车</view>
            <view class="buy_now"  bind:tap="buynowHandle">立即购买</view>
        </view>
    </view>
    <!--弹窗区域 -->
    <!-- show:控制动作面板显示与隐藏布尔值 -->
    <van-action-sheet show="{{showSheet}}" bind:click-overlay="overPlay">
        <!-- 动作面板内部展示商品内容 -->
        <view class="sheet_wrapper">
            <!-- 商品信息 -->
            <view class="goods_item flex">
                <image class="img mid" src="{{goods.imageUrl}}"></image>
                <view class="info flex">
                <view class="title">{{goods.name}}</view>
                <view class="buy">
                    <view class="price">
                        <view class="symbol">¥</view>
                        <view class="num">{{goods.price}}</view>
                    </view>
                    <!-- 立即购买的时候不显示数量添加 -->
                    <view wx:if="{{type==='cart'}}" class="buy-btn" style="minWidth:400rpx;">
                        <van-stepper value="{{skuNum}}" bind:change="skuNumChange"/>
                    </view>
                </view>  
            </view>
        </view>
            <!-- 祝福语 -->
            <view class="time-wraper">
                <view class="title">祝福语</view>
                <textarea  model:value="{{remarks}}" class="form-textarea" placeholder="必填,写上您的祝福语,给心爱的他(她)送上你的祝福(请勿填写特殊符号或表情符号)" name="textarea" />
            </view>
            <!-- 确定按钮 -->
            <view class="sheet-footer-btn">
                <van-button block type="primary" round bind:tap="confirm">确定</van-button>
            </view>
        </view>
    </van-action-sheet>
</view>
<!-- 请提示组件 -->
<van-toast id="van-toast" />

/pages/goodsDetail.json

json
{
    "usingComponents": {
        "van-action-sheet": "@vant/weapp/action-sheet/index",
        "van-stepper": "@vant/weapp/stepper/index",
        "van-icon": "@vant/weapp/icon/index",
        "van-button": "@vant/weapp/button/index",
        "van-toast": "@vant/weapp/toast/index"
    }
}

订单详情页面

购物车页面结算按钮 和 商品详情页面立即购买按钮 都可以跳转到订单详情页面

总体思路

ts
新建 订单详情静态页面
修改订单详情页面标题

封装接口

ts
//获取订单详情页-立即购买的商品的信息
export const reqBuyNowGoodsInfo = (goodsId, blessing) => request({
    url: `/mall-api/order/buy/${goodsId}?blessing=${blessing}`
})

//获取订单收货人地址信息
export const reqAdress = () => request({
    url: `/mall-api/userAddress/getOrderAddress`
})

获取获取订单详情数据

ts
封装 获取订单详情数据(通过立即购买按钮跳转) 接口 参数[goodsId,祝福语]
export const reqBuyNowGoodsInfo = () =>{
    
}

订单详情页面定义 商品id 和祝福语数据状态

订单详情页面的onLoad(options){} options参数中获取 query 参
通过options参数中的数据判断是从 立即购买按钮 来的还是从 购物车结算按钮 来的
onLoad(options){
    如果options.remarks存在,代表是从立即购买按钮来的
    存储 商品id和祝福语
    调用 发送获取立即购买商品信息
}


定义 获取订单详情数据 函数(通过立即购买确定按钮来的) getOrderListByImme(){}
getOrderListByImme(){
    获取 商品id和 祝福语 参数
    调用 获取立即购买商品id 的接口[需要商品id和祝福语]
    获取 立即购买商品信息 存储商品数据,同时渲染数据
    
}

定义 获取订单详情数据 函数(通过购物车结算按钮来的) getOrderListBySettle(){}
getOrderListBySettle(){
    发送获取购物车数据请求
    存储购物车商品数据 cartList:[]
    订单页面渲染购物车商品数据
    调用计算商品总价函数
}
ts
import { reqCart, reqBuyNowGoodsInfo, reqAdress, reqSubmit, reqPayInfo, reqPayStatus } from '../../api/index'
Page({
    data: {
        userAddress: {}, //收货人的地址
        goodsId: 0, //存储立即购买而来的商品的ID
        remarks: '', //存储立即购买的携带祝福语
        cartList: [], //展示商品的数组
        totalPrice: 0, //存储商品的总价
        buyName: '', //收集订购人的名字
        buyPhone: '', //订购人手机号码
        isShowPop: false, //控制pop弹窗显示与隐藏
        minDate: new Date().getTime(), //日期显示最小的时间
    },
    onLoad: function (options) {
        // 携带祝福语:立即购买而来
        if (options.remarks) {
            // 存储祝福语与立即购买商品的ID
            this.setData({
                goodsId: options.goodsId,
                remarks: options.remarks
            });
            // 调用 获取立即购买商品 方法
            this.getGoodsByImme()

        } else {
            // 调用 获取购物车商品 方法
            this.getGoodsByCart();
        }
    },
    //  获取 立即购买商品 方法
    async getGoodsByImme(){
        // 解构出 商品id和祝福语
        const {goodsId,remarks} = this.data;
        // 发送请求 获取立即购买的商品的信息
        const result = await reqBuyNowGoodsInfo(goodsId, remarks);
        // 如果响应状态码为200
        if (result.code == 200) {
            // 存储数据状态
            this.setData({
                cartList: result.data.cartVoList, //商品信息
                totalPrice: result.data.totalAmount, //立即购买商品总价
            })
        }
    },
    // 获取 购物车商品 方法
    async getGoodsByCart (){
        // 发送获取购物车请求
        const result = await reqCart();
        // 判断响应码是否为200
        if (result.code == 200) {
            // 存储购物车的数据
            this.setData({
                cartList: result.data
            });
            // 获取购物车数据完毕以后计算总价
            this.computedTotal();
        }
    },
})

计算购物车商品价格

ts
定义购物车商品总价 totalPrice,渲染总价数据

定义计算商品总价的函数 computedTotal(){
    使用数组的 reduce方法计算购物车的总价
    更新totalPrice数据状态
}
ts
// 计算购物车商品总价
    computedTotal(){
        const total = this.data.cartList.reduce((prev,next)=>{
            return prev += next.count * next.price;
        },0)
        //更新总价
        this.setData({
            totalPrice:total
        })

    },

日期选择并渲染(moment插件)

ts
在van-cell标签上绑定单击事件showPop,同时定义数据状态isShowPopup,用于控制popup组件显示与隐藏
定义 showPop 函数,修改isShowPopup数据状态,控制popUp组件的显示与隐藏

在van-popup标签上绑定单击事件bind:click-overlay="close",控制popup组件的隐藏

在van-datetime-picker标签上绑定事件bind:cancel="close",控制popup组件隐藏
在van-datetime-picker标签上绑定事件bind:confirm="confirm",收集确定的事件
在van-datetime-picker设置 min-date属性控制最小的时间,同时定义数据状态获取当前的时间(使用JS原生 new Date)

定义 close 函数,设置数据状态isShowPopup
定义 confirm 函数,获取确定的事件参数是event对象 event.detail代表当前时间戳
	通过moment插件将时间戳转为年月日格式,并存储
	关闭popup组件显示
    
渲染日期数据

通过moments或datejs将时间戳转为年月日
npm install moment
import moment from 'moment'
moment(时间戳).format('YYYY-MM-DD')

项目代码

xml
<!-- 日期选项卡 -->
    <van-popup round bind:click-overlay="close" position="bottom" custom-style="height: 50%" show="{{isShowPop}}">
        <van-datetime-picker bind:confirm="confirm" min-date="{{minDate}}" bind:cancel="close" type="date" title='今天是个好日子' />
    </van-popup>
ts
// 弹出日期选择卡
    showPop(){
        this.setData({
            isShowPop: true
        })
    },
    //收集确定的预期时间
    confirm(event) {
        this.setData({
            // 把当前时间的字符串 处理为 响应格式
            deliveryDate: moment(event.detail).format('YYYY-MM-DD'),
            // 关闭pop
            isShowPop: false
        })
    },
    //遮罩层与取消
    close() {
        this.setData({
            isShowPop: false
        })
    },

默认地址

ts
默认地址和添加收货人 使用wx:if控制显示和隐藏,通过userInfo.name不存在,则显示添加收货人,否则显示默认地址

点击添加收货人
	跳转到地址列表页面(没有则显示新建地址按钮)
xml
<view class="address_card">
    <!-- 用户没有默认地址 -->
    <navigator wx:if="{{!userAddress.name}}" class="flex nav" hover-class="navigator-hover" url="/packageA/pages/address/address" >
        <van-icon size="22px" name="add" />
        <view class="title_text">添加收货人地址</view>
    </navigator>
    <!-- 用户有默认地址,显示默认地址 -->
    <navigator wx:else class="flex nav nav_address" hover-class="navigator-hover" url="/packageA/pages/address/address">
        <view class="address_content">
            <view class="address_title">{{userAddress.fullAddress}}</view>
            <view class="address_full">{{userAddress.tagName}}</view>
            <view class="info">
                <text class="username">{{userAddress.name}}</text>
                <text class="phone">{{userAddress.phone}}</text>
            </view>
        </view>
        <van-icon color="#BBBBBB" name="arrow" size="22px" />
    </navigator>
    <view class="top_line"></view>
</view>

结算支付

ts
封装提交订单的接口 获取订单编号 参数[请求体]
	请求地址
	请求方法
	收货人的id
    订购人的姓名
    订购人手机号
    商品列表
    收货日期

订单详情页面的结算按钮绑定单击事件 submitOrder(){}

定义结算按钮提交订单函数 获取订单号 submitOrder(){}
submitOrder(){
    收集参数
    发送请求,获取数据result
    如果result数据存在,则调用 获取支付参数信息 函数,获取支付参数信息
}

收集 结算按钮提交订单函数 参数 const data={}
	定义 订购人姓名数据状态同时使用 model:value双向绑定
    定义 订购人手机号同时使用 model:value双向绑定
    定义 期望送达日期 通过confim函数收集
    祝福语同时使用 model:value双向绑定
    商品数组 数据状态已经存储了
    
wx.requestPayment({})

根据订单号,获取支付参数信息
封装 获取支付参数信息 接口 参数[订单号]

定义 获取支付参数信息 函数 参数[订单号]
	发送 获取支付参数信息 请求,获取支付参数数据 result
    如果 result.code === 200 发起微信支付
    wx.requestPayment(object)
支付成功的回调
	success(){
        查询支付状态(真实情况),调用 查询支付状态 接口
        如果响应的状态是成功的
        	跳转到支付成功页面
           	新建一个支付成功页面
                支付成功页面放一个图片和一个按钮,按钮跳转首页,使用wx.reLaunch方法跳转到tabBar首页
            
       
    }
封装 查询支付状态 接口 参数[订单号] 返回订单的支付状态(向后台发送请求)

报错问题

ts
包报错,把构建后的文件夹删除,把node-models文件夹删除,然后重新安装包,重新构建就可以了

详细代码

ts
import { reqCart, reqBuyNowGoodsInfo, reqAdress, reqSubmit, reqPayInfo, reqPayStatus } from '../../api/index'
import moment from 'moment';
Page({

    /**
     * 页面的初始数据
     */
    data: {
        userAddress: {}, //收货人的地址
        goodsId: 0, //存储立即购买而来的商品的ID
        remarks: '', //存储立即购买的携带祝福语
        cartList: [], //展示商品的数组
        totalPrice: 0, //存储商品的总价
        buyName: '', //收集订购人的名字
        buyPhone: '', //订购人手机号码
        isShowPop: false, //控制pop弹窗显示与隐藏
        minDate: new Date().getTime(), //日期显示最小的时间
    },

    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {
        // 携带祝福语:立即购买而来
        if (options.remarks) {
            // 存储祝福语与立即购买商品的ID
            this.setData({
                goodsId: options.goodsId,
                remarks: options.remarks
            });
            // 调用 获取立即购买商品 方法
            this.getGoodsByImme()

        } else {
            // 调用 获取购物车商品 方法
            this.getGoodsByCart();
        }
    },
    //  获取 立即购买商品 方法
    async getGoodsByImme(){
        // 解构出 商品id和祝福语
        const {goodsId,remarks} = this.data;
        // 发送请求 获取立即购买的商品的信息
        const result = await reqBuyNowGoodsInfo(goodsId, remarks);
        // 如果响应状态码为200
        if (result.code == 200) {
            // 存储数据状态
            this.setData({
                cartList: result.data.cartVoList, //商品信息
                totalPrice: result.data.totalAmount, //立即购买商品总价
            })
        }
    },
    // 获取 购物车商品 方法
    async getGoodsByCart (){
        // 发送获取购物车请求
        const result = await reqCart();
        // 判断响应码是否为200
        if (result.code == 200) {
            // 存储购物车的数据
            this.setData({
                cartList: result.data
            });
            // 获取购物车数据完毕以后计算总价
            this.computedTotal();
        }
    },
    // 计算购物车商品总价
    computedTotal(){
        const total = this.data.cartList.reduce((prev,next)=>{
            return prev += next.count * next.price;
        },0)
        //更新总价
        this.setData({
            totalPrice:total
        })

    },
    // 获取用户 收货地址
    async getUserAddress() {
        const result = await reqAdress();
        if (result.code == 200) {
            this.setData({
                userAddress: result.data
            })
        }
    },
    // 弹出日期选择卡
    showPop(){
        this.setData({
            isShowPop: true
        })
    },
    //收集确定的预期时间
    confirm(event) {
        this.setData({
            // 把当前时间的字符串 处理为 响应格式
            deliveryDate: moment(event.detail).format('YYYY-MM-DD'),
            // 关闭pop
            isShowPop: false
        })
    },
    //遮罩层与取消
    close() {
        this.setData({
            isShowPop: false
        })
    },
    // 结算按钮回调
    async submitOrder(){
        //整理参数
        const data = {
            userAddressId: this.data.userAddress.id, //收货人地址的ID
            buyName: this.data.buyName, //订购人姓名
            buyPhone: this.data.buyPhone, //订购人手机号码
            deliveryDate: this.data.deliveryDate, //期望送达日期
            remarks: this.data.remarks, //祝福语
            cartList: this.data.cartList, //商品数组
        }
        // 发送请求
        const result = await reqSubmit(data);
        if (result.code == 200) {
            console.log("订单号是 ",result.data)
            //获取支付参数信息
            this.getPayParams(result.data);
        }
    },
    //获取wx支付相关的参数
    async getPayParams(orderNo) {
        //获取支付参数
        const result = await reqPayInfo(orderNo);
        console.log('获取的订单参数是 ',result.data);
        if (result.code == 200) {
            //发起微信支付:你们没有办法真机测试
            wx.requestPayment({
                nonceStr: result.data.nonceStr,
                package: result.data.package,
                paySign: result.data.paySign,
                timeStamp: result.data.timeStamp,
                signType: result.data.signType,
                //支付成功的回调
                async success() {
                    // 询问后台服务器支付的结果在进行下一步处理
                    const result = await reqPayStatus(orderNo);
                    console.log("询问服务支付结果 ",result)
                    if (result.code == 200) {
                        wx.navigateTo({
                            url: '/pages/order/paySuccess/paysuccess',
                        })
                    }
                }
            })
        }
    },
    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady: function () {

    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {
        //不管是购物车而来、还是立即购买而来都需要收货人的地址
        this.getUserAddress();
    },

    /**
     * 生命周期函数--监听页面隐藏
     */
    onHide: function () {

    },

    /**
     * 生命周期函数--监听页面卸载
     */
    onUnload: function () {

    },

    /**
     * 页面相关事件处理函数--监听用户下拉动作
     */
    onPullDownRefresh: function () {

    },

    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom: function () {

    },

    /**
     * 用户点击右上角分享
     */
    onShareAppMessage: function () {

    }
})

order.wxml

xml
<!-- 订单详情 -->
<view class="order_detail_container">
    <!-- 地址管理 -->
    <view class="address_card">
        <!-- 用户没有默认地址 -->
        <navigator wx:if="{{!userAddress.name}}" class="flex nav" hover-class="navigator-hover" url="/packageA/pages/address/address" >
            <van-icon size="22px" name="add" />
            <view class="title_text">添加收货人地址</view>
        </navigator>
        <!-- 用户有默认地址,显示默认地址 -->
        <navigator wx:else class="flex nav nav_address" hover-class="navigator-hover" url="/packageA/pages/address/address">
            <view class="address_content">
                <view class="address_title">{{userAddress.fullAddress}}</view>
                <view class="address_full">{{userAddress.tagName}}</view>
                <view class="info">
                    <text class="username">{{userAddress.name}}</text>
                    <text class="phone">{{userAddress.phone}}</text>
                </view>
            </view>
            <van-icon color="#BBBBBB" name="arrow" size="22px" />
        </navigator>
        <view class="top_line"></view>
    </view>
    <!-- 订购人信息 -->
    <van-cell-group>
        <van-field label-class="filed-label" label="订购人姓名" placeholder="订购人的姓名" model:value="{{buyName}}"></van-field>
        <van-field label-class="filed-label" model:value="{{buyPhone}}" label="订购人手机号" type="number" maxlength="11" placeholder="订购人的手机号" />
        <van-cell title="期望送达日期" value="{{ deliveryDate}}" is-link bind:tap="showPop" />
        <van-cell title="订单备注" use-label-slot>
            <textarea model:value="{{remarks}}" slot="label" class="form-textarea" placeholder="美满的祝福语啊!" name="textarea" />
        </van-cell>
    </van-cell-group>
    <!-- 商品列表信息 -->
    <view class="goods_wrapper">
        <view class="goods_list">
            <view class="goods_item" wx:for="{{cartList}}" wx:key="goodsId">
                <view class="img">
                    <image src="{{item.imageUrl}}" />
                </view>
                <view class="content">
                    <view class="goods_title">{{item.name}}</view>
                    <view class="goods_price">{{item.price}}</view>
                </view>
                <view class="num">
                    <van-stepper value="{{item.count}}" />
                </view>
            </view>
        </view>
        <!-- 支付方式 -->
        <view class="time-wraper flex">
            <image src="https://img02.hua.com/m/images/m_payment_wxzf.png" />
            <view class="title">支付方式</view>
            <van-checkbox value="{{true}}"></van-checkbox>
        </view>
    </view>
    <!-- 底部结算 -->
    <view class="footer flex">
        <view class="left">¥{{totalPrice}}</view>
        <viwe class="right" bind:tap="submitOrder">去结算</viwe>
    </view>
    <!-- 日期选项卡 -->
    <van-popup round bind:click-overlay="close" position="bottom" custom-style="height: 50%" show="{{isShowPop}}">
        <van-datetime-picker bind:confirm="confirm" min-date="{{minDate}}" bind:cancel="close" type="date" title='今天是个好日子' />
    </van-popup>
</view>

order.json

json
{
    "navigationBarTitleText": "订单详情",
    "usingComponents": {
        "van-icon": "@vant/weapp/icon/index",
        "van-field": "@vant/weapp/field/index",
        "van-cell": "@vant/weapp/cell/index",
        "van-stepper": "@vant/weapp/stepper/index",
        "van-checkbox": "@vant/weapp/checkbox/index",
        "van-popup": "@vant/weapp/popup/index",
        "van-cell-group": "@vant/weapp/cell-group/index",
        "van-datetime-picker": "@vant/weapp/datetime-picker/index"
    }
}

地址页面

用户地址列表

ts
封装 获取订单收货人地址接口 参数[token] 

在订单详情页面的 onLoad(){} 中定义调用 订单收货人地址 函数 getAddress(){}

getAddress(){
    函数中发送 获取订单收货人地址 接口 
	定义收货人地址 数据状态
	渲染订单页面 收货人数据
}

定义获取地址列表数据函数
	发送请求
    定义响应式数据,存储数据

封装获取地址列表数据 接口api 参数[token]

地址列表页面的onShow()生命周期发送请求
	调用获取地址列表函数

点击新建地址按钮
	跳转到新增地址页面
    
使用wx:if和wx:else控制 空数据和地址列表的条件渲染

使用wx:for渲染地址列表数据

地址列表数据渲染
js
Page({
    data: {
        addressArr: [], //存储全部收件人地址
    },
    // 获取用户地址列表
    async getUserAddress() {
    	const result = await reqAddressList();
        if (result.code == 200) {
            this.setData({
                addressArr: result.data
            })
        }
	},
    // 编辑按钮回调
    edit(event) {
        const id = event.currentTarget.dataset.id;
        wx.navigateTo({
            url: `/packageA/pages/address/add/add?id=${id}`,
        })
    },
    // 删除按钮回调
    async del(event) {
        const id = event.currentTarget.dataset.id;
        const result = await reqDeleteUser(id);
        if(result.code==200){
            //再次获取全部收件人地址
            this.getUserAddress();
        }
    },
    //每一次页面出现的时候都获取一次地址列表的数据
    onShow() {
        //获取地址列表
        this.getUserAddress();
    },
})
xml
<view class="list-warpper" wx:if="{{addressArr.length}}">
    <view class="title">我的收货地址</view>
    <!-- 展示收件人的信息 -->
    <view class="list-item flex" wx:for="{{addressArr}}" wx:key="id">
        <view class="info">
            <view class="address-info">
                <text>{{item.address}}</text>
                <text wx:if="{{item.isDefault==1}}" class="default-tag">默认</text>
            </view>
            <view class="user-info">
                <view class="tag">{{item.tagName}}</view>
                <text>{{item.name}} {{item.phone}}</text>
            </view>
        </view>
        <view class="edit-address">
            <van-icon bind:tap="edit" data-id="{{item.id}}" class="edit" name="edit" size="22px" color="#999" />
            <van-icon data-id="{{item.id}}" bind:tap="del" name="delete" size="22px" color="#999" />
        </view>
    </view>
</view>
<!-- 当没有收件人的信息的时候展示 -->
<van-empty description="还没有收获地址,快去添加吧~" wx:else/>
<view class="footer">
    <view class="btn">
        <navigator url="/packageA/pages/address/add/add">
            新建地址
        </navigator>
    </view>
</view>

新增地址页面

收集数据

ts
渲染静态页面和样式

收集数据:收集收件人姓名,收集收件人手机号,收集收件人地址,收件人详细地址,收件人默认地址,tagName

封装 添加收件人 接口api

双向绑定收件人姓名,手机号,收件人详细地址model:value
ts
data: {
    name: '', //新增收件人的名字,
    phone: '', //收件人的手机号码
    provinceCode: '', //省的区域编号
    cityCode: '', //市
    districtCode: '', //区
    address: '', //详细地址
    tagName: '家', //标签
    isDefault: 0, //默认地址
    //展示地址:省/市/区
    region: '',
    // 存储 用户收货地址id
    id: ''
},
xml
<form class="form-content">
    <van-cell-group>
        <!-- 收件人 -->
        <van-field placeholder="收件人" label-class="filed-label" model:value="{{ name }}" label="收货人" />
        <!-- 手机号 -->
        <van-field placeholder="手机号" label-class="filed-label" model:value="{{ phone }}" label="手机号" maxlength="11" />
        <!-- 微信小程序自带的组件,并非vant -->
        <picker mode="region" bindchange="selectCity">
            <van-field label-class="filed-label" label="地址" value="{{region}}" placeholder="省/市/区" />
        </picker>
        <van-field label-class="filed-label" model:value="{{ address }}" label="详细地址" placeholder="门牌号等(例如:10栋1001号)" />
        <van-cell title="标签" title-width="94px">
            <van-radio-group model:value="{{ tagName }}" direction="horizontal">
                <van-radio name="家">家</van-radio>
                <van-radio name="公司">公司</van-radio>
            </van-radio-group>
        </van-cell>
        <van-cell title="是否设置为默认地址">
            <switch checked="{{isDefault}}" bindchange="handler" />
        </van-cell>

    </van-cell-group>
    </form>

获取省市区

ts
使用微信小程序组件picker组件:mode="region",绑定单击事件 bind:change="selectCity"
定义 selectCity事件回调(点击确定的时候触发)参数[event] event.detail 是省市区的编码
	this.setData({
        定义省编码:event.detail.code[0]
    })
定义 省市区地址编码 region
	this.setData({
        region:event.detail.value.join('/')
    })

picker组件中使用 vant-field组件填充内容
xml
<picker mode="region" bindchange="selectCity">
    <van-field label-class="filed-label" label="地址" value="{{region}}" placeholder="省/市/区" />
</picker>
js
//picker的change事件
selectCity(event) {
    //收集数据:请求携带
    this.setData({
        provinceCode: event.detail.code[0], //省的code
        cityCode: event.detail.code[1], //市
        districtCode: event.detail.code[2], //区
        region: event.detail.value.join('/')
    })
},

收集标签数据

ts
收集标签 (双向绑定)
使用van-radio标签的name属性设置 "家""公司" 属性值
使用van-radio-group设置model:value属性双向绑定,默认绑定的数值为单选name属性设置的属性值
xml
<van-cell title="标签" title-width="94px">
    <van-radio-group model:value="{{ tagName }}" direction="horizontal">
        <van-radio name="家">家</van-radio>
        <van-radio name="公司">公司</van-radio>
    </van-radio-group>
</van-cell>

收集默认状态

ts
使用微信小程序的 switch组件 绑定bind:change="handle"事件回调
定义 handle 函数 参数[event]
xml-dtd
<van-cell title="是否设置为默认地址">
    <switch checked="{{isDefault}}" bindchange="handler" />
</van-cell>
ts
 //收集开关的数据
handler(event) {
    this.setData({
        isDefault: event.detail.value ? 1 : 0
    })
},

保存按钮

ts
点击确定按钮,发送请求绑定 bind:tap="save"

在 onLoad 生命周期函数中 通过存在id 判断是 更新操作还是新增操作
onLoad(options) {
    // 如果id存在 则代表 编辑按钮 过来的
    if (options.id) {
        this.setData({
            // 存储地址的id
            id: options.id
        });
        // 获取 当前编辑按钮点击的 地址信息
        this.getAddressById();
    }
},
      
async save() {
    如果 id 不存在则代表是新增地址
	整理参数
    发新增地址请求
    成功返回数据
    路由返回到地址列表中
    地址列表中重新获取数据
}
ts
Page({
    //保存按钮
    async save() {
        // 如果id存在,则代表更新按钮
        if (this.data.id) {
            var result = await reqUpdateAddress(this.data);
        } else {
            //添加
            var result = await reqAddAddress(this.data);
        }
        //判断添加或者更新是否成功
        if (result.code == 200) {
            // 路由返回
            wx.navigateBack();
        }
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad(options) {
        // 如果id存在 则代表 编辑按钮 过来的
        if (options.id) {
            this.setData({
                // 存储地址的id
                id: options.id
            });
            // 获取 当前编辑按钮点击的 地址信息
            this.getAddressById();
        }
    },
})
ts


订单详情页面,展示默认地址,把获取 收货人函数 放在 onShow(){} 钩子中

编辑用户地址

用户地址列表页面

ts
小程序的页面跳转只能带基本数据类型 path?key=1&name=auqa
所以只能通过传递一个id,然后通过这个id重新发送请求获取地址数据
在编辑按钮上设置自定义属性data-id={{id}},同时绑定单击事件 bind:tap="edit"
定义edit函数
	路由跳转到新建地址页面,并传递id
    
编辑按钮绑定单击事件 edit
edit(event) {
    // 获取点击对象的id
    const id = event.currentTarget.dataset.id;
    // 路由导航到编辑页面
    wx.navigateTo({
        url: `/packageA/pages/address/add/add?id=${id}`,
    })
},
    
删除按钮绑定单击事件 del
async del(event) {
    // 获取id
    const id = event.currentTarget.dataset.id;
    // 发送请求
    const result = await reqDeleteUser(id);
    if(result.code==200){
        //再次获取全部收件人地址
        this.getUserAddress();
    }
},

新增用户页面

ts
封装 更新 地址列表数据 接口api
封装 通过id获取地址信息数据 接口api

定义 通过id获取地址信息数据 函数
getAddressById(){
    发送 通过id获取地址信息数据 接口api 请求
    存储数据
    处理省市区名字拼接
}

新增地址页面的 onLoad(){} 钩子中获取 query中的id
	如果 option.id 存在,则是 编辑按钮 过来的
    	调用 通过id获取地址信息数据 函数


在保存 save 函数中
	根据 id是否存在判断是 更新操作还是新增操作
    id存在,则发送更新请求
    id不存在,则发送新增请求
    请求响应成功,路由返回
    wx.navigateBack();
ts
// pages/address/add/add.js
import {
    reqAddAddress,
    reqEditAddress,
    reqUpdateAddress
} from '../../../../api/index';

Page({
    async getAddressById() {
        // 由于微信小程序路由跳转不能携带引用类型数据
        // 因此如果是编辑而来,需要根据用户ID发请求获取更新用户地址信息
        const result = await reqEditAddress(this.data.id);
        if (result.code == 200) {
            const {
                name,
                phone,
                address,
                tagName,
                isDefault,
                provinceCode,
                cityCode,
                districtCode,
                provinceName,
                cityName,
                districtName
            } = result.data;
            this.setData({
                name,
                phone,
                address,
                tagName,
                isDefault,
                provinceCode,
                cityCode,
                districtCode,
                region: `${provinceName}/${cityName}/${districtName}`
            })
        }
    },
    onLoad(options) {
        // 如果id存在 则代表 编辑按钮 过来的
        if (options.id) {
            this.setData({
                // 存储地址的id
                id: options.id
            });
            // 获取 当前编辑按钮点击的 地址信息
            this.getAddressById();
        }
    },
    async save() {
        // 如果id存在,则代表更新按钮
        if (this.data.id) {
            var result = await reqUpdateAddress(this.data);
        } else {
            //添加
            var result = await reqAddAddress(this.data);
        }
        //判断添加或者更新是否成功
        if (result.code == 200) {
            // 路由返回
            wx.navigateBack();
        }
    },
})

删除用户地址

用户地址列表

ts
封装 删除地址列表 接口api
在 地址列表的删除 按钮上设置 自定义属性 data-id 绑定单击事件 bind:tap="del"
del(event){
    获取 id
    发送删除地址列表请求
    再次获取收件人地址
}
ts
// 删除按钮回调
async del(event) {
    const id = event.currentTarget.dataset.id;
    const result = await reqDeleteUser(id);
    if(result.code==200){
        //再次获取全部收件人地址
        this.getUserAddress();
    }
},
xml
<view class="edit-address">
    <van-icon bind:tap="edit" data-id="{{item.id}}" class="edit" name="edit" size="22px" color="#999" />
    <van-icon data-id="{{item.id}}" bind:tap="del" name="delete" size="22px" color="#999" />
</view>

微信小程序分包

ts
小程序的项目上限不能超过2M,微信不需要审核代码,直接打回
如果项目超出了2M,设置分包
注意:tabBar页面必须放在主包内

配置方法

ts
微信小程序 分包

配置方法
首先在根目录新建文件夹 packageA
把pages文件夹内的A页面移动到 packageA/pages中
删除app.json应用配置文件中的 page:{} 对应路由字段 A
在app.json 应用配置文件中添加 subpackages字段中,添加子包的路由A
"subpackages":[
    {
        "root":"packageA",
        "pages":[
            "pages/A"
        ]
    }
]

在页面使用时,需要在路由地址上添加 root路径 例如:
<navigator wx:else class="flex nav nav_address" hover-class="navigator-hover" url="/packageA/pages/address/address">

注意:
独立分包:子包不能够使用全局静态资源
包大于2M真机就测试不了,需要使用分包