8.1 效果
8.2 收货地址
8.2.1 收获地址按钮样式
注意在cart文件夹下
<!-- 收货地址 -->
<view class="revice_address_row">
<view class="address_btn">
<button
bindtap="handleChooseAddress"
type="primary"
plain
>获取收货地址
</button>
</view>
</view>
.revice_address_row{
.address_btn{
padding: 20rpx;
button{
width: 60%;
}
}
}
8.2.2 使用es7的async语法获取收获地址
(1)思路
1 绑定点击事件
2 调用小程序内置 api 获取用户的收货地址 wx.chooseAddress(但单单用这种方法不行,取消后无法再去授权)
3 获取用户对小程序所授予获取地址的权限状态(scope) wx.getSetting (用这种方法)
-
假设用户点击获取收货地址的提示框 确定 authSetting scope.address
scope 值 true 直接调用 获取收货地址 -
假设用户从来没有调用过收货地址的api
scope undefined 直接调用 获取收货地址 -
假设用户点击获取收货地址的提示框 取消
scope 值 false- 诱导用户自己打开授权设置页面(wx.openSetting) 当用户重新给予获取地址权限的时候
- 获取收货地址
-
把获取到的收货地址存入到本地存储中 wx.setStorageSync
(2)代码
- 在util文件夹下新建一个asyncWx.js 用来写微信的内置api请求
// 获取配置信息
export const getSetting=()=>{
return new Promise((resolve,reject)=>{
wx.getSetting({
success: (result) => {
resolve(result);
},
fail: (err) =>{
reject(err);
}
});
})
}
// 跳出是否允许访问地址的弹窗
export const chooseAddress=()=>{
return new Promise((resolve,reject)=>{
wx.chooseAddress({
success: (result) => {
resolve(result);
},
fail: (err) =>{
reject(err);
}
});
})
}
// 打开设置界面
export const openSetting=()=>{
return new Promise((resolve,reject)=>{
wx.openSetting({
success: (result) => {
resolve(result);
},
fail: (err) => {
reject(err);
}
});
})
}
- 在 index.js 中使用es7的async语法写获取收获地址的流程
// 引入用来发送请求的方法 一定要把路径补全
import {getSetting,chooseAddress,openSetting} from "../../utils/asyncWx.js"
import regeneratorRuntime from "../../lib/runtime/runtime.js";
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
async handleChooseAddress(e){
try {
// 1 获取权限状态
const res1 = await getSetting();
// 发现一些属性名称很怪异的时候,都要使用 []形式来获取属性
const scopeAddress = res1.authSetting["scope.address"]
// 2 判断权限状态
if(scopeAddress === false){
await openSetting();
}
// 4 调用获取收获地址的 api
const address = await chooseAddress();
// 5 存入到缓存中
wx.setStorageSync('address', address);
} catch (error) {
console.log(error);
}
}
})
8.2.3 收获地址和按钮切换显示
(1)思路
本地存储中有地址数据,不显示按钮显示地址信息;否则,显示按钮不显示地址信息
(2)代码
<!-- 收货地址 -->
<view class="revice_address_row">
<!-- 当收货地址不存在 按钮显示 因为空对象bool类型也是true 所以写 address.userName -->
<view class="address_btn" wx:if="{{!address.userName}}">
<button
bindtap="handleChooseAddress"
type="primary"
plain
>获取收货地址
</button>
</view>
<!-- 当收货地址 存在 详细信息就显示 -->
<view wx:else class="user_info_row">
<view class="user_info">
<view>{{address.userName}}</view>
<view>{{address.all}}</view>
</view>
<view class="user_phone">{{address.telNumber}}</view>
</view>
</view>
.revice_address_row{
.address_btn{
padding: 20rpx;
button{
width: 60%;
}
}
.user_info_row{
display: flex;
padding: 20rpx;
.user_info{
flex: 5;
}
.user_phone{
flex: 3;
text-align: right;
}
}
}
// 引入用来发送请求的方法 一定要把路径补全
import {getSetting,chooseAddress,openSetting} from "../../utils/asyncWx.js"
import regeneratorRuntime from "../../lib/runtime/runtime.js";
Page({
data: {
address:{}
},
/*
使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
重新打开可以初始化页面
*/
onShow(){
// 1 获取缓存中的收货地址信息
const address = wx.getStorageSync('address');
this.setData({
address
})
},
async handleChooseAddress(e){
try {
// 1 获取权限状态
const res1 = await getSetting();
// 发现一些属性名称很怪异的时候,都要使用 []形式来获取属性
const scopeAddress = res1.authSetting["scope.address"]
// 2 判断权限状态
if(scopeAddress === false){
await openSetting();
}
// 4 调用获取收获地址的 api
let address = await chooseAddress();
address.all = address.provinceName + address.cityName + address.countyName + address.detailInfo;
// 5 存入到缓存中
wx.setStorageSync('address', address);
} catch (error) {
console.log(error);
}
}
})
8.3 购物车内容动态渲染
8.3.1 插件
这里推荐一个插件css tree,当你要写页面样式时,你可以使用这个插件在你的wxml页面选择准备要写样式的组件,ctrl+shift+p 选择css tree 即可看到生成的css样式文件,你可复制它再到主样式文件中粘贴,当然,如果想要删除相同的多余文字,可以选择它并按住ctrl + shift + L 多选删除
8.3.2 代码
<!-- 购物车内容 -->
<view class="cart_content">
<view class="cart_title">购物车</view>
<view class="cart_main">
<view
class="cart_item"
wx:for="{{cart}}"
wx:key="goods_id"
>
<!-- 复选框 -->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.goods_id}}" bindchange="handeItemChange">
<checkbox checked="{{item.checked}}"></checkbox>
</checkbox-group>
</view>
<!-- 商品图片 -->
<navigator class="cart_img_wrap">
<image mode="widthFix" src="{{item.goods_small_logo}}"></image>
</navigator>
<!-- 商品信息 -->
<view class="cart_info_wrap">
<view class="goods_name">{{item.goods_name}}</view>
<view class="goods_price_wrap">
<view class="goods_price">¥{{item.goods_price}}</view>
<view class="cart_num_tool">
<view class="num_edit">-</view>
<view class="goods_num">{{item.num}}</view>
<view class="num_edit">+</view>
</view>
</view>
</view>
</view>
</view>
</view>
.cart_content {
.cart_title {
padding: 20rpx;
font-size: 36rpx;
color: var(--themeColor);
border-top: 1rpx solid currentColor;
border-bottom: 1rpx solid currentColor;
}
.cart_main {
.cart_item {
display: flex;
padding: 10rpx;
border-bottom: 1rpx solid #ccc;
.cart_chk_wrap {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
checkbox-group {
checkbox {
}
}
}
.cart_img_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
image {
width: 80%;
}
}
.cart_info_wrap {
flex: 4;
display: flex;
flex-direction: column;
justify-content: space-around;
.goods_name {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp:2;
color: #666;
}
.goods_price_wrap {
display: flex;
justify-content: space-between;
.goods_price {
color: var(--themeColor);
font-size: 34rpx;
}
.cart_num_tool {
display: flex;
.num_edit {
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
border: 1rpx solid #ccc;
}
.goods_num {
width: 55rpx;
height: 55rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
}
}
}
data: {
address:{},
cart:[]
},
/*
使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
重新打开可以初始化页面
*/
onShow(){
// 1 获取缓存中的收货地址信息
const address = wx.getStorageSync('address');
// 2 获取缓存中的购物车数组
const cart = wx.getStorageSync('cart');
this.setData({
address,
cart
})
},
8.4 购物车底部工具栏
8.4.1 页面布局
<!-- 底部工具栏 -->
<view class="footer_tool">
<!-- 全选 -->
<view class="all_chk_wrap">
<checkbox-group>
<checkbox>全选</checkbox>
</checkbox-group>
</view>
<!-- 总价格 -->
<view class="total_price_wrap">
<view class="total_price">
合计:
<text class="total_price_text">¥999</text>
</view>
<view>包含运费</view>
</view>
<!-- 结算 -->
<view class="order_pay_wrap">
结算(2)
</view>
</view>
.footer_tool {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 90rpx;
background-color: #fff;
display: flex;
border-top: 1rpx solid #ccc;
.all_chk_wrap {
flex: 2;
display: flex;
justify-content: center;
align-items: center;
}
.total_price_wrap {
flex: 5;
padding-right: 15rpx;
text-align: right;
.total_price {
.total_price_text {
color: var(--themeColor);
font-size: 34rpx;
font-weight: 600;
}
}
}
.order_pay_wrap {
flex: 3;
background-color: var(--themeColor);
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
}
}
8.4.2 全选的数据展示
8.4.2.1 效果
如果购物车的商品都被选中,则全选按钮也会自动选上,否则,全选按钮不会被选上
8.4.2.2 思路
1 在 onShow 周期函数中获取缓存中的购物车数组
2 根据购物车中的商品数据 所有的商品都被选中 checked=true 全选就被选中
8.4.2.3 代码
(1)因为购物车商品的复选框,应该在加入购物车时,同时被选上,所以需在商品详情的index.js文件中加入 this.GoodsInfo.checked = true; 然后,在购物车的index.wxml 中加入
//点击 加入购物车
handleCartAdd(e){
// 1 获取缓存中的购物车数据 数组格式 如果没有赋予空数组
let cart = wx.getStorageSync('cart') || [];
// 2 判断 商品对象是否存在于购物车中
// findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
let index = cart.findIndex(v=>v.goods_id === this.GoodsInfo.goods_id);
console.log(index);
if(index === -1){
// 3 不存在 第一次添加
this.GoodsInfo.num = 1;
this.GoodsInfo.checked = true;
...
<!-- 复选框 -->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.goods_id}}">
<checkbox checked="{{item.checked}}"></checkbox>
</checkbox-group>
(2)全选的行为
data: {
address:{},
cart:[],
allChecked: false
},
/*
使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
重新打开可以初始化页面
*/
onShow(){
// 1 获取缓存中的收货地址信息
const address = wx.getStorageSync('address');
const cart = wx.getStorageSync('cart') || [];
// 计算全选
// every 是遍历数组方法 会接收一个回调函数 如果每个回调函数都返回true 那么every方法的返回值为true
// 注意: 空数组调用every,返回值也为 true
const allChecked = cart.length?cart.every(v=>v.checked):false;
this.setData({
address,
cart,
allChecked
})
},
<!-- 全选 -->
<view class="all_chk_wrap">
<checkbox-group>
<checkbox checked="{{allChecked}}">全选</checkbox>
</checkbox-group>
</view>
8.4.3 总价格和总数量
8.4.3.1 效果
计算所有被选中的商品总价和总数量
8.4.3.2 思路
1 商品都需要被选中 我们才拿它来计算
2 获取购物车数组
3 遍历
4 判断商品是否被选中
5 总价格 += 商品的单价 * 商品的数量
6 总数量 += 商品的数量
7 把计算出来的价格和数量返回到data中
8.4.3.3 代码
data: {
address:{},
cart:[],
allChecked: false,
totalPrice: 0,
totalNum: 0
},
/*
使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
重新打开可以初始化页面
*/
onShow(){
// 1 获取缓存中的收货地址信息
const address = wx.getStorageSync('address');
const cart = wx.getStorageSync('cart') || [];
// 计算全选
// every 是遍历数组方法 会接收一个回调函数 如果每个回调函数都返回true 那么every方法的返回值为true
// 注意: 空数组调用every,返回值也为 true
// 为了提高性能,不用那么多个循环
// const allChecked = cart.length?cart.every(v=>v.checked):false;
let allChecked = true;
// 总价格和总数量
let totalPrice = 0;
let totalNum = 0;
cart.forEach(v=>{
if(v.checked){
totalPrice += v.num * v.goods_price;
totalNum += v.num;
}else{
allChecked = false;
}
})
// 判断数组是否为空
allChecked = cart.length!=0?allChecked:false;
this.setData({
address,
cart,
allChecked,
totalPrice,
totalNum
})
},
<!-- 总价格 -->
<view class="total_price_wrap">
<view class="total_price">
合计:
<text class="total_price_text">¥{{totalPrice}}</text>
</view>
<view>包含运费</view>
</view>
<!-- 结算 -->
<view class="order_pay_wrap">
结算({{totalNum}})
</view>
然而,这还不行,你点复选框,总价和数量不会改变
8.4.4 商品选中
8.4.4.1 效果
只计算被选中的商品的总价格和数量,没被选中的商品不计算,简单来说是,利用 8.4.3 总价格和总数量 结合复选框实现总价和数量的计算
8.4.4.2 思路
1 绑定复选框的change事件
2 获取到被修改的商品对象
3 商品对象的选中状态 取反
4 重新填充回data中和缓存中
5 重新计算全选、总价格和总数量
8.4.4.3 代码
(1)绑定复选框的change事件
<!-- 复选框 -->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
<checkbox checked="{{item.checked}}"></checkbox>
</checkbox-group>
</view>
(2)获取到被修改的商品对象、商品对象的选中状态 取反、重新计算全选、总价格和总数量
// 商品选中
handleItemChange(e){
// 获取商品的 goods_id
const goods_id = e.currentTarget.dataset.id;
// 获取购物车数组
let {cart} = this.data;
// 获取被修改的商品对象
let index = cart.findIndex(v=>v.goods_id===goods_id);
// 被选中状态取反
cart[index].checked = !cart[index].checked;
// 重新计算总价格和总数量
this.setCart(cart);
},
(3)为了降低代码冗余,定义一个用来重新计算总价格和总数量的方法
// 计算总价格和总数量
setCart(cart){
let allChecked = true;
// 总价格和总数量
let totalPrice = 0;
let totalNum = 0;
cart.forEach(v=>{
if(v.checked){
totalPrice += v.num * v.goods_price;
totalNum += v.num;
}else{
allChecked = false;
}
})
// 判断数组是否为空
allChecked = cart.length!=0?allChecked:false;
this.setData({
cart,
allChecked,
totalPrice,
totalNum
})
wx.setStorageSync('cart', cart)
}
8.4.5 全选和反选
8.4.5.1 效果
全选就会计算购物车中所有商品的总价和总数量,再点一次全选即取消全选,就什么都不买
8.4.5.2 思路
1 全选复选框绑定事件 change
2 获取 data中的全选变量 allChecked
3 直接取反 allChecked=!allChecked
4 遍历购物车数组 让里面 商品 选中状态跟随 allChecked 改变而改变
5 把购物车数组 和 allChecked 重新设置回data 把购物车重新设置回 缓存中
8.4.5.3 代码
<!-- 全选 -->
<view class="all_chk_wrap">
<checkbox-group bindchange="handleAllChange">
<checkbox checked="{{allChecked}}">全选</checkbox>
</checkbox-group>
</view>
// 全选和反选
handleAllChange(){
// 1 获取data中的数据
let { cart, allChecked } = this.data;
// 2 修改值
allChecked = !allChecked;
// 3 循环修改cart数组 中的商品选中状态
cart.forEach(v => v.checked = allChecked);
// 4 把修改后的值 填充回data或者缓存中
this.setCart(cart);
},
8.5 商品数量编辑
8.5.1 思路
1 “+” “-” 按钮 绑定同一个点击事件 区分的关键 自定义属性
1 “+” “+1”
2 “-” “-1”
2 传递被点击的商品id goods_id
3 获取data中的购物车数组 来获取需要被修改的商品对象
4 当 购物车的数量 =1 同时 用户 点击 “-”
使用async语法编写 wx.showModal 的api请求 弹窗提示 询问用户 是否要删除
1 确定 直接执行删除
2 取消 什么都不做
4 直接修改商品对象的数量 num
5 把cart数组 重新设置回 缓存中 和data中 this.setCart
8.5.2 代码
<view class="cart_num_tool">
<view
bindtap="handleItemNumEdit"
data-id="{{item.goods_id}}"
data-operation="{{-1}}"
class="num_edit"
>-
</view>
<view class="goods_num">{{item.num}}</view>
<view
bindtap="handleItemNumEdit"
data-id="{{item.goods_id}}"
data-operation="{{1}}"
class="num_edit"
>+
</view>
</view>
使用async语法编写 wx.showModal 的api请求
// 弹出提示窗口
export const showModal=({content})=>{
return new Promise((resolve,reject)=>{
wx.showModal({
title: '提示',
content: content,
success :(res) =>{
resolve(res);
},
fail:(err)=>{
reject(err);
}
})
})
}
// 商品数量的编辑功能
async handleItemNumEdit(e) {
// 1 获取传递过来的参数
const { operation, id } = e.currentTarget.dataset;
// 2 获取购物车数组
let { cart } = this.data;
// 3 找到需要修改的商品的索引
const index = cart.findIndex(v => v.goods_id === id);
// 4 判断是否要执行删除
if (cart[index].num === 1 && operation === -1) {
// 4.1 弹窗提示
const res = await showModal({ content: "您是否要删除?" });
if (res.confirm) {
// 4.2 从index位开始,删除一个元素
cart.splice(index, 1);
this.setCart(cart);
}
} else {
// 5 进行修改数量
cart[index].num += operation;
// 6 设置回缓存和data中
this.setCart(cart);
}
},
8.6 没有商品的状态显示
代码
<!-- 当cart数组 长度不为0 显示 商品信息 -->
<block wx:if="{{cart.length!==0}}">
<view
class="cart_item"
wx:for="{{cart}}"
wx:key="goods_id"
>
<!-- 复选框 -->
<view class="cart_chk_wrap">
<checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
<checkbox checked="{{item.checked}}"></checkbox>
</checkbox-group>
</view>
<!-- 商品图片 -->
<navigator class="cart_img_wrap">
<image mode="widthFix" src="{{item.goods_small_logo}}"></image>
</navigator>
<!-- 商品信息 -->
。。。
</block>
<block wx:else>
<image mode="widthFix" src="http://hbimg.b0.upaiyun.com/e1b1467beea0a9c7d6a56b32bac6d7e5dcd914f7c3e6-YTwUd6_fw658"></image>
</block>
8.7 结算按钮功能
8.7.1 效果
如果没有获取收货地址,按结算按钮会出现"您还没有选择收货地址"的弹窗;如果没有购买商品,按结算按钮会出现"您还没有选购商品"的弹窗
8.7.2 思路
1 绑定结算按钮 bindtap 事件
2 使用async语法编写 wx.showToast 的api请求 显示消息提示框
3 判断有没有收货地址信息
4 判断用户有没有选购商品
5 经过以上的验证 跳转到 支付页面!
8.7.3 代码
<!-- 结算 -->
<view class="order_pay_wrap" bindtap="handlePay">
结算({{totalNum}})
</view>
// 显示消息提示框
export const showToast=({title})=>{
return new Promise((resolve,reject)=>{
wx.showToast({
title: title,
icon: 'none',
success :(res) =>{
resolve(res);
},
fail:(err)=>{
reject(err);
}
})
})
}
// 结算按钮
async handlePay(){
// 1 判断收货地址
const {address,totalNum}=this.data;
// 判断有没有收货地址
if(!address.userName){
await showToast({title:"您还没有选择收货地址"});
return;
}
// 2 判断用户有没有选购商品
if(totalNum===0){
await showToast({title:"您还没有选购商品"});
return;
}
// 3 跳转到 支付页面
wx.navigateTo({
url: '/pages/pay/index'
});
},